Skip to content
This repository was archived by the owner on Aug 17, 2026. It is now read-only.

chore(sync): upstream develop @ e75445fc - #18

Closed
joeshull wants to merge 199 commits into
mainfrom
sync/upstream-develop
Closed

chore(sync): upstream develop @ e75445fc#18
joeshull wants to merge 199 commits into
mainfrom
sync/upstream-develop

Conversation

@joeshull

Copy link
Copy Markdown

Upstream sync (latest only)

Automation replaces any previous open sync PR. Review this one when ready; older sync PRs are closed as superseded.

  • Upstream: @
  • Into: Usage: upstream-sync.sh <status|merge|verify|pr|ff-develop> [options]

Commands:
status Show ahead/behind vs upstream ref
merge Create/update sync branch and merge upstream (auto-ours on deploy/**)
verify Run structural + focused unit + build gates
pr Open/update PR from current sync branch into main
ff-develop Fast-forward origin/develop to origin/main (mirror)

Env:
UPSTREAM_REF upstream branch (default: develop)
TARGET_BRANCH integration branch (default: main)
SYNC_BRANCH override sync branch (default: sync/upstream-, stable/single PR)
DRY_RUN=1 print actions only for merge/pr
SKIP_BUILD=1 verify skips npm run build
SYNC_ADOPT_UPSTREAM=1 do not auto-checkout --ours for protected paths

  • Branch: (stable; force-updated each run)
  • Approx behind/ahead at open: behind=191 ahead=58

Protected ZGE surfaces (must remain green)

  • self-host installer + systemd/zrok
  • Web Push (, push routes, SW)
  • OMP runtime paths (, tool registry, spawn env)
  • OMP config mirror routes

Policy

  • Conflict default: ours on protected paths (see Usage: upstream-sync.sh <status|merge|verify|pr|ff-develop> [options]

Commands:
status Show ahead/behind vs upstream ref
merge Create/update sync branch and merge upstream (auto-ours on deploy/**)
verify Run structural + focused unit + build gates
pr Open/update PR from current sync branch into main
ff-develop Fast-forward origin/develop to origin/main (mirror)

Env:
UPSTREAM_REF upstream branch (default: develop)
TARGET_BRANCH integration branch (default: main)
SYNC_BRANCH override sync branch (default: sync/upstream-, stable/single PR)
DRY_RUN=1 print actions only for merge/pr
SKIP_BUILD=1 verify skips npm run build
SYNC_ADOPT_UPSTREAM=1 do not auto-checkout --ours for protected paths)

  • Shared hubs (, , ): combined manually
  • Never force-push Usage: upstream-sync.sh <status|merge|verify|pr|ff-develop> [options]

Commands:
status Show ahead/behind vs upstream ref
merge Create/update sync branch and merge upstream (auto-ours on deploy/**)
verify Run structural + focused unit + build gates
pr Open/update PR from current sync branch into main
ff-develop Fast-forward origin/develop to origin/main (mirror)

Env:
UPSTREAM_REF upstream branch (default: develop)
TARGET_BRANCH integration branch (default: main)
SYNC_BRANCH override sync branch (default: sync/upstream-, stable/single PR)
DRY_RUN=1 print actions only for merge/pr
SKIP_BUILD=1 verify skips npm run build
SYNC_ADOPT_UPSTREAM=1 do not auto-checkout --ours for protected paths (sync branch may force-with-lease)

Gates

  • ==> node v22.22.2
    ==> Gate 0: structural
    ==> Gate 0 OK
    ==> Gate 1: focused vitest
    ==> vitest in packages/shared -- src/tests/omp-agent-paths.test.ts src/tests/config-push.test.ts

RUN v4.1.9 /tmp/omp-dashboard-sync-real/packages/shared

[test-isolation] HOME=/tmp/pi-test-SWLXrC (real=/home/joe)
·······················

Test Files 2 passed (2)
Tests 23 passed (23)
Start at 16:52:39
Duration 139ms (transform 51ms, setup 13ms, import 59ms, tests 8ms, environment 0ms)

==> vitest in packages/server -- src/tests/build-push-payload.test.ts src/tests/push-dispatcher.test.ts src/tests/push-vapid.test.ts src/tests/push-trigger-classifier.test.ts src/tests/ws-ticket.test.ts

RUN v4.1.9 /tmp/omp-dashboard-sync-real/packages/server

[test-isolation] HOME=/tmp/pi-test-SWLXrC (real=/home/joe)
····································

Test Files 5 passed (5)
Tests 36 passed (36)
Start at 16:52:39
Duration 147ms (transform 159ms, setup 27ms, import 188ms, tests 25ms, environment 0ms)

==> Gate 1 OK (or skipped missing files)
==> Gate 2: build

@blackbelt-technology/pi-agent-dashboard@0.5.4 build
npm run build --workspace=@blackbelt-technology/pi-dashboard-web

@blackbelt-technology/pi-dashboard-web@0.5.4 build
node scripts/vite-build.mjs build && node scripts/precompress.mjs

vite v6.4.3 building for production...
[plugin-loader] discovered 8 plugin(s): automation, flows, goal, kb, roles, subagents, flows-anthropic-bridge, demo
[vite-dashboard-plugins] Generated plugin-registry.tsx
transforming...
✓ 5257 modules transformed.
rendering chunks...
computing gzip size...
../dist/index.html 1.58 kB │ gzip: 0.55 kB
../dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff 4.42 kB
../dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 4.93 kB
../dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 5.21 kB
../dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 5.47 kB
../dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff 5.98 kB
../dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff 6.19 kB
../dist/assets/KaTeX_Size1-Regular-C195tn64.woff 6.50 kB
../dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 6.91 kB
../dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 6.91 kB
../dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf 7.59 kB
../dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff 7.66 kB
../dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff 7.72 kB
../dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 9.64 kB
../dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 10.34 kB
../dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf 10.36 kB
../dist/assets/KaTeX_Script-Regular-D5yQViql.woff 10.59 kB
../dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 11.32 kB
../dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 11.35 kB
../dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf 11.51 kB
../dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 12.03 kB
../dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 12.22 kB
../dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf 12.23 kB
../dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff 12.32 kB
../dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf 12.34 kB
../dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf 12.37 kB
../dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff 13.21 kB
../dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff 13.30 kB
../dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 13.57 kB
../dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff 14.11 kB
../dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff 14.41 kB
../dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff 16.03 kB
../dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 16.40 kB
../dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 16.44 kB
../dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf 16.65 kB
../dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 16.78 kB
../dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 16.99 kB
../dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff 18.67 kB
../dist/assets/KaTeX_Math-Italic-DA0__PXp.woff 18.75 kB
../dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff 19.41 kB
../dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf 19.44 kB
../dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf 19.57 kB
../dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf 19.58 kB
../dist/assets/KaTeX_Main-Italic-BMLOBm91.woff 19.68 kB
../dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf 22.36 kB
../dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf 24.50 kB
../dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 25.32 kB
../dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 26.27 kB
../dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf 27.56 kB
../dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 28.08 kB
../dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff 29.91 kB
../dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff 30.77 kB
../dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf 31.20 kB
../dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf 31.31 kB
../dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf 32.97 kB
../dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff 33.52 kB
../dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf 33.58 kB
../dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf 51.34 kB
../dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf 53.58 kB
../dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf 63.63 kB
../dist/assets/codicon-DCmgc-ay.ttf 80.34 kB
../dist/assets/editor.worker-BCzxt1at.js 232.35 kB
../dist/assets/json.worker-B7c_PmGb.js 364.77 kB
../dist/assets/html.worker-CKrFyw_2.js 674.50 kB
../dist/assets/css.worker-DaIe3gwK.js 1,011.91 kB
../dist/assets/pdf.worker.min-yatZIOMy.mjs 1,375.84 kB
../dist/assets/xterm-6GBZ9nXN.css 5.24 kB │ gzip: 1.92 kB
../dist/assets/diff-D_jQ-W02.css 21.33 kB │ gzip: 3.36 kB
../dist/assets/monaco-D6kYW_CN.css 133.00 kB │ gzip: 21.09 kB
../dist/assets/index-qdTqjsAm.css 160.86 kB │ gzip: 29.39 kB
../dist/assets/pdf.worker.min-ocdtAcA3.js 0.07 kB │ gzip: 0.09 kB
../dist/assets/channel-DVkCQ1b3.js 0.11 kB │ gzip: 0.13 kB
../dist/assets/init-Gi6I4Gst.js 0.15 kB │ gzip: 0.13 kB
../dist/assets/chunk-2Q5K7J3B-C44KYlEO.js 0.19 kB │ gzip: 0.16 kB
../dist/assets/chunk-XXDRQBXY-Cf8jc-jU.js 0.26 kB │ gzip: 0.22 kB
../dist/assets/chunk-JWPE2WC7-DQwlVora.js 0.30 kB │ gzip: 0.20 kB
../dist/assets/chunk-5VM5RSS4-BZxHvEht.js 0.37 kB │ gzip: 0.27 kB
../dist/assets/chunk-VR4S4FIN-Dx6p_Wq8.js 0.53 kB │ gzip: 0.37 kB
../dist/assets/stateDiagram-v2-6OUMAXLB-BXOp3Mx-.js 0.63 kB │ gzip: 0.40 kB
../dist/assets/classDiagram-OUVF2IWQ-DDJ0ZhY8.js 0.67 kB │ gzip: 0.42 kB
../dist/assets/classDiagram-v2-EOCWNBFH-DDJ0ZhY8.js 0.67 kB │ gzip: 0.42 kB
../dist/assets/swimlanesDiagram-G3AALYLV-lGxfXPag.js 0.75 kB │ gzip: 0.46 kB
../dist/assets/infoDiagram-FWYZ7A6U-CXDYphhq.js 0.79 kB │ gzip: 0.50 kB
../dist/assets/MarkdownEditor-CCf01RTc.js 1.06 kB │ gzip: 0.65 kB
../dist/assets/ordinal-Cboi1Yqb.js 1.19 kB │ gzip: 0.57 kB
../dist/assets/sizeCapture-X5ZJPWSS-Ce2CtUNJ.js 1.20 kB │ gzip: 0.66 kB
../dist/assets/railroadDiagram-RFXS5EU6-ChgrLXps.js 1.81 kB │ gzip: 0.85 kB
../dist/assets/chunk-32BRIVSS-CDa7LggN.js 1.91 kB │ gzip: 0.85 kB
../dist/assets/abnfDiagram-VRR7QNED-BBs3doU3.js 2.01 kB │ gzip: 0.99 kB
../dist/assets/pegDiagram-2B236MQR-DpcaDiNu.js 2.16 kB │ gzip: 1.02 kB
../dist/assets/ebnfDiagram-CCIWWBDH-BPgOuvTU.js 2.21 kB │ gzip: 0.98 kB
../dist/assets/monaco-setup-CyE3tqBZ.js 2.40 kB │ gzip: 0.95 kB
../dist/assets/MonacoBuffer-DcKC6iIm.js 2.80 kB │ gzip: 1.41 kB
../dist/assets/arc-BlVBpbND.js 3.43 kB │ gzip: 1.47 kB
../dist/assets/diagram-NH7WQ7WH-B9KDfyQr.js 4.45 kB │ gzip: 1.96 kB
../dist/assets/defaultLocale-DX6XiGOO.js 4.69 kB │ gzip: 2.18 kB
../dist/assets/linear-BujcPsVm.js 5.65 kB │ gzip: 2.30 kB
../dist/assets/diagram-WEI45ONY-SsK-yCca.js 6.28 kB │ gzip: 2.70 kB
../dist/assets/pieDiagram-ENE6RG2P-Jz2sD6vN.js 6.56 kB │ gzip: 2.75 kB
../dist/assets/chunk-RYQCIY6F-BW_W8WHo.js 7.15 kB │ gzip: 2.76 kB
../dist/assets/diagram-OA4YK3LP-BB5d947h.js 8.68 kB │ gzip: 3.87 kB
../dist/assets/dagre-VKFMJZFB-KUrktgNc.js 9.02 kB │ gzip: 3.46 kB
../dist/assets/cynefinDiagram-TSTJHNR4-DhR9Uplp.js 10.30 kB │ gzip: 3.73 kB
../dist/assets/stateDiagram-2N3HPSRC-WJ1VGb0x.js 10.54 kB │ gzip: 3.71 kB
../dist/assets/diagram-FQU43EPY-DnJsgDJM.js 11.02 kB │ gzip: 4.27 kB
../dist/assets/chunk-MOJQB5TN-Ciohy4qm.js 15.76 kB │ gzip: 4.43 kB
../dist/assets/diagram-G47NLZAW-Cn2ECYmQ.js 16.27 kB │ gzip: 5.84 kB
../dist/assets/ishikawaDiagram-FXEZZL3T-Cb9usVAw.js 17.77 kB │ gzip: 6.78 kB
../dist/assets/kanban-definition-HUTT4EX6-DjZ-9DcZ.js 20.90 kB │ gzip: 7.40 kB
../dist/assets/mindmap-definition-LN4V7U3C-DJnLpY4I.js 23.57 kB │ gzip: 8.00 kB
../dist/assets/sankeyDiagram-HTMAVEWB-f_kEluA7.js 23.59 kB │ gzip: 8.68 kB
../dist/assets/journeyDiagram-5HDEW3XC-CL-XvB2t.js 23.77 kB │ gzip: 8.45 kB
../dist/assets/wardleyDiagram-EHGQE667-BxTSAo0a.js 26.30 kB │ gzip: 7.04 kB
../dist/assets/erDiagram-Q63AITRT-C7wFdsBu.js 27.42 kB │ gzip: 9.55 kB
../dist/assets/gitGraphDiagram-IHSO6WYX-7z4h28NB.js 30.14 kB │ gzip: 8.98 kB
../dist/assets/requirementDiagram-TGXJPOKE-e3OV55hP.js 31.41 kB │ gzip: 9.92 kB
../dist/assets/timeline-definition-FHXFAJF6-meAduJgN.js 31.55 kB │ gzip: 10.53 kB
../dist/assets/quadrantDiagram-ABIIQ3AL-Bb6wLm2a.js 34.70 kB │ gzip: 10.23 kB
../dist/assets/chunk-EX3LRPZG-oA0ODuFr.js 38.69 kB │ gzip: 12.71 kB
../dist/assets/xychartDiagram-FW5EYKEG-gCO8yN3N.js 42.14 kB │ gzip: 12.15 kB
../dist/assets/vennDiagram-L72KCM5P-1pMtwo05.js 42.58 kB │ gzip: 15.91 kB
../dist/assets/dnd-xsEmu8eG.js 48.96 kB │ gzip: 16.22 kB
../dist/assets/chunk-V7JOEXUC-Dq7WEM-s.js 49.37 kB │ gzip: 15.81 kB
../dist/assets/util-Cb6vekaB.js 57.07 kB │ gzip: 21.73 kB
../dist/assets/flowDiagram-23GEKE2U-BaUj17kh.js 61.47 kB │ gzip: 19.70 kB
../dist/assets/ganttDiagram-NO4QXBWP-CxlWICLs.js 69.90 kB │ gzip: 23.65 kB
../dist/assets/c4Diagram-LMCZKHZV-DUKzxVDR.js 70.17 kB │ gzip: 19.75 kB
../dist/assets/blockDiagram-677ZJIJ3-9RBsE4wT.js 75.31 kB │ gzip: 21.70 kB
../dist/assets/cose-bilkent-JH36ORCC-68wFxBC2.js 81.92 kB │ gzip: 22.59 kB
../dist/assets/sequenceDiagram-DBY2YBRQ-DQZZZ_Qg.js 117.65 kB │ gzip: 31.38 kB
../dist/assets/swimlanes-5IMT3BWC-BuACkpes.js 120.67 kB │ gzip: 44.15 kB
../dist/assets/architectureDiagram-ZJ3FMSHR-zAU6FNyj.js 151.91 kB │ gzip: 43.24 kB
../dist/assets/react-vendor-CChIRzeM.js 194.27 kB │ gzip: 60.74 kB
../dist/assets/xterm-zg-euOi2.js 335.87 kB │ gzip: 85.14 kB
../dist/assets/markdown-COVWc3k7.js 355.30 kB │ gzip: 109.67 kB
../dist/assets/pdf-CcZYcL52.js 365.12 kB │ gzip: 107.61 kB
../dist/assets/cytoscape.esm-DTSO7Bv0.js 443.72 kB │ gzip: 142.36 kB
../dist/assets/mermaid.core-Ce0RPquC.js 569.20 kB │ gzip: 128.92 kB
../dist/assets/syntax-B-e8QZRG.js 666.56 kB │ gzip: 228.30 kB
../dist/assets/cynefin-VYW2F7L2-B6whK_F0.js 690.83 kB │ gzip: 155.11 kB
../dist/assets/diff-UEgWwdD8.js 1,117.84 kB │ gzip: 342.91 kB
../dist/assets/monaco-D15WGkKO.js 3,930.10 kB │ gzip: 998.63 kB
../dist/assets/index-Dyj_oz8D.js 4,823.18 kB │ gzip: 1,390.31 kB
✓ built in 20.24s
[precompress] 69 files gzipped: 16.74 MB → 4.63 MB (72% saved)
==> Gate 2 OK (Gate 0–2)

  • CI green
  • Manual/prod promote not done by this PR

Docs:

Follow-up to fix-stuck-tool-card-on-dropped-event: add a supersede-proof
terminal fallback that finalizes a permanently-stuck tool card when the
authoritative result is unrecoverable (store/ring eviction -> 404 forever)
but a later assistant turn proves the tool finished. Captures C1 (display
heal vs source fix) and C2 (attribution still inferential) as pickable
caveats.
Clear 17 stale kb-index DOX entries (dox-staleness.json drift): review each
drifted source file against its AGENTS.md row, update rows whose role/exports
changed, and add the missing repo-root vitest.config.ts row.

- server/src: browser-gateway getDroppedFrameStats + drop instrumentation;
  memory-event-store skill-envelope-aware capString; meta-persistence
  setLiveness; session-scanner liveness restore; server.ts line count 2099.
- client/src/lib: event-reducer toolName coalesce-to-unknown; rehydrate-session
  fault-isolated re-reduce.
- docs: root vitest.config.ts row (test.projects, packages/shell).
…heal (BlackBeltTechnology#271)

* feat(fix-stuck-tool-card-superseded-heal): client supersede terminal heal

Finalize a stuck `running` tool card when recovery is exhausted (base
reconcile 404s >= SUPERSEDE_MIN_404) AND the transcript proves the tool
finished (a later assistant `message_start` / `assistantInferenceSeq`).
Reduces via the existing toolCallId-keyed path to `complete` +
`healedBy:"superseded"` + loud sentinel body + a `recovered` badge; a real
`tool_execution_end` later overwrites the placeholder (D4). Runs after the
base HTTP reconcile so a real result always wins.

- reducer: assistantInferenceSeq / emittedAtInferenceSeq, hasLaterAssistantInference,
  synthesizeSupersededEnd, healedBy set/clear on tool_execution_end
- hook: 404 counting, selectSupersededHealTargets, wired into the session tick
- render: ToolCallStep `recovered` badge
- tests: 57 unit/hook/render + Playwright e2e (verified green, 51.5s)

Implements OpenSpec change fix-stuck-tool-card-superseded-heal.
Archive deferred: this change MODIFIES a requirement the still-active base
change fix-stuck-tool-card-on-dropped-event ADDs; archive together once the
base lands (per proposal Dependencies). QA/manual tasks marked done for
post-merge verification.

* fix: apply CodeRabbit feedback for fix-stuck-tool-card-superseded-heal

Doc-consistency (align all artifacts on the inference/message_start proof
boundary): design.md state diagram, proposal.md What-Changes, spec.md
scenario, tasks.md 1.2 (message_start guard) + 3.1 (data.healedBy).
Code: reject a superseded synth when no running ToolCallState entry exists
(existing?.status !== "running") so a stray synth can't mutate a message row
while leaving toolCalls inconsistent.

Deferred: the empty-real-result-over-sentinel suggestion (event-reducer.ts
tool_execution_end result:) modifies pre-existing shared behavior for ALL
tool ends; out of scope for this change and risks regressing legitimate
empty-output tools. Tracked in the PR thread.
Capture a proposal fixing remote-connect on both surfaces, each blocked by
an origin-based CORS gate that top-level navigation escapes but in-app
fetch does not:

- Electron remote attach hangs: loading.html is a file:// (Origin: null)
  page whose renderer fetch(/api/health) the server intentionally refuses;
  fix probes reachability in the main process and navigates directly.
- Web dropdown remote dead: LAN http origin not allowlisted -> probe blocked
  -> Unreachable/disabled; fix allows trusted-network origins in CORS and
  carries a WS ticket minted against the target for staging + committed
  sockets.

Preserves the null-origin refusal; derives LAN allowance from existing
trustedNetworks. Deltas: server-cors, electron-shell, server-selector.
…eltTechnology#270)

- npm: packages/server/package.json dep ^0.80.2 -> ^0.80.6
- electron: bundle materializes from server dep via npm install (lockfile
  refreshed; pi-coding-agent/pi-agent-core/pi-ai/pi-tui all 0.80.6, stale
  top-level pi-tui 0.80.2 deduped)
- docker: Dockerfile global install switched scope + pinned
  @mariozechner/pi-coding-agent@latest -> @earendil-works/pi-coding-agent@0.80.6
  (old scope frozen, never published 0.80.6; pi bin unchanged)

Non-server peer deps stay *; piCompatibility untouched.
Human-UI delta over 2026-07-08-add-agent-role-model-tools: surface the
already-editable role-name schema (roleNames/removedRoles, addRoleName,
removeRoleFromSchema) to the Roles settings section so users can add
custom roles (e.g. @doubt-verifier-1, @doubt-verifier-x) for subagent
spawn.

- Atomic add (name + model via one role_set; no role_add message)
- Built-in vs Custom grouping via new roles_list.builtinRoleNames field
- Custom-only remove via new role_remove WS message -> removeRoleFromSchema
- Shared isValidRoleName helper enforced on client and bridge
- Built-ins permanent (server-side guard)

Proposal + design + tasks + spec deltas (model-selector,
dashboard-roles-ownership). openspec validate: passing.
…tailscale/zerotier) (BlackBeltTechnology#272)

* feat(add-tunnel-providers): provider abstraction + Gateway UI (ngrok/tailscale/zerotier)

Implements OpenSpec change add-tunnel-providers. Extracts a TunnelProvider seam
from the zrok-specific tunnel core (child vs daemon lifecycle), adds ngrok,
tailscale, and zerotier providers behind config.tunnel.provider/mode, multi-
sourced "Accessible at" endpoints, whitelisted server-side enroll, block-event
trusted-network banner, two-QR transport split (pairing QR TLS-only + link QR),
and the "Gateway" settings page + tabbed dialog (UI-only relabel; internals stay
tunnel). Archives + syncs specs (tunnel-provider, qr-device-pairing).

QA/manual tasks deferred to post-merge verification.

* fix: apply CodeRabbit feedback for add-tunnel-providers

- UI: surface config load/save/disconnect errors + catch unhandled rejections
  (GatewayDialog, GatewayPage, GatewayEndpoints); reset stale success on retry
  (GatewaySetupGuide); dismiss block-event banner on wide-subnet trust.
- Security (D3): convert provider execSync string-interpolation to execFileSync
  argv (zrok release/reserve, tailscale + zerotier runner) — closes command
  injection via token/networkId; redact reserved-share tokens in core + zrok logs.
- Correctness: tighten tailscale serveHasHttps to real :443 handlers (no
  false-positive TLS); drop non-existent tailscale:activate enroll step; align
  gateway-endpoints TLS_SCHEME strictness with SECURE_SCHEME.
- Test: zerotier connect-without-networkId edge case.

Deferred (reported): config-api sibling-secret clearing (switch-back UX),
/api/tunnel/endpoints provider-status aggregation, defaultRunner dedup.

* test(tunnel): assert execFileSync argv in releaseShare after injection fix
Adds tests/e2e/reconcile-heal.spec.ts (base change fix-stuck-tool-card-on-
dropped-event, task 5.1): the RECOVERABLE counterpart to superseded-heal
.spec.ts. Drops the tool_execution_end WS frame (server->browser) but leaves
the reconcile route unstubbed, so GET .../tool-result/* hits the real server
(store still holds it) -> 200; asserts the burst flips running->done with the
real echo output and no supersede badge. Verified green against the Docker
harness with PW_CHANNEL=chrome (system Chrome), ~41s heal.

gotoDashboard now arms a one-per-page addLocatorHandler that auto-dismisses the
async first-launch display-preset modal (its backdrop intercepts onboarding
clicks on a wiped/fresh harness; a one-shot check races its render). Makes all
isolated specs robust on a clean container.

Also marks base task 5.1 done and adds the archive-order guard + supersede
cross-reference to the base change proposal/design (must archive with/before
fix-stuck-tool-card-superseded-heal, which MODIFIEs its reconcile requirement).
Telemetry-only proposal to make the in-memory event store's silent shed paths
observable. trimBufferToLimit + evictIfNeeded currently drop events with no
counter, so the deferred Gate B backstop (never-evict latest tool_execution_end,
parked in fix-stuck-tool-card-superseded-heal design.md 'Deferred') cannot be
evidence-gated. Live evidence showed 0 transport drops at both hops but store
trim is unmeasurable by construction; trimBufferToLimit drops oldest tool_*
first, which is exactly the eviction failure mode.

Adds getTrimStats() on the store handle {trimmedEvents:{total,toolExecutionEnd,
bySession},evictedSessions} and surfaces it on /api/health#storeTrim beside the
existing droppedFrames. ADDs a Store-trim instrumentation requirement to
incremental-event-sync. No behavior/protocol/cap change. Validates --strict.
…brella)

Behavioral gate (chat-render-fx.spec.ts, default run):
- Phase 1 reduced-motion contract (tasks 2.7/5.3): live .chat-stream-live
  computed animation-name = chat-stream-glow-pulse/tool-group-sweep, flips to
  none under emulateMedia({reducedMotion:'reduce'}) — real Chrome, not CSS-source.
- task 4.3 auto-scroll-follow bullet: long-transcript overflows + keeps growing
  while the viewport stays pinned to the bottom. (Scroll-up lock, jump-to-turn,
  windowing bounds already gated by chat-transcript-virtualization.spec.ts.)

Advisory perf probe (chat-render-perf.spec.ts, opt-in PW_PERF=1):
- CDP Performance.getMetrics LayoutCount/RecalcStyleCount over a 10s idle window
  on a settled ~120-turn session; idle layouts/s < 30 regression ceiling.
- npm run test:e2e:perf; advisory per design Decision 5 (trace-diff, not budgets).

All 3 green vs the live Docker harness (system Chrome). No app code changed.
…eded-heal

Archive fix-stuck-tool-card-on-dropped-event and
fix-stuck-tool-card-superseded-heal (base-first, dependency order) and
sync their deltas into specs/incremental-event-sync:
  + Stale running-tool reconcile
  + Drop-site delivery instrumentation
  + Superseded terminal heal (modifies Stale running-tool reconcile)

Fix pre-existing main-spec corruption: line-5 header was the delta-only
'## ADDED Requirements', which hid every requirement from openspec
tooling and blocked all syncs; corrected to '## Requirements'.
…nt-event-serialization

Add tests/e2e/oversized-event-liveness.spec.ts automating the change's open
manual task (5.3): drive [[faux:oversized-turn]] (bash ~90KB output -> oversized
event through the real ingest->persist->broadcast JSON.stringify path that used
to OOM-crash the server), assert /api/health stays 200 and a follow-up turn
round-trips in the same session (server alive + responsive).

- qa/fixtures/faux-scenarios.ts: add 'oversized-turn' scenario + OVERSIZED_TURN_MARKER
- sync delta spec (per-event size ceiling + depth-limit collapse) into
  openspec/specs/in-memory-event-buffer/spec.md
- archive change -> openspec/changes/archive/2026-07-12-bound-subagent-event-serialization
- doc rows: tests/e2e/AGENTS.md, qa/AGENTS.md
…nder-cpu-umbrella (tasks 2.8/4.4)

CDP perf probe vs live Docker harness (system Chrome), settled ~120-turn
session: idle layouts/s=1.0, recalcs/s=3.7 over 10s. Vs ~85/s baseline and
under the <5/s target. Consistent with idle-busy <5% (4.4).
…ability specs

Sync (new main specs): chat-event-render-batching, chat-idle-render-cost,
chat-transcript-virtualization (all ADDED capabilities, validated).
Archive → openspec/changes/archive/2026-07-12-reduce-chat-render-cpu-umbrella.
7 manual/verification tasks (1.4, 2.8, 4.3, 4.4, 4.5, 5.1, 5.3) deferred to ship.
Completes the proposal artifacts (proposal.md + specs/content-copy delta
landed in a2c1359). Root-cause: TableWrapper computes CopyButton text
eagerly at render time via a ref read, but React assigns refs at commit;
under React.memo the empty text is frozen so table copy writes "".
Fix direction: CopyButton takes getText: () => string, resolved at click.
…eltTechnology#267)

Capture Layer B of issue BlackBeltTechnology#267: the virtualized chat transcript can leave
image-bearing rows collapsed because ImageAttachments has no onLoad re-measure
and no reserved dimensions, so a row measured before base64 decode stays at its
pre-decode estimate and overlaps its neighbour. Server-side Layer A (per-event
size ceiling) is already fixed on develop by 05239e9; this scopes the client
re-measure hardening only.
…rash-undefined-toolname

Automates the change's manual QA (tasks 5.1 + 5.2) as Playwright e2e:
reducer-poisoned-cache-heal.spec.ts poisons the live IndexedDB
pi-dashboard-replay-cache with an absent-toolName tool_execution_start,
reloads a Dockerized session, and asserts the shell survives (no root
unmount, no toLowerCase pageerror) plus a clean lastSeq:0 full replay
after clearing the cache. Passed PW_CHANNEL=chrome.

Archive the completed change and sync its two ADDED requirements
(event-reducer tolerance, session-replay-persistence fault-isolation)
into the main specs.
…uly-dead -latest aliases

The OAUTH_INCOMPATIBLE table dropped 3 pre-4.x -latest aliases absent from
the live registry catalog, but claude-3-5-haiku-latest IS shipped by the
pi-ai copy the proxy resolves via the tool registry (differs from top-level
node_modules) and is OAuth-unreachable — restore it so /v1/models does not
leak it over Claude Pro/Max OAuth. Verified live via /api/model-proxy/diagnostics.
Spec: fill Purpose + pin the live-catalog -latest denial + maintenance note.
Deterministic tree-sitter code-symbol indexing into the KB Tier-1
nodes/edges graph, retrieved via kb_search + a new find_symbol nav
surface. WASM binding (web-tree-sitter), data-driven language registry
(Core-16 offline + integrity-gated lazy tail), node-as-concept name
model, per-root scoping, pinned perf budget. SCIP precise-xref and
live-LSP enrichment deferred behind the same seam.
…8.2, 9.3)

Grow chat-transcript-virtualization.spec.ts from the 6-test skeleton to 11
passing tests against the docker harness (PW_CHANNEL=chrome), covering the
scroll/streaming/windowing behaviors jsdom cannot reach:

- 9.3: activate the skeleton — timeout calibration (120-turn fixture fires 120
  real bash calls) + expect.poll de-flake; add long-transcript-nav (40-turn)
  faux scenario so turn 0's stat survives MAX_TURN_STATS=50 and the turn-bar
  affordance renders for the off-screen scrollToTurn jump.
- 6.1: resume-within-50px (manual re-arm, no click) + user-scroll-up-mid-replay-wins.
- 4.2: streaming growth keeps the bottom pinned while following.
- 7.2: session-switch scroll persistence restores the anchored row, not bottom.
- 8.2: collapsing an above-viewport tool group does not yank the viewport
  (TanStack measure-driven scroll adjustment + overflowAnchor:none).

Harden tail-wait budgets (waitForTail 240s, test.setTimeout 300s) for real-bash
streaming under load. tsc + biome clean.
New OpenSpec change: keep an active transcript text selection alive across
virtualized-list churn (streaming updates, new cards, auto-scroll).

Design (post doubt-review, incl. deepseek-v4-pro cross-model):
- D1: selectionchange hook; range-intersects-container; proactive anchor-span
  capture into a ref (DOM Range endpoints move synchronously + irreversibly on
  unmount, so retention must be proactive, not reactive).
- D2: suspend auto-scroll bottom-pin while selecting; dep-array + lastScrollHeightRef
  fixes so follow resumes on collapse.
- D3: rangeExtractor unions the tracked span into the mounted range (not bolt-on
  rows); getTotalSize allowed to change; device-aware span ceiling (desktop ~100
  / mobile ~40); past-cap actively removeAllRanges (passive collapse silently
  truncates).
- D4: streaming tail no-worse-than-baseline; node-stable render deferred.
- D5: copy fidelity via copy-event interception deferred; scoped honestly
  (cloneContents for partial rows; slice(0,1000) needs per-renderer cooperation).

Artifacts: proposal, design, specs (new chat-selection-preservation + modified
chat-scroll-lock, chat-transcript-virtualization), tasks. openspec validate passes.
…ange

Grounded diagnosis of 'Gemini 2.5 Pro unusable with subagents': not an
access/auth/tool-schema/model-proxy problem (all refuted live). Root cause
captured via dashboard-spawn D3 repro: Gemini returns a thinking-only turn
(all output tokens reasoning, 0 visible text, no tool call, stopReason=stop,
no error); pi/dashboard idles it silently -> 'no response, nothing reported'.

Artifacts (4/4, validated):
- proposal.md: primary defect = empty-actionable-turn guard; error-surfacing
  complementary; tool-schema sanitizer dropped (parametersJsonSchema accepts
  anyOf/const/format).
- design.md: D3 capture, continue-or-surface guard, upstream adapter-vs-Gemini
  open question.
- specs/empty-actionable-turn-guard, specs/spawned-session-error-surfacing.
- tasks.md: TDD, grounded in bridge.ts stopReason branch + drainFollowupQueue.
Model proxy cannot resolve slash-bearing model ids: split("/", 2)
truncates at the first slash, so openrouter/anthropic/claude-3.5-sonnet
404s despite being advertised by /v1/models. Also adds a preferred-model
mechanism (none exists beyond a single defaultModel fallback).

Change captures:
- fix: first-slash parse via shared parseModelId helper + round-trip invariant
- deterministic source precedence (dedup by fqid; built-in > custom > models.json)
- new modelProxy.preferredModels (ordered availability fallback) + modelAliases
- Settings UI reusing ModelSelector for model picking (+ mockup)

Artifacts only (proposal/design/spec-delta/tasks/mockup); no impl.
- Fold the automatable slice of the task 10.1 perf gate into the windowing
  test: a 120-turn session measures 179 transcript DOM nodes vs the 46,918
  Step-A baseline (< 3000 ceiling), proving nodes track the viewport working
  set, not session length (design Decision 7). GC/heap/listener/idle-busy
  remain a manual DevTools trace.
- De-flake "streaming tail stays mounted": replace the single-snapshot
  live-count read with a retrying toBeAttached — the .chat-stream-live class
  briefly drops at 120-turn boundaries; the invariant is that scrolling up
  never windows the live tail away. Verified 3/3 under --repeat-each.

Full suite 11 passed via PW_CHANNEL=chrome. tsc + biome clean.
…pecs

Move the completed change to archive/2026-07-12-virtualize-chat-transcript-tanstack
and sync the chat-transcript-virtualization capability spec to the Step-B
(windowing) requirements: mounted-node/listener bound, scroll-lock preserved,
off-screen scrollToTurn top-align, streaming-tail pinning, per-session restore.

Remaining perf-trace (GC/heap/listeners/idle) + manual smoke tasks are deferred
(to be verified later); all browser-testable behavior is covered by the e2e suite.
…ckBeltTechnology#273)

* docs(openspec): fold doubt-review findings into scroll-to-top design

Cross-model (deepseek-v4-pro) adversarial review + source verification:
- scrollToIndex(0) is bounded (maxAttempts=10, not infinite); re-issue on
  async img.onload so a late image-load remeasure can't bump off index 0
- guard the scroll-to-top re-arm race with an ascendingRef latch so
  handleScroll can't flip stickToBottomRef back on mid-flight
- image reserve is per-renderer-kind (300px user / 512px toolResult),
  not one global constant
- Decision 1 constants gated on the required Playwright e2e convergence
  gate; jsdom demoted to logic-only (shim can't reproduce the timing race)
- mechanism note: stale-offset story inferred, not version-pinned

* fix(chat): content-aware row estimate + scroll-to-top affordance

Scrolling up a virtualized transcript never converged on index 0: the
largest rows (pasted image, 24k-char tool result) sit near the top and
the static per-role estimate under-shot them 10-50x, so on mount
getTotalSize() jumped and the top receded.

- chat-virtual-rows.ts: estimateVirtualRowSize is now content-aware
  (base + ceil(textChars/80)*20, clamped 8000px, + per-kind image reserve
  300 user / 512 toolResult); computeRowTextChars aggregates rendered text
  once per displayRows rebuild so estimateSize stays O(1). Shrinking the
  estimate delta shrinks TanStack's built-in above-viewport correction
  below perception.
- ChatView.tsx: precompute rowTextChars in the displayRows useMemo; add a
  scroll-to-top button (ascendingRef latch). scrollToIndex(0) is bounded
  (maxAttempts=10), so onChange re-issues it on measurement growth (covers
  async image-load remeasure); handleScroll ascending branch prevents the
  bottom-pin re-arm race; cancelDescent clears the latch on user input.
  No manual scrollTop+=delta (would double-move); overflowAnchor:none kept.
- Tests: unit (monotonic estimate, per-kind image reserve, aggregate
  textChars) + scroll-to-top logic guards (button visibility, re-arm race).
  Browser-timing convergence is Playwright-gated (jsdom shim can't
  reproduce it).

See change: fix-chat-scroll-to-top-estimate-drift

* test(e2e): scroll-to-top convergence gate + top-heavy faux fixture

The required browser-timing gate jsdom cannot provide (doubt-review #4/#5):

- qa/fixtures/faux-scenarios.ts: buildScrollTopHeavy() + scroll-top-heavy
  scenario — biggest rows (16k thinking, 9k text, 24k bash toolResult,
  inline image) near the TOP, then ~40 small turns. Exported
  SCROLL_TOP_HEAVY_TAIL.
- tests/e2e/scroll-to-top.spec.ts: 2 tests via [[faux:scroll-top-heavy]] —
  (1) scroll-to-top lands scrollTop<=2 and STAYS after the near-top image
  loads async (the bounded-scrollToIndex re-issue path); (2) incremental
  scroll-up converges on scrollTop<=2 + [data-index=0] visible (top does
  not recede). test.slow() (180s) for the ~40 real bash round-trips.
- helpers: scrollToTop testid.

Verified 2/2 green against the Docker harness. Test 2 fails if the top
recedes (the pre-fix bug); test 1 fails without the new button — the gate
is not vacuous.

See change: fix-chat-scroll-to-top-estimate-drift

* chore(openspec): archive fix-chat-scroll-to-top-estimate-drift

Sync delta specs into openspec/specs/chat-transcript-virtualization.
QA/manual tasks were validated via the Playwright e2e gate + existing
virtualization suite.

* test: add clamp-boundary + empty-payload edge cases (CodeRabbit)

Covers exact text-reserve clamp boundary + saturation above it, and
0-length message/group/burst payloads for the O(1) sizing contract.
…G via electron-builder (BlackBeltTechnology#277)

Rewire build_native() to mirror CI (_electron-build.yml): darwin uses
electron-forge package -> electron-builder --mac dmg --prepackaged (with
CSC_IDENTITY_AUTO_DISCOVERY=false); linux uses forge make -> electron-builder
--linux AppImage. Fixes the hard failure where npm run electron:build could not
produce a macOS DMG. Removes obsolete macos-alias plumbing (ensure-macos-alias
postinstall, maker-dmg devDep, Doctor row + test) since electron-builder's DMG
target uses hdiutil, not macos-alias.

Adds tests/e2e-electron/dmg-build-launch.electron.spec.ts (Playwright _electron)
that mounts the built DMG and asserts the app launches + dashboard opens.

Implements OpenSpec change fix-local-electron-dmg-build. Archives + syncs specs.
QA/manual tasks (arm64 --mac-both) deferred to post-merge CI verification.
…c decode (BlackBeltTechnology#267) (BlackBeltTechnology#275)

* fix(fix-virtualized-image-row-measurement): re-measure image rows on decode

Image-bearing user rows in the virtualized transcript could collapse and
overlap their neighbour (issue BlackBeltTechnology#267): a base64 data-URL decodes async, so a
row is first measured near-zero and, when the decode-driven ResizeObserver
re-measure is missed (ChatView reuse across session switch), stays cached at
the collapsed height.

- ImageAttachments: onImageLoad re-measures the owning virtual row via
  virtualizer.measureElement, coalesced to one measure per row per frame.
- Reserve a bounded loading box on the <img> so the pre-decode measurement
  is not near-zero (dropped once decoded).
- estimateVirtualRowSize: taller pre-mount estimate for image-bearing user
  rows to bound first-paint drift (measurement stays authoritative).

Archives + syncs specs. QA/manual tasks (5.1/5.2) deferred to post-merge.

* merge develop: drop superseded estimate edit, keep onLoad re-measure

develop PR BlackBeltTechnology#273 rewrote estimateVirtualRowSize into a content-aware form
(estimateVirtualRowSize(item, textChars)) that already reserves
IMAGE_RESERVE_USER=300 for image rows. Take develop's chat-virtual-rows.ts
wholesale — our interim task-3.1 estimate bump is superseded and its textual
auto-merge broke the new baseRowSize(role) scope (TS2304 msg).

The load-bearing fix (ImageAttachments onLoad -> coalesced row re-measure +
reserved loading box in ChatView.tsx) is independent of the estimate rewrite
and preserved. tsc clean; full client suite 3195 green.

* fix: apply CodeRabbit feedback for fix-virtualized-image-row-measurement

- Add onError-path test: a failed image decode must not schedule a re-measure.
- Archived design.md: convert the ASCII decode-sequence to a Mermaid
  sequenceDiagram (repo Diagram Style rule).
…time (BlackBeltTechnology#276)

CopyButton now takes a getText callback resolved at click time instead of an
eager text string bound at render, so ref-derived payloads (tables, code blocks,
message body) copy real content even under React.memo single-render. Migrates
all call sites. Adds click-level unit tests + a Playwright e2e (copy-surfaces
faux scenario, real clipboard) automating the former manual QA.

Implements OpenSpec change fix-table-copy-empty-clipboard. Archives + syncs specs.
Repairs pre-existing structural corruption in openspec/specs/content-copy/spec.md
(leaked delta header + missing Purpose section) to allow the sync.
robertcsakany and others added 27 commits July 16, 2026 10:30
…sks + test-plan

Client-render out-of-cwd session diffs from captured Write/Edit payload; lazy
full-fidelity fetch from pi session JSONL by (sessionId,toolCallId). No server
read of the out-of-cwd path. Doubt-review (single+cross-model) killed an earlier
server-side realpath eligibility gate as a remote arbitrary-read hole; this is
the safe pivot. 16 automated scenarios folded to tasks (test-plan.md manifest).
Capture a change proposal to fix low kb_* tool adoption. Evidence from the
last 20 main-repo sessions: 234 grep/rg vs 24 kb_search, 137 greps into
source (mostly symbol lookups), kb_neighbors unused. Root cause is framing:
the prose Docs-First Gate is ignored while the mechanical Discipline-Skills
table is followed.

Proposal converts the gate into a tool-substitution table (reflex -> exact
kb command), names the symbol-lookup case, adds kb_neighbors/kb_get as the
chain-through, and fixes the project-init coding template that ships
'Read the file first' to new projects.

New capability spec kb-read-discipline (4 requirements, 8 scenarios);
validates --strict.
Implement steer-agents-to-kb-tools. Convert the prose Docs-First Gate into a
mechanical reflex -> exact-kb-command table that names the symbol-lookup case
(kb_search --doc-type agents) and adds kb_neighbors/kb_get as the chain-through.
Drop the STOP/violation scare framing; keep the explicit fall-through (grep on
tree miss -> add the missing row).

- AGENTS.md: gate rewritten as table; fix 'read it before answering' steer.
- project-init dox-doctrine.md: same table seeded into new projects
  (dox:read:kb) + degraded chain-walk variant (dox:read:manual).
- coding AGENTS.md.tmpl: 'read the file first' -> consult doc tree first.

12/14 tasks; 5.x manual scaffold checks deferred. openspec validate --strict ok.
…tion table

Cover tasks 5.1/5.2 deterministically. project-init seeding runs through the
composer seed-doctrine.ts buildDoctrineBlock({kbWired}); assert its output
directly rather than via docker/Playwright (the skill's browser path drives a
model, not this static content).

- kb-wired variant: symbol-lookup row (kb_search --doc-type agents),
  kb_neighbors/kb_get chain-through, explicit fall-through.
- manual variant: degraded chain-walk table, explicit fall-through, no kb tools.

9/9 pass in project-init-seed-doctrine.test.ts. tasks.md 14/14, validate ok.
…pline spec

Move the completed change to archive/2026-07-16-steer-agents-to-kb-tools and
create openspec/specs/kb-read-discipline/spec.md (4 requirements, 8 scenarios)
from the delta. Validates --strict.
Create openspec/specs/kb-read-discipline/spec.md from the archived
steer-agents-to-kb-tools delta (4 requirements, 8 scenarios). validate --strict ok.
kb_neighbors/kb_get open an empty store via getKb but never populate it, so on a
never-indexed cwd they return false-empty while kb_search self-populates via its
freshness reindexNow. Proposal: ensurePopulated(state,cwd) that reindexes only
when counts().chunks===0, called in both handlers (guarded). Delta adds 3
scenarios to markdown-knowledge-base pull-retrieval. validate --strict ok.
kb_neighbors/kb_get called getKb() (opens an EMPTY store) but never populated
it, so on a never-indexed cwd they returned false-empty while kb_search
self-populates via its freshness reindexNow. Add ensurePopulated(state,cwd):
build the index once only when store.counts().chunks===0 (warm index = COUNT-only
no-op; removed cwd = safe no-op). Wire into both handlers, guarded try/catch so a
failed walk falls back to the existing index (mirrors kb_search).

Tests: cold populate, warm no-walk (edit-without-reindex stays unsearchable),
removed-cwd degrade. 14/14 pass, tsc clean, biome clean (also removed 3
pre-existing unused imports in the touched test file).

Implements fix-kb-neighbors-get-cold-start.
Move the completed change to archive/2026-07-16-fix-kb-neighbors-get-cold-start
and fold its delta into openspec/specs/markdown-knowledge-base: the pull-retrieval
requirement gains the cold-start self-population rule + 3 scenarios (cold
self-populate, warm-not-re-walked, failed-degrade). validate --strict ok.
… research/

Research pass over pingdotgg/t3code -> pi-dashboard feature-adaptation map,
plus three explore-mode OpenSpec proposals derived from it.

- docs/research/t3code-feature-adaptation.md: comprehensive feature comparison
  (6 candidates, our-edge, parity, not-adaptable), effort/value shortlist.
- add-supervised-tool-approval: per-session supervised mode gating risky tool
  calls via pi's blockable tool_call hook + ctx.ui.confirm (PromptBus);
  approval-only scope (sandboxing delegated to the container path).
- add-workspace-checkpointing: per-turn git working-tree snapshots (private
  shadow ref via temp index) + non-destructive revert (pre-revert safety snap).
- add-ai-pr-description: server-side model-proxy endpoint drafting PR title/body
  from commit log + diffstat; prefill-not-autosubmit, graceful degradation.
- Consolidate: move research/ into docs/research/; update 3 LoRA proposal refs;
  index all research docs in docs/AGENTS.md.

All changes pass openspec validate --strict. Explore-mode artifacts only;
no implementation.
Full-system STRIDE sweep (security-hardening skill applied post-hoc) across
all packages, captured as OpenSpec artifacts. No product code changes.

security-boundary-audit/ — findings register (7 clusters A–G), 5 verification
  checks (V1–V5), and 5 line-cited verified deep-dives (VD1–VD5). Verdict: core
  is well-secured (path-containment, CSPRNG+timingSafe secrets, scoped WS
  tickets, argv-everywhere, sanitized EML/DOCX, hardened Electron webPreferences)
  but the auth guard is applied non-uniformly.

Remediation planned as 7 validated changes (openspec validate --strict):
  Highs:
   - add-universal-network-guard         VD2 automation RCE + provider-auth
   - sanitize-untrusted-rendered-content VD3 markdown/asciidoc/mermaid XSS
   - fix-git-checkout-command-injection  VD1 (+ B6 Windows escape)
   - harden-electron-renderer-boundary   VD5 preload + VD4 openExternal (+ B10)
  Mediums (batched by subsystem):
   - harden-server-capability-bounds     B7 PTY cap · B8 browse · B9 recovery
   - harden-untrusted-content-ingestion  B11 mounts · B12 SSRF · B13 zip-slip · B26 xlsx
   - harden-trust-and-credential-boundaries B4 bearer · B5 CSRF · B14 loopback · B15 events · B25 config 0600

Still open (not drafted): S2 bridge WS auth (V2-rescoped LAN-only), Low tail B16–B24.
…per-plugin handlers (BlackBeltTechnology#343)

* plan(bundle-python-runtime): doubt-reviewed proposal+design+specs+tasks

Planning artifacts for bundling a pip-capable Python env (uv + python-build-standalone,
Tier 2) into the Electron bundle, exposed on PATH for agent bash/tool calls all platforms.

Doubt-review (single + cross-model GLM) corrected 13 findings, notably:
- D5 rewritten: sync-inject + bare-interpreter fallback (async materialize can't be
  awaited in the synchronous spawn-env seam)
- UV_* scoped to materialize subprocess; SSL_CERT_FILE -> interpreter CA (pip certs)
- Windows rebuild-vs-live-install via versioned overlay dirs
- offline base rebuild = online/warm-cache only (Tier 2 non-goal)

scenario-design gate resolved 2 spec gaps (offline pip = pip's own exit/stderr passthrough;
small hash-pinned starter baseline). 24 automated scenarios folded to tasks (test-plan.md
manifest), 0 manual-only. #9 uv file:// mirror layout gated behind a blocking spike.

* docs(security): plan add-universal-network-guard (doubt-review + scenario-design)

plan-proposal on develop. Added design.md + test-plan.md; revised proposal/
specs/tasks after a 2-cycle doubt-driven-review (single-model + cross-model
@propose-review-1 GLM, @propose-review-2 DeepSeek).

Design pivoted: the first draft's "deny-all + enumerate a public allowlist" was
shown unimplementable (hashed static assets rooted at /, SPA history-fallback via
setNotFoundHandler) and would brick the app shell with auth off over a tunnel.
Revised to a namespace-scoped guard: jurisdiction = /api,/v1,/editor,/live,
deny-by-default within, everything else (static/SPA/auth/manifest) untouched.

Reviewer-found + reconciled:
 - /v1 proxy brick: proxy gate must set request.isAuthenticated; guard registered
   last (after bearer/oauth/proxy hooks).
 - /auth/status + SPA shell + deep-link refresh must stay public (now out of
   jurisdiction by construction).
 - model-proxy SECOND port (server.ts:1875) is a separate Fastify instance with
   only the proxy gate -> loopback-bind invariant, test-asserted.
 - proposal/design contradiction (ws-ticket mint) + phantom service worker fixed.

scenario-design: test-plan.md manifest, 18 automated (17 L1 + 1 L3) + 1
manual-only, folded into tasks.md as vanilla checkboxes with exemplar+Triple+
manifest ref. openspec validate --strict passes; all 4 artifacts done.

* plan(openspec-artifact-dialog-desktop): proposal+design+specs+tasks+test-plan

Desktop-viewport modal for P/D/S/T artifact badges; mobile keeps full-page route. Planned via plan-proposal: 2 doubt-review cycles (single+cross-model), scenario-design manifest (20 automated + 1 manual), folded to tasks.

* plan(cloud-sync): add-cloud-sync-connector planning artifacts

Bidirectional local↔cloud file sync connector (Drive/Dropbox/OneDrive).
Baseline-ledger engine, held-conflict resolution, provider adapter seam.

- proposal.md: scope, safety posture (lossless+faithful), pinned decisions
- design.md: impossibility triangle, 8 decisions, 3 engine invariants,
  pressure-tested Provider interface (7 verbs)
- specs/cloud-file-sync/spec.md: 42-scenario completeness matrix (checkable
  total-coverage backbone), N=3 hot-edit defer, K=10 tombstone window
- test-plan.md: manifest, 45 automated (L1/L2) + 2 manual-only, functional-only
- tasks.md: 68 tasks, folded automated scenarios (parser-safe vanilla checkboxes)

Doubt-reviewed 3 cycles (single + cross-model glm-5.2): fixed 4 data-loss paths
(conflict→push re-overwrite, pull pre-write overwrite, native-doc push,
resolve pull-inversion).

* docs(openspec): shelve close-initialize-handoff-via-bus (never implemented)

Doubt-review (single-model + GLM-5.2 cross-model) found the design incoherent
with the shipped server, defeating its own no-click goal via the TOFU trust
resolution, and resting on a wrong value premise:

- No bus primitive awaits worktree_init_done (keyed by requestId+cwd, not a
  session status); plain spawn({cwd}) doesn't fire the hook and re-launches
  project-init. until()/await()/read cannot observe completion.
- TOFU trust gate: auto-confirming a just-written hook bypasses the human gate
  and poisons worktree-init-trust.json, so the next manual click skips TOFU.
- Premise wrong: project-init's worktreeInit hook is ~npm ci; kb-index is the
  kb-extension's separate hook; openspec-init already runs inline at Step 6.
- connect() has no timeout arg; connect-failed is never thrown -> bounded
  degradation is unimplementable as designed.

Archived pre-implementation with NOTES.md capturing findings + revival
prerequisites so it is not re-proposed.

* plan(fix-plugin-action-fanout-and-handlers): design + tasks artifacts

* feat(fix-plugin-action-fanout-and-handlers): pluginId fan-out + real per-plugin handlers

Make plugin_action a real universal seam. The gateway now fans out plugin_action
by pluginId (Map<pluginId,Handler> + registerPluginActionHandler) instead of a
last-writer-wins Map<type,Handler>, so flows/kb/automation/goal coexist regardless
of load order. Unknown pluginId returns a structured plugin_action_error to the
sender (never a silent drop). flows/kb/automation gain production plugin_action
handlers dispatching to their existing in-process cores. bus-client
KNOWN_PLUGIN_HANDLERS extended to goal/flows/kb/automation.

Implements OpenSpec change fix-plugin-action-fanout-and-handlers. Archives + syncs specs.

* fix: apply CodeRabbit feedback for fix-plugin-action-fanout-and-handlers

Validate plugin_action inputs: reject non-object/array flow.run `inputs`
(flows) and require a plain-object `config.set` patch (kb, no fallback to the
control payload). Hoist kb handler-test mocks into vi.hoisted to avoid a TDZ
hit in the vi.mock factory.
…ion resolution (Phase 1) (BlackBeltTechnology#344)

* plan(bundle-python-runtime): doubt-reviewed proposal+design+specs+tasks

Planning artifacts for bundling a pip-capable Python env (uv + python-build-standalone,
Tier 2) into the Electron bundle, exposed on PATH for agent bash/tool calls all platforms.

Doubt-review (single + cross-model GLM) corrected 13 findings, notably:
- D5 rewritten: sync-inject + bare-interpreter fallback (async materialize can't be
  awaited in the synchronous spawn-env seam)
- UV_* scoped to materialize subprocess; SSL_CERT_FILE -> interpreter CA (pip certs)
- Windows rebuild-vs-live-install via versioned overlay dirs
- offline base rebuild = online/warm-cache only (Tier 2 non-goal)

scenario-design gate resolved 2 spec gaps (offline pip = pip's own exit/stderr passthrough;
small hash-pinned starter baseline). 24 automated scenarios folded to tasks (test-plan.md
manifest), 0 manual-only. #9 uv file:// mirror layout gated behind a blocking spike.

* docs(security): plan add-universal-network-guard (doubt-review + scenario-design)

plan-proposal on develop. Added design.md + test-plan.md; revised proposal/
specs/tasks after a 2-cycle doubt-driven-review (single-model + cross-model
@propose-review-1 GLM, @propose-review-2 DeepSeek).

Design pivoted: the first draft's "deny-all + enumerate a public allowlist" was
shown unimplementable (hashed static assets rooted at /, SPA history-fallback via
setNotFoundHandler) and would brick the app shell with auth off over a tunnel.
Revised to a namespace-scoped guard: jurisdiction = /api,/v1,/editor,/live,
deny-by-default within, everything else (static/SPA/auth/manifest) untouched.

Reviewer-found + reconciled:
 - /v1 proxy brick: proxy gate must set request.isAuthenticated; guard registered
   last (after bearer/oauth/proxy hooks).
 - /auth/status + SPA shell + deep-link refresh must stay public (now out of
   jurisdiction by construction).
 - model-proxy SECOND port (server.ts:1875) is a separate Fastify instance with
   only the proxy gate -> loopback-bind invariant, test-asserted.
 - proposal/design contradiction (ws-ticket mint) + phantom service worker fixed.

scenario-design: test-plan.md manifest, 18 automated (17 L1 + 1 L3) + 1
manual-only, folded into tasks.md as vanilla checkboxes with exemplar+Triple+
manifest ref. openspec validate --strict passes; all 4 artifacts done.

* plan(openspec-artifact-dialog-desktop): proposal+design+specs+tasks+test-plan

Desktop-viewport modal for P/D/S/T artifact badges; mobile keeps full-page route. Planned via plan-proposal: 2 doubt-review cycles (single+cross-model), scenario-design manifest (20 automated + 1 manual), folded to tasks.

* plan(cloud-sync): add-cloud-sync-connector planning artifacts

Bidirectional local↔cloud file sync connector (Drive/Dropbox/OneDrive).
Baseline-ledger engine, held-conflict resolution, provider adapter seam.

- proposal.md: scope, safety posture (lossless+faithful), pinned decisions
- design.md: impossibility triangle, 8 decisions, 3 engine invariants,
  pressure-tested Provider interface (7 verbs)
- specs/cloud-file-sync/spec.md: 42-scenario completeness matrix (checkable
  total-coverage backbone), N=3 hot-edit defer, K=10 tombstone window
- test-plan.md: manifest, 45 automated (L1/L2) + 2 manual-only, functional-only
- tasks.md: 68 tasks, folded automated scenarios (parser-safe vanilla checkboxes)

Doubt-reviewed 3 cycles (single + cross-model glm-5.2): fixed 4 data-loss paths
(conflict→push re-overwrite, pull pre-write overwrite, native-doc push,
resolve pull-inversion).

* docs(openspec): shelve close-initialize-handoff-via-bus (never implemented)

Doubt-review (single-model + GLM-5.2 cross-model) found the design incoherent
with the shipped server, defeating its own no-click goal via the TOFU trust
resolution, and resting on a wrong value premise:

- No bus primitive awaits worktree_init_done (keyed by requestId+cwd, not a
  session status); plain spawn({cwd}) doesn't fire the hook and re-launches
  project-init. until()/await()/read cannot observe completion.
- TOFU trust gate: auto-confirming a just-written hook bypasses the human gate
  and poisons worktree-init-trust.json, so the next manual click skips TOFU.
- Premise wrong: project-init's worktreeInit hook is ~npm ci; kb-index is the
  kb-extension's separate hook; openspec-init already runs inline at Step 6.
- connect() has no timeout arg; connect-failed is never thrown -> bounded
  degradation is unimplementable as designed.

Archived pre-implementation with NOTES.md capturing findings + revival
prerequisites so it is not re-proposed.

* feat(server-side-file-mention-resolution): lazy server-side file-mention resolution (Phase 1)

Client detects mentions synchronously; the server resolves them against the
real filesystem lazily on click. Fixes broken/tilde tool-output links.

- Add resolveFileMention() + POST /api/file/resolve-mention (untrusted-cwd
  gate before resolve; containment before stat; fixed ~/.pi allowlist anchor).
- Open/preview routes (/api/file, /api/file/raw, /api/file/render) honor the
  ~/.pi anchor so a resolved home path previews without a 403.
- Client: one ~/ tokenizer branch; FileLink resolves on click and opens the
  server-resolved path directly (no double re-root), inline not-found on null,
  client-side fallback on transport failure; split-open routes through resolve.

Archives + syncs specs. Manual latency check (test-plan #M1) deferred to
post-merge verification.
…lackBeltTechnology#345)

* plan(bundle-python-runtime): doubt-reviewed proposal+design+specs+tasks

Planning artifacts for bundling a pip-capable Python env (uv + python-build-standalone,
Tier 2) into the Electron bundle, exposed on PATH for agent bash/tool calls all platforms.

Doubt-review (single + cross-model GLM) corrected 13 findings, notably:
- D5 rewritten: sync-inject + bare-interpreter fallback (async materialize can't be
  awaited in the synchronous spawn-env seam)
- UV_* scoped to materialize subprocess; SSL_CERT_FILE -> interpreter CA (pip certs)
- Windows rebuild-vs-live-install via versioned overlay dirs
- offline base rebuild = online/warm-cache only (Tier 2 non-goal)

scenario-design gate resolved 2 spec gaps (offline pip = pip's own exit/stderr passthrough;
small hash-pinned starter baseline). 24 automated scenarios folded to tasks (test-plan.md
manifest), 0 manual-only. #9 uv file:// mirror layout gated behind a blocking spike.

* docs(security): plan add-universal-network-guard (doubt-review + scenario-design)

plan-proposal on develop. Added design.md + test-plan.md; revised proposal/
specs/tasks after a 2-cycle doubt-driven-review (single-model + cross-model
@propose-review-1 GLM, @propose-review-2 DeepSeek).

Design pivoted: the first draft's "deny-all + enumerate a public allowlist" was
shown unimplementable (hashed static assets rooted at /, SPA history-fallback via
setNotFoundHandler) and would brick the app shell with auth off over a tunnel.
Revised to a namespace-scoped guard: jurisdiction = /api,/v1,/editor,/live,
deny-by-default within, everything else (static/SPA/auth/manifest) untouched.

Reviewer-found + reconciled:
 - /v1 proxy brick: proxy gate must set request.isAuthenticated; guard registered
   last (after bearer/oauth/proxy hooks).
 - /auth/status + SPA shell + deep-link refresh must stay public (now out of
   jurisdiction by construction).
 - model-proxy SECOND port (server.ts:1875) is a separate Fastify instance with
   only the proxy gate -> loopback-bind invariant, test-asserted.
 - proposal/design contradiction (ws-ticket mint) + phantom service worker fixed.

scenario-design: test-plan.md manifest, 18 automated (17 L1 + 1 L3) + 1
manual-only, folded into tasks.md as vanilla checkboxes with exemplar+Triple+
manifest ref. openspec validate --strict passes; all 4 artifacts done.

* plan(openspec-artifact-dialog-desktop): proposal+design+specs+tasks+test-plan

Desktop-viewport modal for P/D/S/T artifact badges; mobile keeps full-page route. Planned via plan-proposal: 2 doubt-review cycles (single+cross-model), scenario-design manifest (20 automated + 1 manual), folded to tasks.

* plan(cloud-sync): add-cloud-sync-connector planning artifacts

Bidirectional local↔cloud file sync connector (Drive/Dropbox/OneDrive).
Baseline-ledger engine, held-conflict resolution, provider adapter seam.

- proposal.md: scope, safety posture (lossless+faithful), pinned decisions
- design.md: impossibility triangle, 8 decisions, 3 engine invariants,
  pressure-tested Provider interface (7 verbs)
- specs/cloud-file-sync/spec.md: 42-scenario completeness matrix (checkable
  total-coverage backbone), N=3 hot-edit defer, K=10 tombstone window
- test-plan.md: manifest, 45 automated (L1/L2) + 2 manual-only, functional-only
- tasks.md: 68 tasks, folded automated scenarios (parser-safe vanilla checkboxes)

Doubt-reviewed 3 cycles (single + cross-model glm-5.2): fixed 4 data-loss paths
(conflict→push re-overwrite, pull pre-write overwrite, native-doc push,
resolve pull-inversion).

* docs(openspec): shelve close-initialize-handoff-via-bus (never implemented)

Doubt-review (single-model + GLM-5.2 cross-model) found the design incoherent
with the shipped server, defeating its own no-click goal via the TOFU trust
resolution, and resting on a wrong value premise:

- No bus primitive awaits worktree_init_done (keyed by requestId+cwd, not a
  session status); plain spawn({cwd}) doesn't fire the hook and re-launches
  project-init. until()/await()/read cannot observe completion.
- TOFU trust gate: auto-confirming a just-written hook bypasses the human gate
  and poisons worktree-init-trust.json, so the next manual click skips TOFU.
- Premise wrong: project-init's worktreeInit hook is ~npm ci; kb-index is the
  kb-extension's separate hook; openspec-init already runs inline at Step 6.
- connect() has no timeout arg; connect-failed is never thrown -> bounded
  degradation is unimplementable as designed.

Archived pre-implementation with NOTES.md capturing findings + revival
prerequisites so it is not re-proposed.

* feat(terminals-in-tabbed-panes): host terminals as editor-pane tabs

Terminals now open as virtual `term:<id>` tabs (new `terminal` ViewerKind)
inside the editor pane, replacing the standalone TerminalsView.

- Session split: opt-in `+ Terminal` create; folder pane: auto-surfaces cwd terminals.
- Keep-alive TerminalPaneLayer (single TerminalView mount per id); registry placeholder.
- SplitWorkspaceContext terminal slice via useTerminalPaneTabs (reconcile D5, auto-surface D3, close-kills D4).
- closeByPath reducer action; VALID_VIEWERS accepts terminal (persisted tabs survive reload).
- Cold-load guard: empty live set = "not yet known", never wipes persisted tabs before the WS snapshot.
- Remove /folder/:cwd/terminals route + TerminalsView + create-time navigation (deselected the session).
- Sidebar [Terminals(N)] retargets to the folder pane.

Implements OpenSpec change terminals-in-tabbed-panes.

* docs(terminals-in-tabbed-panes): archive change + sync specs

Syncs delta specs into openspec/specs (terminal-viewer-tab created,
split-editor-workspace updated, terminals-view capability removed as
fully superseded) and archives the change. Also repairs pre-existing
main-spec corruption (terminals-view had a leaked delta header).
…us-client-scripting

PR BlackBeltTechnology#341 committed the change into both the archive/ path and the active
changes/ path. The archived copy (2026-07-16-add-dashboard-bus-client-scripting)
is authoritative with all tasks checked and specs already synced; the active
duplicate lingered and kept showing as an active 'Complete' change. Remove it.
/live/:id/* threw 500 "reply.from is not a function" after
remove-external-editor-integration (BlackBeltTechnology#342) deleted editor-proxy.ts, the
sole registrant of @fastify/reply-from. live-server-proxy still called
reply.from() but the decorator was never installed.

live-server-proxy is now the sole from-decorator consumer, so it owns
its own dependency and registers the plugin itself.

Harden the regression test: drop the manual @fastify/reply-from
registration that was masking the break, so the test now exercises the
proxy's own self-registration and would catch a future deletion.
…est-plan

Two-axis file-open behavior: mode stickiness (reveal only from closed) +
focus intent (user-click activates, auto-canvas adds silent/unread when
editor shown). Doubt-reviewed (single + cross-model @propose-review-1);
reconciled 10 findings incl. removing a fabricated tool-result path,
handling the deep-link 6th mode-changer, and the shared useOpenTarget
callsite. Scenario-design: 28 scenarios (26 automated / 2 manual-only),
folded to tasks. Coordinate: land after redesign-split-layout-controls.
… test-plan + mockups

Planning artifacts for the split-layout controls redesign:
- divider becomes resize-only (dotted grip); collapse via header switch only
- always-visible pane captions; in-flow rotated restore tabs (fixes overlap bug)
- header: mode switch before Seek; remove model+level (keep piVersion)
- unified session-list dotted-grip seam + centered knob + vertical SESSIONS tab

Approved mockup at mockups/index.html (v1.html superseded). Doubt-reviewed
(single + cross-model glm-5.2): piVersion-not-on-card blocker fixed per user,
mobile scoped to orientation h, a11y + centering + tablet carve-outs added.
16 automated scenarios folded to tasks §7; 2 manual-only to §8.
…s from payload (no file read) (BlackBeltTechnology#346)

* plan(bundle-python-runtime): doubt-reviewed proposal+design+specs+tasks

Planning artifacts for bundling a pip-capable Python env (uv + python-build-standalone,
Tier 2) into the Electron bundle, exposed on PATH for agent bash/tool calls all platforms.

Doubt-review (single + cross-model GLM) corrected 13 findings, notably:
- D5 rewritten: sync-inject + bare-interpreter fallback (async materialize can't be
  awaited in the synchronous spawn-env seam)
- UV_* scoped to materialize subprocess; SSL_CERT_FILE -> interpreter CA (pip certs)
- Windows rebuild-vs-live-install via versioned overlay dirs
- offline base rebuild = online/warm-cache only (Tier 2 non-goal)

scenario-design gate resolved 2 spec gaps (offline pip = pip's own exit/stderr passthrough;
small hash-pinned starter baseline). 24 automated scenarios folded to tasks (test-plan.md
manifest), 0 manual-only. #9 uv file:// mirror layout gated behind a blocking spike.

* docs(security): plan add-universal-network-guard (doubt-review + scenario-design)

plan-proposal on develop. Added design.md + test-plan.md; revised proposal/
specs/tasks after a 2-cycle doubt-driven-review (single-model + cross-model
@propose-review-1 GLM, @propose-review-2 DeepSeek).

Design pivoted: the first draft's "deny-all + enumerate a public allowlist" was
shown unimplementable (hashed static assets rooted at /, SPA history-fallback via
setNotFoundHandler) and would brick the app shell with auth off over a tunnel.
Revised to a namespace-scoped guard: jurisdiction = /api,/v1,/editor,/live,
deny-by-default within, everything else (static/SPA/auth/manifest) untouched.

Reviewer-found + reconciled:
 - /v1 proxy brick: proxy gate must set request.isAuthenticated; guard registered
   last (after bearer/oauth/proxy hooks).
 - /auth/status + SPA shell + deep-link refresh must stay public (now out of
   jurisdiction by construction).
 - model-proxy SECOND port (server.ts:1875) is a separate Fastify instance with
   only the proxy gate -> loopback-bind invariant, test-asserted.
 - proposal/design contradiction (ws-ticket mint) + phantom service worker fixed.

scenario-design: test-plan.md manifest, 18 automated (17 L1 + 1 L3) + 1
manual-only, folded into tasks.md as vanilla checkboxes with exemplar+Triple+
manifest ref. openspec validate --strict passes; all 4 artifacts done.

* plan(openspec-artifact-dialog-desktop): proposal+design+specs+tasks+test-plan

Desktop-viewport modal for P/D/S/T artifact badges; mobile keeps full-page route. Planned via plan-proposal: 2 doubt-review cycles (single+cross-model), scenario-design manifest (20 automated + 1 manual), folded to tasks.

* plan(cloud-sync): add-cloud-sync-connector planning artifacts

Bidirectional local↔cloud file sync connector (Drive/Dropbox/OneDrive).
Baseline-ledger engine, held-conflict resolution, provider adapter seam.

- proposal.md: scope, safety posture (lossless+faithful), pinned decisions
- design.md: impossibility triangle, 8 decisions, 3 engine invariants,
  pressure-tested Provider interface (7 verbs)
- specs/cloud-file-sync/spec.md: 42-scenario completeness matrix (checkable
  total-coverage backbone), N=3 hot-edit defer, K=10 tombstone window
- test-plan.md: manifest, 45 automated (L1/L2) + 2 manual-only, functional-only
- tasks.md: 68 tasks, folded automated scenarios (parser-safe vanilla checkboxes)

Doubt-reviewed 3 cycles (single + cross-model glm-5.2): fixed 4 data-loss paths
(conflict→push re-overwrite, pull pre-write overwrite, native-doc push,
resolve pull-inversion).

* docs(openspec): shelve close-initialize-handoff-via-bus (never implemented)

Doubt-review (single-model + GLM-5.2 cross-model) found the design incoherent
with the shipped server, defeating its own no-click goal via the TOFU trust
resolution, and resting on a wrong value premise:

- No bus primitive awaits worktree_init_done (keyed by requestId+cwd, not a
  session status); plain spawn({cwd}) doesn't fire the hook and re-launches
  project-init. until()/await()/read cannot observe completion.
- TOFU trust gate: auto-confirming a just-written hook bypasses the human gate
  and poisons worktree-init-trust.json, so the next manual click skips TOFU.
- Premise wrong: project-init's worktreeInit hook is ~npm ci; kb-index is the
  kb-extension's separate hook; openspec-init already runs inline at Step 6.
- connect() has no timeout arg; connect-failed is never thrown -> bounded
  degradation is unimplementable as designed.

Archived pre-implementation with NOTES.md capturing findings + revival
prerequisites so it is not re-proposed.

* feat(session-diff): opt-in out-of-cwd session diffs (payload-only, no file read)

Render diffs for files a session writes OUTSIDE its workspace from the captured
Write/Edit event payload — the server never reads the out-of-cwd file (the
doubt-review's core property).

Server:
- session-diff.ts: `resolvePathKey` carries out-of-cwd Write/Edit entries keyed
  by absolute path; `buildSessionDiff` splits in-cwd (enriched) vs out-of-cwd
  (payload-only, previewable:false) BEFORE enrichment so an out-of-cwd path can
  never reach the untracked `readFileSync(resolve(cwd,absPath))` branch. Threads
  `toolCallId` + `truncated` onto FileChangeEvent.
- session-file-reader.ts: `findSessionToolCallPayload(file, toolCallId)` returns
  the FULL untruncated Write/Edit payload from the on-disk JSONL (session-scoped
  id only, no path input/traversal).
- routes: `GET /api/session-change/:sessionId/:toolCallId` → { content?, edits? }
  resolved via sessionManager.sessionFile; miss → 404, reads nothing else.
- preferences-store: backfill + merge `showOutOfCwdSessionDiffs` (default off).

Client:
- DisplayPrefs.showOutOfCwdSessionDiffs (default off, all presets) + ChatViewMenu
  toggle. ChatView suppresses out-of-cwd rows unless on.
- DiffPanel: hide the File toggle when previewable:false; lazy-fetch full payload
  on truncation; truncation banner on fetch failure (never blank).
- diff-tree: out-of-cwd entries group under "outside workspace" (no blank-root).
- normalize-path: `isOutOfCwd`.

Tests: L1 E1-E8 + X3 + truncation threading (session-diff/reader/routes/prefs);
unit F4 (tree) + isOutOfCwd + F5/F3/X2 (DiffPanel); L3 e2e F1/F2/F5 + API-level
F3 (out-of-cwd-session-diffs.spec.ts, harness-verified). X1 superseded by the
payload-only design (never reads the file → deletion is moot).

OpenSpec: opt-in-out-of-cwd-session-diffs

* chore(opt-in-out-of-cwd-session-diffs): archive + sync specs

Archives the change to openspec/changes/archive/ and syncs the
change-summary-table + session-diff-extraction delta specs into
openspec/specs/. Manual-only QA task deferred to post-merge verification.

* fix: apply CodeRabbit feedback for opt-in-out-of-cwd-session-diffs

- DiffPanel: accept the lazy-fetch payload only when it carries a string
  `content` or an `edits` array ({success:true,data:{}} → truncation banner,
  not a silent blank); reset viewMode to Diff when a refresh flips an entry to
  out-of-cwd (previewable:false) in File mode so the /api/session-file fetch
  (403 for out-of-cwd) can never fire.
- Tests: F3 now asserts the fetched full content REPLACES the truncated text in
  the render (RichDiff mock exposes newText) + a previewable→false rerender test;
  isOutOfCwd(undefined-cwd) case; E3 also asserts no existsSync/statSync probe of
  the out-of-cwd path; nested-toolCallId test uses a distinct decoy top-level id.

Skipped (with reason): archive-dir relocation (the change is correctly archived
by the ship workflow); oversized AGENTS.md row splits (pre-existing giant rows —
out of scope); e2e session-scan/marker refinements (reliable as-is on a fresh
container, endpoint content assertion already proves full fidelity).
# Conflicts:
#	CHANGELOG.md
#	docs/AGENTS.md
#	package-lock.json
#	packages/client/src/components/ChatView.tsx
#	packages/client/src/components/ChatViewMenu.tsx
#	packages/client/src/components/ChatViewMenu.tsx.AGENTS.md
#	packages/client/src/components/CommandInput.tsx
#	packages/client/src/components/SessionCard.tsx
#	packages/client/src/components/SettingsPanel.tsx
#	packages/client/src/components/ToolCallStep.tsx
#	packages/client/src/components/__tests__/ChatViewMenu.flip.test.tsx
#	packages/client/src/components/interactive-renderers/SelectRenderer.tsx
#	packages/client/src/hooks/AGENTS.md
#	packages/client/src/hooks/usePopoverFlip.ts
#	packages/client/src/lib/AGENTS.md
#	packages/extension/src/__tests__/role-manager.test.ts
#	packages/extension/src/bridge.ts
#	packages/extension/src/role-manager.ts
#	packages/roles-plugin/src/RolesSettingsSection.tsx
#	packages/roles-plugin/src/__tests__/RolesSettingsSection.test.tsx
#	packages/server/package.json
#	packages/server/src/AGENTS.md
#	packages/server/src/event-wiring.ts
#	packages/server/src/routes/system-routes.ts
#	packages/server/src/server.ts
Add scripts/upstream-sync.sh (status/merge/verify/pr), docs policy,
weekly upstream-sync workflow, and ci-zge focused gates so we can merge
BlackBelt develop without clobbering deploy/push/OMP surfaces. Default
install ref is main; README links the sync runbook.
Use stable branch sync/upstream-<ref>, force-with-lease each run, upsert
the same PR, and close older labeled/sync-head PRs as superseded so a
late review only has the latest merge to look at.
Gate 1 invoked vitest with real HOME, which upstream globalSetup
rejects. Match root npm test isolation and pass package vitest.config.
@joeshull joeshull added the upstream-sync Automated or manual upstream merge label Jul 16, 2026
@joeshull

Copy link
Copy Markdown
Author

Use PR #17 as the single sync review thread; this duplicate is superseded.

@joeshull joeshull closed this Jul 16, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

upstream-sync Automated or manual upstream merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants