wiki-viewer 1.14.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/agent/activity/route.js +6 -6
- package/.next/standalone/.next/server/app/api/agent/activity/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/events/[...path]/route.js +8 -8
- package/.next/standalone/.next/server/app/api/agent/events/[...path]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/files/[...path]/route.js +9 -9
- package/.next/standalone/.next/server/app/api/agent/files/[...path]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/fs/file/[...path]/route.js +8 -8
- package/.next/standalone/.next/server/app/api/agent/fs/file/[...path]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/fs/ls/[[...path]]/route.js +8 -8
- package/.next/standalone/.next/server/app/api/agent/fs/ls/[[...path]]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/fs/move/route.js +8 -8
- package/.next/standalone/.next/server/app/api/agent/fs/move/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/fs/search/route.js +9 -9
- package/.next/standalone/.next/server/app/api/agent/fs/search/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/internal/span/route.js +8 -8
- package/.next/standalone/.next/server/app/api/agent/internal/span/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/settings/route.js +7 -7
- package/.next/standalone/.next/server/app/api/agent/settings/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/sidecar/[...path]/route.js +6 -6
- package/.next/standalone/.next/server/app/api/agent/sidecar/[...path]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/app-proxy/[...path]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/assets/[...path]/route.js +5 -5
- package/.next/standalone/.next/server/app/api/assets/[...path]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/share/[token]/route.js +6 -5
- package/.next/standalone/.next/server/app/api/share/[token]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/share/route.js +4 -4
- package/.next/standalone/.next/server/app/api/share/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/browse/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/clear-root/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/reveal/route.js +6 -6
- package/.next/standalone/.next/server/app/api/system/reveal/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/root-status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/set-root/route.js +6 -5
- package/.next/standalone/.next/server/app/api/system/set-root/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/workspaces/[id]/open/route.js +5 -4
- package/.next/standalone/.next/server/app/api/system/workspaces/[id]/open/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/workspaces/[id]/refresh/route.js +6 -5
- package/.next/standalone/.next/server/app/api/system/workspaces/[id]/refresh/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/workspaces/[id]/route.js +5 -4
- package/.next/standalone/.next/server/app/api/system/workspaces/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/workspaces/route.js +7 -6
- package/.next/standalone/.next/server/app/api/system/workspaces/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/upload/[...path]/route.js +5 -5
- package/.next/standalone/.next/server/app/api/upload/[...path]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/app/route.js +7 -7
- package/.next/standalone/.next/server/app/api/wiki/app/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/backlinks/route.js +7 -7
- package/.next/standalone/.next/server/app/api/wiki/backlinks/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/content/route.js +7 -7
- package/.next/standalone/.next/server/app/api/wiki/content/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/download/route.js +6 -6
- package/.next/standalone/.next/server/app/api/wiki/download/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/file/route.js +5 -5
- package/.next/standalone/.next/server/app/api/wiki/file/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/folder/route.js +5 -5
- package/.next/standalone/.next/server/app/api/wiki/folder/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/git-branches/route.js +4 -4
- package/.next/standalone/.next/server/app/api/wiki/git-branches/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/git-checkout/route.js +6 -6
- package/.next/standalone/.next/server/app/api/wiki/git-checkout/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/git-diff/route.js +4 -4
- package/.next/standalone/.next/server/app/api/wiki/git-diff/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/git-file-info/route.js +4 -4
- package/.next/standalone/.next/server/app/api/wiki/git-file-info/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/git-history/route.js +4 -4
- package/.next/standalone/.next/server/app/api/wiki/git-history/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/git-pull/route.js +8 -8
- package/.next/standalone/.next/server/app/api/wiki/git-pull/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/move/route.js +6 -6
- package/.next/standalone/.next/server/app/api/wiki/move/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/new-file/route.js +5 -5
- package/.next/standalone/.next/server/app/api/wiki/new-file/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/outlinks/route.js +7 -7
- package/.next/standalone/.next/server/app/api/wiki/outlinks/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/page/route.js +5 -5
- package/.next/standalone/.next/server/app/api/wiki/page/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/presence/route.js +6 -6
- package/.next/standalone/.next/server/app/api/wiki/presence/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/route.js +5 -5
- package/.next/standalone/.next/server/app/api/wiki/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/search/route.js +5 -5
- package/.next/standalone/.next/server/app/api/wiki/search/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/slugs/route.js +5 -5
- package/.next/standalone/.next/server/app/api/wiki/slugs/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/upload/route.js +5 -5
- package/.next/standalone/.next/server/app/api/wiki/upload/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/watch/route.js +5 -5
- package/.next/standalone/.next/server/app/api/wiki/watch/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +3 -3
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/s/[token]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/signin/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-jzw2_._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0.-oq-7._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__000spch._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__01oin-a._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__01qcera._.js +16 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__02.uul~._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__024967n._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__06booxp._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__071_pmz._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_0bqpg._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_1f2cw._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0a6j6r4._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0aoqat-._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0as-u6z._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0bvtbax._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0c-ee48._.js +5 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0e_9pc5._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0esmskc._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0fshk._._.js +20 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0fu72o7._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0k9mili._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0l-wu5u._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0lvz22t._.js +20 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0n-chxk._.js +16 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0n9zdf0._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0nmx3k2._.js +12 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0nzlmkb._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o0v6hf._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0s38pid._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0s_8poz._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0sfrx~.._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0sggstu._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0u81k~2._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0uibxf_._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0wfv_by._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0wlj-yw._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0w~__xz._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0yat~ul._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0yxauu6._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zbkgnr._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zv_j05._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0~rntxj._.js +3 -0
- package/.next/standalone/.next/server/chunks/src_lib_search_0d-_j3u._.js +4 -4
- package/.next/standalone/.next/server/chunks/src_lib_search_0ymmp0c._.js +3 -3
- package/.next/standalone/.next/server/chunks/src_lib_workspaces_ts_0qon40u._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{0~w0_katex_dist_katex_mjs_029cdbg._.js → 0~w0_katex_dist_katex_mjs_0s4yb1t._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0z41nrr._.js → [root-of-the-server]__10te1ye._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_13p348f._.js → node_modules__pnpm_0-a_zjq._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0gos._c._.js → node_modules__pnpm_02l_1_8._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0ru1i2-._.js → node_modules__pnpm_03rzttw._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_08xpp3w._.js → node_modules__pnpm_04frxp9._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0b8odbc._.js → node_modules__pnpm_057-t6m._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0f_n_zp._.js → node_modules__pnpm_05_oahi._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0~i3a.i._.js → node_modules__pnpm_05ans~m._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0i2m0b.._.js → node_modules__pnpm_06-2ia.._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0aha1~u._.js → node_modules__pnpm_086.ow4._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0w.kwue._.js → node_modules__pnpm_0cahog3._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0e0r_qk._.js → node_modules__pnpm_0cxanql._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0ajqqlq._.js → node_modules__pnpm_0e09gmf._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0hzzx9d._.js → node_modules__pnpm_0eqjo2f._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_02bje_g._.js → node_modules__pnpm_0f-yazg._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0xk042~._.js → node_modules__pnpm_0iu44jv._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_11ede0p._.js → node_modules__pnpm_0jh8q66._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0k-7uzm._.js → node_modules__pnpm_0m39f~t._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0xr.ztu._.js → node_modules__pnpm_0ma-8l.._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0i6_kfe._.js → node_modules__pnpm_0met6sx._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_04v1_ty._.js → node_modules__pnpm_0mtrsi0._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0607~kg._.js → node_modules__pnpm_0n_swwn._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0t4l89_._.js → node_modules__pnpm_0ndyy93._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0-_58b8._.js → node_modules__pnpm_0s-cj7c._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0y2h_cz._.js → node_modules__pnpm_0tr41-t._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_08edl1n._.js → node_modules__pnpm_0u.uija._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0026mgr._.js → node_modules__pnpm_0wiyn6d._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_09o_spj._.js → node_modules__pnpm_0ygzt82._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_app_page_tsx_0ss2.w7._.js +2 -2
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/middleware-manifest.json +1 -1
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/static/chunks/0ci7khnv7i~7_.js +17 -0
- package/.next/standalone/.next/static/chunks/0sx~65~lg4mgl.css +5 -0
- package/.next/standalone/package.json +1 -1
- package/README.md +88 -17
- package/bin/postbuild.js +21 -0
- package/bin/wiki-viewer.js +198 -6
- package/package.json +1 -1
- package/.next/standalone/.github/workflows/publish.yml +0 -37
- package/.next/standalone/.handoffs/archive/handoff-20260612-113332.md +0 -98
- package/.next/standalone/.handoffs/archive/handoff-20260612-141032.md +0 -73
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-.y6im._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-3_t2y._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-kjkzt._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-puax4._.js +0 -4
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__01h996g._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0329ktu._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__03_xj4r._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__05398dt._.js +0 -4
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__06lfvxh._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__06w0aet._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__070_p2a._.js +0 -4
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0905hi1._.js +0 -21
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0aa63~j._.js +0 -5
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0avcplk._.js +0 -4
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0b8_kyo._.js +0 -16
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0bticq1._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0bwevvu._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0c__k3q._.js +0 -4
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cp~z4x._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0fmdv9h._.js +0 -20
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0h0qbwr._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0jk~oyi._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ndq77b._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0nvgpuk._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ocvng2._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ps4h~.._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q7jqwp._.js +0 -12
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0qew~6p._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0s_9f-a._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0uv7b8u._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0uv8.9i._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0vfe2io._.js +0 -4
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0w11kjy._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0wc-.e4._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ymovmp._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0~c6ouv._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0~rhs0u._.js +0 -16
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__10h9tid._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__10yk-u1._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__11105ew._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__11a3dhw._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__13420tt._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__13h~pdf._.js +0 -3
- package/.next/standalone/.next/static/chunks/02kg.lbkfi1yc.css +0 -5
- package/.next/standalone/.next/static/chunks/0rcwd6-.iu~nd.js +0 -17
- package/.next/standalone/.pi/ownership.json +0 -3
- package/.next/standalone/AGENTS.md +0 -114
- package/.next/standalone/DESIGN.md +0 -521
- package/.next/standalone/README.md +0 -821
- package/.next/standalone/TODO.md +0 -20
- package/.next/standalone/agents/bootstrap-prompt.md +0 -14
- package/.next/standalone/agents/wiki-viewer-skill/SKILL.md +0 -456
- package/.next/standalone/bin/postbuild.js +0 -78
- package/.next/standalone/bin/wiki-viewer.js +0 -922
- package/.next/standalone/certificates/localhost-key.pem +0 -28
- package/.next/standalone/certificates/localhost.pem +0 -24
- package/.next/standalone/docs/agent-collab-plan.md +0 -1615
- package/.next/standalone/docs/agent-collab-v2-plan.md +0 -771
- package/.next/standalone/docs/agent-fs-plan.md +0 -232
- package/.next/standalone/docs/editorial-skin-spec.md +0 -242
- package/.next/standalone/docs/file-vs-collab-authority.md +0 -124
- package/.next/standalone/docs/mobile-readiness-spec.md +0 -229
- package/.next/standalone/docs/workspacing-plan.md +0 -364
- package/.next/standalone/packages/wiki-viewer-mcp/README.md +0 -195
- package/.next/standalone/packages/wiki-viewer-mcp/bench-e2e.mjs +0 -166
- package/.next/standalone/packages/wiki-viewer-mcp/bench-latency.mjs +0 -87
- package/.next/standalone/packages/wiki-viewer-mcp/bench-payload.mjs +0 -45
- package/.next/standalone/packages/wiki-viewer-mcp/dist/http-client.js +0 -331
- package/.next/standalone/packages/wiki-viewer-mcp/dist/index.js +0 -531
- package/.next/standalone/packages/wiki-viewer-mcp/dist/register.js +0 -94
- package/.next/standalone/packages/wiki-viewer-mcp/dist/state-cache.js +0 -28
- package/.next/standalone/packages/wiki-viewer-mcp/package-lock.json +0 -1716
- package/.next/standalone/packages/wiki-viewer-mcp/package.json +0 -41
- package/.next/standalone/packages/wiki-viewer-mcp/src/http-client.ts +0 -459
- package/.next/standalone/packages/wiki-viewer-mcp/src/index.ts +0 -638
- package/.next/standalone/packages/wiki-viewer-mcp/src/register.ts +0 -144
- package/.next/standalone/packages/wiki-viewer-mcp/src/state-cache.ts +0 -55
- package/.next/standalone/packages/wiki-viewer-mcp/tsconfig.json +0 -19
- package/.next/standalone/pnpm-lock.yaml +0 -6697
- package/.next/standalone/pnpm-workspace.yaml +0 -8
- package/.next/standalone/postcss.config.mjs +0 -2
- package/.next/standalone/src/app/api/agent/activity/route.ts +0 -59
- package/.next/standalone/src/app/api/agent/admin/agents/[agentId]/revoke/route.ts +0 -40
- package/.next/standalone/src/app/api/agent/admin/agents/route.ts +0 -33
- package/.next/standalone/src/app/api/agent/admin/registrations/[regId]/approve/route.ts +0 -83
- package/.next/standalone/src/app/api/agent/admin/registrations/[regId]/deny/route.ts +0 -36
- package/.next/standalone/src/app/api/agent/admin/registrations/route.ts +0 -27
- package/.next/standalone/src/app/api/agent/events/[...path]/route.ts +0 -148
- package/.next/standalone/src/app/api/agent/files/[...path]/route.ts +0 -219
- package/.next/standalone/src/app/api/agent/fs/file/[...path]/route.ts +0 -446
- package/.next/standalone/src/app/api/agent/fs/ls/[[...path]]/route.ts +0 -152
- package/.next/standalone/src/app/api/agent/fs/move/route.ts +0 -130
- package/.next/standalone/src/app/api/agent/fs/search/route.ts +0 -272
- package/.next/standalone/src/app/api/agent/internal/span/route.ts +0 -121
- package/.next/standalone/src/app/api/agent/register/[regId]/route.ts +0 -50
- package/.next/standalone/src/app/api/agent/register/route.ts +0 -118
- package/.next/standalone/src/app/api/agent/settings/route.ts +0 -34
- package/.next/standalone/src/app/api/agent/settings/token/regenerate/route.ts +0 -20
- package/.next/standalone/src/app/api/agent/sidecar/[...path]/route.ts +0 -53
- package/.next/standalone/src/app/api/agents/install/route.ts +0 -143
- package/.next/standalone/src/app/api/agents/skill/route.ts +0 -20
- package/.next/standalone/src/app/api/agents/skill.tar.gz/route.ts +0 -87
- package/.next/standalone/src/app/api/app-proxy/[...path]/route.ts +0 -247
- package/.next/standalone/src/app/api/assets/[...path]/route.ts +0 -75
- package/.next/standalone/src/app/api/auth/[...all]/route.ts +0 -7
- package/.next/standalone/src/app/api/owner/init/route.ts +0 -14
- package/.next/standalone/src/app/api/share/[token]/route.ts +0 -269
- package/.next/standalone/src/app/api/share/route.ts +0 -127
- package/.next/standalone/src/app/api/system/admins/route.ts +0 -103
- package/.next/standalone/src/app/api/system/auth-config/route.ts +0 -22
- package/.next/standalone/src/app/api/system/auth-settings/route.ts +0 -86
- package/.next/standalone/src/app/api/system/browse/route.ts +0 -82
- package/.next/standalone/src/app/api/system/clear-root/route.ts +0 -16
- package/.next/standalone/src/app/api/system/config/route.ts +0 -14
- package/.next/standalone/src/app/api/system/pins/route.ts +0 -38
- package/.next/standalone/src/app/api/system/reveal/route.ts +0 -36
- package/.next/standalone/src/app/api/system/root-status/route.ts +0 -13
- package/.next/standalone/src/app/api/system/set-root/route.ts +0 -53
- package/.next/standalone/src/app/api/system/users/route.ts +0 -120
- package/.next/standalone/src/app/api/system/workspaces/[id]/open/route.ts +0 -34
- package/.next/standalone/src/app/api/system/workspaces/[id]/refresh/route.ts +0 -32
- package/.next/standalone/src/app/api/system/workspaces/[id]/route.ts +0 -78
- package/.next/standalone/src/app/api/system/workspaces/route.ts +0 -128
- package/.next/standalone/src/app/api/upload/[...path]/route.ts +0 -85
- package/.next/standalone/src/app/api/wiki/app/route.ts +0 -60
- package/.next/standalone/src/app/api/wiki/backlinks/route.ts +0 -29
- package/.next/standalone/src/app/api/wiki/content/route.ts +0 -159
- package/.next/standalone/src/app/api/wiki/download/route.ts +0 -100
- package/.next/standalone/src/app/api/wiki/file/route.ts +0 -57
- package/.next/standalone/src/app/api/wiki/folder/route.ts +0 -34
- package/.next/standalone/src/app/api/wiki/git-branches/route.ts +0 -32
- package/.next/standalone/src/app/api/wiki/git-checkout/route.ts +0 -45
- package/.next/standalone/src/app/api/wiki/git-diff/route.ts +0 -33
- package/.next/standalone/src/app/api/wiki/git-file-info/route.ts +0 -27
- package/.next/standalone/src/app/api/wiki/git-history/route.ts +0 -27
- package/.next/standalone/src/app/api/wiki/git-pull/route.ts +0 -48
- package/.next/standalone/src/app/api/wiki/move/route.ts +0 -61
- package/.next/standalone/src/app/api/wiki/new-file/route.ts +0 -56
- package/.next/standalone/src/app/api/wiki/outlinks/route.ts +0 -76
- package/.next/standalone/src/app/api/wiki/page/route.ts +0 -92
- package/.next/standalone/src/app/api/wiki/presence/route.ts +0 -56
- package/.next/standalone/src/app/api/wiki/route.ts +0 -116
- package/.next/standalone/src/app/api/wiki/search/route.ts +0 -44
- package/.next/standalone/src/app/api/wiki/slugs/route.ts +0 -54
- package/.next/standalone/src/app/api/wiki/upload/route.ts +0 -95
- package/.next/standalone/src/app/api/wiki/watch/route.ts +0 -67
- package/.next/standalone/src/app/globals.css +0 -1249
- package/.next/standalone/src/app/icon.svg +0 -11
- package/.next/standalone/src/app/layout.tsx +0 -78
- package/.next/standalone/src/app/manifest.ts +0 -30
- package/.next/standalone/src/app/page.tsx +0 -3260
- package/.next/standalone/src/app/s/[token]/page.tsx +0 -357
- package/.next/standalone/src/app/signin/page.tsx +0 -16
- package/.next/standalone/src/app/signin/signin-form.tsx +0 -238
- package/.next/standalone/src/components/ai-panel/activity-row.tsx +0 -284
- package/.next/standalone/src/components/ai-panel/ai-panel.tsx +0 -311
- package/.next/standalone/src/components/ai-panel/token-section.tsx +0 -313
- package/.next/standalone/src/components/auth-settings-sheet.tsx +0 -474
- package/.next/standalone/src/components/confirm-dialog.tsx +0 -56
- package/.next/standalone/src/components/dir-picker.tsx +0 -606
- package/.next/standalone/src/components/editor/backlinks-panel.tsx +0 -139
- package/.next/standalone/src/components/editor/bubble-menu.tsx +0 -346
- package/.next/standalone/src/components/editor/callout-extension.ts +0 -36
- package/.next/standalone/src/components/editor/comment-pip.tsx +0 -56
- package/.next/standalone/src/components/editor/comment-thread.tsx +0 -254
- package/.next/standalone/src/components/editor/csv-viewer.tsx +0 -379
- package/.next/standalone/src/components/editor/document-outline.tsx +0 -230
- package/.next/standalone/src/components/editor/editor-toolbar.tsx +0 -564
- package/.next/standalone/src/components/editor/editor.tsx +0 -1327
- package/.next/standalone/src/components/editor/extensions/drag-handle.ts +0 -325
- package/.next/standalone/src/components/editor/extensions/heading-anchors.ts +0 -55
- package/.next/standalone/src/components/editor/extensions/proof-span.ts +0 -60
- package/.next/standalone/src/components/editor/extensions/resizable-image.tsx +0 -159
- package/.next/standalone/src/components/editor/extensions.ts +0 -150
- package/.next/standalone/src/components/editor/file-fallback-viewer.tsx +0 -75
- package/.next/standalone/src/components/editor/folder-index.tsx +0 -236
- package/.next/standalone/src/components/editor/google-doc-viewer.tsx +0 -73
- package/.next/standalone/src/components/editor/image-viewer.tsx +0 -57
- package/.next/standalone/src/components/editor/large-file-gate.tsx +0 -108
- package/.next/standalone/src/components/editor/link-popover.tsx +0 -111
- package/.next/standalone/src/components/editor/media-popover.tsx +0 -212
- package/.next/standalone/src/components/editor/media-viewer.tsx +0 -70
- package/.next/standalone/src/components/editor/mermaid-viewer.tsx +0 -286
- package/.next/standalone/src/components/editor/node-app-viewer.tsx +0 -328
- package/.next/standalone/src/components/editor/notebook-viewer.tsx +0 -371
- package/.next/standalone/src/components/editor/office/docx-viewer.tsx +0 -93
- package/.next/standalone/src/components/editor/office/office-chrome.tsx +0 -83
- package/.next/standalone/src/components/editor/office/pptx-viewer.tsx +0 -104
- package/.next/standalone/src/components/editor/office/xlsx-viewer.tsx +0 -192
- package/.next/standalone/src/components/editor/pdf-viewer.tsx +0 -32
- package/.next/standalone/src/components/editor/proof-span-popover.tsx +0 -162
- package/.next/standalone/src/components/editor/slash-commands.tsx +0 -629
- package/.next/standalone/src/components/editor/source-viewer.tsx +0 -289
- package/.next/standalone/src/components/editor/suggest-edit-popover.tsx +0 -229
- package/.next/standalone/src/components/editor/suggestion-card.tsx +0 -206
- package/.next/standalone/src/components/editor/table-menu.tsx +0 -232
- package/.next/standalone/src/components/editor/view-mode-comment-button.tsx +0 -77
- package/.next/standalone/src/components/editor/website-viewer.tsx +0 -75
- package/.next/standalone/src/components/editor/wiki-link-create-dialog.tsx +0 -189
- package/.next/standalone/src/components/editor/wiki-link-decorator.ts +0 -79
- package/.next/standalone/src/components/editor/wiki-link-extension.ts +0 -120
- package/.next/standalone/src/components/editor/wiki-link-picker.tsx +0 -250
- package/.next/standalone/src/components/layout/viewer-toolbar.tsx +0 -54
- package/.next/standalone/src/components/search/search-command-dialog.tsx +0 -246
- package/.next/standalone/src/components/search/sidebar-search-box.tsx +0 -121
- package/.next/standalone/src/components/search/snippet-text.tsx +0 -48
- package/.next/standalone/src/components/share-dialog.tsx +0 -307
- package/.next/standalone/src/components/theme-provider.tsx +0 -16
- package/.next/standalone/src/components/theme-toggle.tsx +0 -24
- package/.next/standalone/src/components/ui/badge.tsx +0 -35
- package/.next/standalone/src/components/ui/button.tsx +0 -56
- package/.next/standalone/src/components/ui/card.tsx +0 -63
- package/.next/standalone/src/components/ui/collapsible.tsx +0 -9
- package/.next/standalone/src/components/ui/command.tsx +0 -151
- package/.next/standalone/src/components/ui/context-menu.tsx +0 -191
- package/.next/standalone/src/components/ui/dialog.tsx +0 -119
- package/.next/standalone/src/components/ui/dropdown-menu.tsx +0 -194
- package/.next/standalone/src/components/ui/input.tsx +0 -22
- package/.next/standalone/src/components/ui/label.tsx +0 -25
- package/.next/standalone/src/components/ui/popover.tsx +0 -33
- package/.next/standalone/src/components/ui/scroll-area.tsx +0 -47
- package/.next/standalone/src/components/ui/select.tsx +0 -156
- package/.next/standalone/src/components/ui/separator.tsx +0 -30
- package/.next/standalone/src/components/ui/sheet.tsx +0 -140
- package/.next/standalone/src/components/ui/skeleton.tsx +0 -15
- package/.next/standalone/src/components/ui/switch.tsx +0 -28
- package/.next/standalone/src/components/ui/tabs.tsx +0 -54
- package/.next/standalone/src/components/ui/textarea.tsx +0 -21
- package/.next/standalone/src/components/ui/tip.tsx +0 -23
- package/.next/standalone/src/components/ui/toggle-group.tsx +0 -41
- package/.next/standalone/src/components/ui/tooltip.tsx +0 -27
- package/.next/standalone/src/components/view-width-toggle.tsx +0 -70
- package/.next/standalone/src/components/wiki/frontmatter-header.tsx +0 -164
- package/.next/standalone/src/hooks/use-is-mobile.ts +0 -14
- package/.next/standalone/src/lib/app-runner.ts +0 -272
- package/.next/standalone/src/lib/auth/admin.ts +0 -120
- package/.next/standalone/src/lib/auth/allowlist.ts +0 -50
- package/.next/standalone/src/lib/auth/client.ts +0 -4
- package/.next/standalone/src/lib/auth/csrf.ts +0 -68
- package/.next/standalone/src/lib/auth/server.ts +0 -215
- package/.next/standalone/src/lib/cabinets/tree.ts +0 -75
- package/.next/standalone/src/lib/config.ts +0 -114
- package/.next/standalone/src/lib/embeds/detect.ts +0 -142
- package/.next/standalone/src/lib/git-secrets.ts +0 -65
- package/.next/standalone/src/lib/git.ts +0 -503
- package/.next/standalone/src/lib/google/detect.ts +0 -107
- package/.next/standalone/src/lib/markdown/parse-frontmatter.ts +0 -104
- package/.next/standalone/src/lib/markdown/remark-wikilinks.ts +0 -100
- package/.next/standalone/src/lib/markdown/sanitize-schema.ts +0 -106
- package/.next/standalone/src/lib/markdown/to-html.ts +0 -282
- package/.next/standalone/src/lib/markdown/to-markdown.ts +0 -252
- package/.next/standalone/src/lib/proof/activity-shared.ts +0 -31
- package/.next/standalone/src/lib/proof/activity.ts +0 -74
- package/.next/standalone/src/lib/proof/audit.ts +0 -88
- package/.next/standalone/src/lib/proof/auth.ts +0 -143
- package/.next/standalone/src/lib/proof/block-refs.ts +0 -133
- package/.next/standalone/src/lib/proof/blocks.ts +0 -73
- package/.next/standalone/src/lib/proof/client-auth.ts +0 -23
- package/.next/standalone/src/lib/proof/collab-state.ts +0 -81
- package/.next/standalone/src/lib/proof/event-bus.ts +0 -47
- package/.next/standalone/src/lib/proof/file-lock.ts +0 -43
- package/.next/standalone/src/lib/proof/glob.ts +0 -51
- package/.next/standalone/src/lib/proof/idempotency.ts +0 -37
- package/.next/standalone/src/lib/proof/lease.ts +0 -117
- package/.next/standalone/src/lib/proof/mutex.ts +0 -36
- package/.next/standalone/src/lib/proof/ops-applier.ts +0 -727
- package/.next/standalone/src/lib/proof/owner-auth.ts +0 -2
- package/.next/standalone/src/lib/proof/pending.ts +0 -97
- package/.next/standalone/src/lib/proof/proof-span.ts +0 -141
- package/.next/standalone/src/lib/proof/rate-limit.ts +0 -53
- package/.next/standalone/src/lib/proof/raw-fs.ts +0 -197
- package/.next/standalone/src/lib/proof/register-rate-limit.ts +0 -53
- package/.next/standalone/src/lib/proof/registry.ts +0 -195
- package/.next/standalone/src/lib/proof/sidecar.ts +0 -103
- package/.next/standalone/src/lib/proof/suggest-capture.ts +0 -69
- package/.next/standalone/src/lib/proof/types.ts +0 -182
- package/.next/standalone/src/lib/proof-config.ts +0 -14
- package/.next/standalone/src/lib/root-dir.ts +0 -35
- package/.next/standalone/src/lib/search/indexable-exts.ts +0 -25
- package/.next/standalone/src/lib/search/indexer.ts +0 -634
- package/.next/standalone/src/lib/search/sanitize.ts +0 -35
- package/.next/standalone/src/lib/search/search-db.ts +0 -57
- package/.next/standalone/src/lib/search/watcher-pool.ts +0 -79
- package/.next/standalone/src/lib/shared-docs/db.ts +0 -177
- package/.next/standalone/src/lib/toast.ts +0 -9
- package/.next/standalone/src/lib/utils.ts +0 -6
- package/.next/standalone/src/lib/wiki-helpers.ts +0 -36
- package/.next/standalone/src/lib/workspace-client.ts +0 -82
- package/.next/standalone/src/lib/workspace-context.ts +0 -173
- package/.next/standalone/src/lib/workspaces.ts +0 -400
- package/.next/standalone/src/middleware.ts +0 -44
- package/.next/standalone/src/stores/ai-panel-store.ts +0 -79
- package/.next/standalone/src/stores/editor-store.ts +0 -439
- package/.next/standalone/src/stores/pin-store.ts +0 -56
- package/.next/standalone/src/stores/proof-store.ts +0 -124
- package/.next/standalone/src/stores/recent-store.ts +0 -51
- package/.next/standalone/src/stores/search-store.ts +0 -104
- package/.next/standalone/src/stores/show-hidden-store.ts +0 -26
- package/.next/standalone/src/stores/sidebar-width-store.ts +0 -39
- package/.next/standalone/src/stores/skin-store.ts +0 -42
- package/.next/standalone/src/stores/tree-store.ts +0 -52
- package/.next/standalone/src/stores/view-width-store.ts +0 -101
- package/.next/standalone/src/stores/wiki-slugs-store.ts +0 -75
- package/.next/standalone/src/tests/proof/activity-aggregator.test.ts +0 -146
- package/.next/standalone/src/tests/proof/admin-create-user.test.ts +0 -43
- package/.next/standalone/src/tests/proof/agent-fs.test.ts +0 -973
- package/.next/standalone/src/tests/proof/agent-ownership.test.ts +0 -140
- package/.next/standalone/src/tests/proof/agent-workspace-scope.test.ts +0 -134
- package/.next/standalone/src/tests/proof/agents-install.test.ts +0 -110
- package/.next/standalone/src/tests/proof/bench-write.ts +0 -179
- package/.next/standalone/src/tests/proof/better-auth.test.ts +0 -68
- package/.next/standalone/src/tests/proof/block-refs.test.ts +0 -108
- package/.next/standalone/src/tests/proof/blocks.test.ts +0 -92
- package/.next/standalone/src/tests/proof/collab-state.test.ts +0 -501
- package/.next/standalone/src/tests/proof/comments-ops.test.ts +0 -177
- package/.next/standalone/src/tests/proof/editor-roundtrip.test.ts +0 -85
- package/.next/standalone/src/tests/proof/external-edit.test.ts +0 -58
- package/.next/standalone/src/tests/proof/file-lock.test.ts +0 -80
- package/.next/standalone/src/tests/proof/git-branches-checkout.test.ts +0 -136
- package/.next/standalone/src/tests/proof/git-read-features.test.ts +0 -473
- package/.next/standalone/src/tests/proof/git-secrets.test.ts +0 -138
- package/.next/standalone/src/tests/proof/git-sparse.test.ts +0 -257
- package/.next/standalone/src/tests/proof/git-workspace.test.ts +0 -135
- package/.next/standalone/src/tests/proof/git.test.ts +0 -243
- package/.next/standalone/src/tests/proof/glob.test.ts +0 -82
- package/.next/standalone/src/tests/proof/helpers/auth-session.ts +0 -27
- package/.next/standalone/src/tests/proof/markdown-to-html.test.ts +0 -46
- package/.next/standalone/src/tests/proof/ops-applier.test.ts +0 -368
- package/.next/standalone/src/tests/proof/owner-init.test.ts +0 -2
- package/.next/standalone/src/tests/proof/parse-frontmatter.test.ts +0 -60
- package/.next/standalone/src/tests/proof/preload.ts +0 -21
- package/.next/standalone/src/tests/proof/proof-span.test.ts +0 -98
- package/.next/standalone/src/tests/proof/rate-limit.test.ts +0 -54
- package/.next/standalone/src/tests/proof/readonly-enforcement.test.ts +0 -161
- package/.next/standalone/src/tests/proof/reconcile-sidecar.test.ts +0 -195
- package/.next/standalone/src/tests/proof/registration-flow.test.ts +0 -330
- package/.next/standalone/src/tests/proof/registry.test.ts +0 -169
- package/.next/standalone/src/tests/proof/routes.test.ts +0 -516
- package/.next/standalone/src/tests/proof/search-index.test.ts +0 -376
- package/.next/standalone/src/tests/proof/sidecar-lifecycle.test.ts +0 -177
- package/.next/standalone/src/tests/proof/sidecar-trim.test.ts +0 -58
- package/.next/standalone/src/tests/proof/suggestion-ops.test.ts +0 -280
- package/.next/standalone/src/tests/proof/wiki-content-put.test.ts +0 -140
- package/.next/standalone/src/tests/proof/wiki-routes-auth.test.ts +0 -208
- package/.next/standalone/src/tests/proof/workspace-isolation.test.ts +0 -87
- package/.next/standalone/src/tests/proof/workspaces-api.test.ts +0 -227
- package/.next/standalone/src/types/index.ts +0 -47
- package/.next/standalone/tailwind.config.ts +0 -191
- package/.next/standalone/tsconfig.json +0 -41
- package/.next/standalone/tsconfig.tsbuildinfo +0 -1
- /package/.next/standalone/.next/static/{WMOTgfV82apmdovxzgbMx → ecpoWt_SP3WkUTh0NsnuT}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{WMOTgfV82apmdovxzgbMx → ecpoWt_SP3WkUTh0NsnuT}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{WMOTgfV82apmdovxzgbMx → ecpoWt_SP3WkUTh0NsnuT}/_ssgManifest.js +0 -0
|
@@ -1,1615 +0,0 @@
|
|
|
1
|
-
# Agent Collaboration Protocol — Implementation Plan
|
|
2
|
-
|
|
3
|
-
**Status:** Ready to implement
|
|
4
|
-
**Target:** wiki-viewer one-shot build, no v2
|
|
5
|
-
**Audience:** An implementing agent with no prior context on this conversation
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## 0. Read this first
|
|
10
|
-
|
|
11
|
-
You are implementing an HTTP collaboration protocol that lets remote AI agents read and edit markdown files in wiki-viewer, with full provenance tracking and human-in-the-loop comments / suggestions.
|
|
12
|
-
|
|
13
|
-
The design is intentionally **API-compatible with [Proof SDK](https://github.com/EveryInc/proof-sdk)** in spirit — same op vocabulary, same block-ref + revision model, same suggestion lifecycle. This is so the user's mental model of "what an agent can do" transfers from Proof (which they already use and like) to wiki-viewer (this project).
|
|
14
|
-
|
|
15
|
-
**Hard constraints:**
|
|
16
|
-
|
|
17
|
-
- Single-user, local-first. No multi-tenancy, no share links, no realtime CRDT.
|
|
18
|
-
- File on disk is the source of truth. Always.
|
|
19
|
-
- Provenance marks live **inline** in the markdown (`<proof-span>` HTML).
|
|
20
|
-
- Comments / threads / suggestions / event log / block-ref maps live in a **sidecar directory** (`.proof/`) next to the file.
|
|
21
|
-
- TipTap is the editor. Do not introduce Milkdown or swap editors.
|
|
22
|
-
- No new runtime dependencies are required. Use what's in `package.json`.
|
|
23
|
-
- Block-ref + revision is the only mutation contract. **No selectors, no quote-matching**, no fuzzy-anchor resolution.
|
|
24
|
-
- Suggestions exist (proof has them, user wants them). Comments exist with threaded replies.
|
|
25
|
-
|
|
26
|
-
**Don't build:**
|
|
27
|
-
|
|
28
|
-
- Yjs, Hocuspocus, WebSockets, realtime collab.
|
|
29
|
-
- Multiple humans editing concurrently.
|
|
30
|
-
- Share-token creation flows, hosted-style `POST /documents` to create slugs.
|
|
31
|
-
- A new editor. Keep TipTap.
|
|
32
|
-
- Auth UI beyond a single bearer-token env var + a "regenerate token" button.
|
|
33
|
-
- Anything in proof's `server/` outside the agent bridge surface (snapshots, milkdown-headless, projection guardrails, S3, sqlite).
|
|
34
|
-
|
|
35
|
-
---
|
|
36
|
-
|
|
37
|
-
## 1. Glossary
|
|
38
|
-
|
|
39
|
-
| Term | Definition |
|
|
40
|
-
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
41
|
-
| **File path** | Relative path from `ROOT_DIR`. Acts as the document slug. URL-encoded in routes. |
|
|
42
|
-
| **Block** | A top-level markdown element. Heading, paragraph, list, blockquote, code fence, table, hr, html block. Lists and tables are treated as a single block. |
|
|
43
|
-
| **Block ref** | Stable string ID like `b7f2a1`. Persists across edits via the sidecar's ref map. |
|
|
44
|
-
| **Revision** | Monotonic integer per file. Bumps on every successful mutation. Used for optimistic locking. |
|
|
45
|
-
| **Provenance span** | An inline `<proof-span>` HTML element wrapping AI-authored text, with `origin`, `basis`, `by`, `at`, `id` attributes. Lives in the markdown. |
|
|
46
|
-
| **Comment** | A discussion thread attached to a block ref. Lives in the sidecar. Has ordered turns (initial + replies). |
|
|
47
|
-
| **Suggestion** | A proposed edit on a block ref. Pending / accepted / rejected lifecycle. Lives in the sidecar until accepted (becomes a real block edit) or rejected (deleted from sidecar). |
|
|
48
|
-
| **Event** | Immutable log entry. New events emitted on every state change. Polled by agents. |
|
|
49
|
-
| **Sidecar** | `<ROOT_DIR>/.proof/<file-path>.json` — one JSON file per markdown file, holds ref map, comments, suggestions, event log. |
|
|
50
|
-
|
|
51
|
-
---
|
|
52
|
-
|
|
53
|
-
## 2. Architectural overview
|
|
54
|
-
|
|
55
|
-
```
|
|
56
|
-
~/notes/ ROOT_DIR
|
|
57
|
-
├── plan.md canonical content + inline <proof-span> marks
|
|
58
|
-
├── specs.md
|
|
59
|
-
├── subdir/
|
|
60
|
-
│ └── notes.md
|
|
61
|
-
└── .proof/
|
|
62
|
-
├── plan.md.json sidecar for plan.md
|
|
63
|
-
├── specs.md.json
|
|
64
|
-
└── subdir/
|
|
65
|
-
└── notes.md.json
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
Sidecar path mirrors the source path under `.proof/`. Create directories as needed. `.proof/` is a single tree at the root, **not** a per-directory dotdir.
|
|
69
|
-
|
|
70
|
-
Mutation flow:
|
|
71
|
-
|
|
72
|
-
```
|
|
73
|
-
agent ── POST /api/agent/files/<path> ──► route handler
|
|
74
|
-
│
|
|
75
|
-
├─ acquire per-file mutex
|
|
76
|
-
├─ load sidecar + file
|
|
77
|
-
├─ verify baseRevision matches
|
|
78
|
-
├─ apply ops (block.* mutate the .md,
|
|
79
|
-
│ comment.* / suggestion.* mutate sidecar,
|
|
80
|
-
│ suggestion.accept does both)
|
|
81
|
-
├─ write .md (rewraps proof-spans on inserts/replaces)
|
|
82
|
-
├─ write sidecar with new revision + appended events
|
|
83
|
-
├─ release mutex
|
|
84
|
-
└─ return new snapshot
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
Read flow:
|
|
88
|
-
|
|
89
|
-
```
|
|
90
|
-
agent ── GET /api/agent/files/<path> ──► load .md → parse → assign refs → return blocks
|
|
91
|
-
agent ── GET /api/agent/events/<path>?after=N ──► tail sidecar's event log
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
Editor flow (browser):
|
|
95
|
-
|
|
96
|
-
```
|
|
97
|
-
chokidar watcher (already exists) → SSE → editor reloads file
|
|
98
|
-
→ re-parses inline <proof-span> via TipTap mark
|
|
99
|
-
→ renders gutter pips for comments (loaded from sidecar via separate REST call)
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
---
|
|
103
|
-
|
|
104
|
-
## 3. File-by-file scope
|
|
105
|
-
|
|
106
|
-
```
|
|
107
|
-
src/
|
|
108
|
-
├── app/api/agent/ ← NEW
|
|
109
|
-
│ ├── files/[...path]/
|
|
110
|
-
│ │ ├── route.ts GET = snapshot, POST = apply ops
|
|
111
|
-
│ ├── events/[...path]/route.ts GET poll events, POST ack
|
|
112
|
-
│ ├── sidecar/[...path]/route.ts GET sidecar (for editor UI to load comments/suggestions)
|
|
113
|
-
│ └── settings/
|
|
114
|
-
│ ├── route.ts GET settings, POST regenerate token
|
|
115
|
-
│ └── token/route.ts GET masked token + scope
|
|
116
|
-
├── lib/proof/ ← NEW
|
|
117
|
-
│ ├── blocks.ts markdown → blocks parser, block → markdown emitter
|
|
118
|
-
│ ├── block-refs.ts ref generation, sidecar ref map updates
|
|
119
|
-
│ ├── proof-span.ts mark schema + serialization helpers (shared client/server)
|
|
120
|
-
│ ├── sidecar.ts read/write/migrate sidecar JSON
|
|
121
|
-
│ ├── ops-applier.ts op execution against (markdown, sidecar) tuple
|
|
122
|
-
│ ├── event-bus.ts emit/poll/ack events on sidecar
|
|
123
|
-
│ ├── idempotency.ts LRU keyed by Idempotency-Key
|
|
124
|
-
│ ├── mutex.ts per-file async mutex
|
|
125
|
-
│ ├── auth.ts bearer-token check middleware
|
|
126
|
-
│ └── types.ts Op, Block, Snapshot, Event, Comment, Suggestion
|
|
127
|
-
├── components/editor/
|
|
128
|
-
│ ├── extensions/
|
|
129
|
-
│ │ └── proof-span.ts ← NEW TipTap mark for <proof-span>
|
|
130
|
-
│ ├── proof-span-popover.tsx ← NEW Hover popover: origin, basis, Accept/Revert
|
|
131
|
-
│ ├── comment-pip.tsx ← NEW Gutter pip per commented block
|
|
132
|
-
│ ├── comment-thread.tsx ← NEW Thread popover w/ reply form
|
|
133
|
-
│ ├── suggestion-card.tsx ← NEW Inline pending-suggestion card with Accept/Reject
|
|
134
|
-
│ └── extensions.ts ← EDIT Register ProofSpan mark
|
|
135
|
-
├── components/ai-panel/ ← NEW
|
|
136
|
-
│ ├── ai-panel.tsx Right-side drawer: connections + activity + token UI
|
|
137
|
-
│ ├── activity-row.tsx
|
|
138
|
-
│ └── token-section.tsx
|
|
139
|
-
├── stores/
|
|
140
|
-
│ ├── proof-store.ts ← NEW zustand: sidecar data per file, event tail cursor
|
|
141
|
-
│ └── ai-panel-store.ts ← EDIT Wire to real activity feed (replace stub)
|
|
142
|
-
├── lib/markdown/
|
|
143
|
-
│ └── to-markdown.ts ← EDIT Add turndown rule preserving <proof-span>
|
|
144
|
-
└── lib/proof-config.ts ← NEW Reads AGENT_BEARER_TOKEN, rate limits, etc.
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
Approx LoC: ~1900. One worker session.
|
|
148
|
-
|
|
149
|
-
---
|
|
150
|
-
|
|
151
|
-
## 4. Wire formats
|
|
152
|
-
|
|
153
|
-
### 4.1 The HTTP surface
|
|
154
|
-
|
|
155
|
-
All routes mount under `/api/agent/`. Authentication: `Authorization: Bearer <token>` header. If `AGENT_BEARER_TOKEN` env var is unset, accept any request from `127.0.0.1` and reject all others.
|
|
156
|
-
|
|
157
|
-
| Method | Route | Purpose |
|
|
158
|
-
| ------ | ---------------------------------------------- | ---------------------------------------------------------- |
|
|
159
|
-
| `GET` | `/api/agent/files/<path>` | Snapshot: blocks + revision + meta |
|
|
160
|
-
| `POST` | `/api/agent/files/<path>` | Apply ops, return new snapshot |
|
|
161
|
-
| `GET` | `/api/agent/events/<path>?after=<n>&limit=<n>` | Poll for events on this file |
|
|
162
|
-
| `POST` | `/api/agent/events/<path>` | Acknowledge events (for cleanup) |
|
|
163
|
-
| `GET` | `/api/agent/sidecar/<path>` | Browser-side UI loads comments/suggestions |
|
|
164
|
-
| `GET` | `/api/agent/settings` | Returns `{ rateLimit, hasToken, root }` |
|
|
165
|
-
| `POST` | `/api/agent/settings/token/regenerate` | Mints a new token (writes to `~/.wiki-viewer/agent-token`) |
|
|
166
|
-
|
|
167
|
-
`<path>` uses Next.js catch-all `[...path]`. Segments are URL-encoded. Forbidden chars: `..`, leading `/`, anything resolving outside `ROOT_DIR`. Use the existing `safeRootPath` from `src/lib/root-dir.ts`.
|
|
168
|
-
|
|
169
|
-
### 4.2 Snapshot response
|
|
170
|
-
|
|
171
|
-
```json
|
|
172
|
-
{
|
|
173
|
-
"path": "notes/plan.md",
|
|
174
|
-
"revision": 47,
|
|
175
|
-
"createdAt": "2026-05-29T10:00:00Z",
|
|
176
|
-
"updatedAt": "2026-05-29T10:30:12Z",
|
|
177
|
-
"fingerprint": "sha256:ab12...",
|
|
178
|
-
"blocks": [
|
|
179
|
-
{ "ref": "b1a3f0", "type": "heading", "level": 1, "markdown": "# Q2 Plan" },
|
|
180
|
-
{
|
|
181
|
-
"ref": "b7f2c1",
|
|
182
|
-
"type": "paragraph",
|
|
183
|
-
"markdown": "Ship the rewrite by end of June."
|
|
184
|
-
},
|
|
185
|
-
{ "ref": "b9c104", "type": "heading", "level": 2, "markdown": "## Risks" },
|
|
186
|
-
{ "ref": "be0123", "type": "bulletList", "markdown": "- tbd" }
|
|
187
|
-
],
|
|
188
|
-
"comments": [
|
|
189
|
-
{
|
|
190
|
-
"id": "c4a1",
|
|
191
|
-
"ref": "b7f2c1",
|
|
192
|
-
"resolved": false,
|
|
193
|
-
"createdAt": "2026-05-29T10:00:00Z",
|
|
194
|
-
"turns": [
|
|
195
|
-
{
|
|
196
|
-
"by": "human",
|
|
197
|
-
"text": "Why end of June?",
|
|
198
|
-
"at": "2026-05-29T10:00:00Z"
|
|
199
|
-
},
|
|
200
|
-
{
|
|
201
|
-
"by": "ai:claude",
|
|
202
|
-
"text": "Because of the API freeze.",
|
|
203
|
-
"at": "2026-05-29T10:00:14Z"
|
|
204
|
-
}
|
|
205
|
-
]
|
|
206
|
-
}
|
|
207
|
-
],
|
|
208
|
-
"suggestions": [
|
|
209
|
-
{
|
|
210
|
-
"id": "s3b2",
|
|
211
|
-
"ref": "b7f2c1",
|
|
212
|
-
"status": "pending",
|
|
213
|
-
"by": "ai:claude",
|
|
214
|
-
"kind": "replace",
|
|
215
|
-
"markdown": "Ship the rewrite by July 15.",
|
|
216
|
-
"basis": "described",
|
|
217
|
-
"basisDetail": "user mentioned slippage in chat",
|
|
218
|
-
"createdAt": "2026-05-29T10:01:00Z"
|
|
219
|
-
}
|
|
220
|
-
],
|
|
221
|
-
"lastEventId": 47
|
|
222
|
-
}
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
`fingerprint` is sha256 of the raw .md file on disk, hex-encoded.
|
|
226
|
-
|
|
227
|
-
### 4.3 Block type vocabulary
|
|
228
|
-
|
|
229
|
-
Block parser must emit exactly one of these `type` values:
|
|
230
|
-
|
|
231
|
-
- `heading` (with `level: 1..6`)
|
|
232
|
-
- `paragraph`
|
|
233
|
-
- `bulletList`
|
|
234
|
-
- `orderedList`
|
|
235
|
-
- `taskList`
|
|
236
|
-
- `blockquote`
|
|
237
|
-
- `codeBlock` (with optional `lang`)
|
|
238
|
-
- `table`
|
|
239
|
-
- `hr`
|
|
240
|
-
- `html` (raw HTML block, e.g. embeds — kept verbatim)
|
|
241
|
-
|
|
242
|
-
A "block" is anything at the root of the markdown AST. Lists are atomic — a 5-item list is one block, not 5. This matches Proof's model and keeps refs stable when items shuffle inside a list.
|
|
243
|
-
|
|
244
|
-
### 4.4 Op vocabulary
|
|
245
|
-
|
|
246
|
-
Send via `POST /api/agent/files/<path>`. Body:
|
|
247
|
-
|
|
248
|
-
```json
|
|
249
|
-
{
|
|
250
|
-
"baseRevision": 47,
|
|
251
|
-
"by": "ai:claude",
|
|
252
|
-
"ops": [ <op>, <op>, ... ]
|
|
253
|
-
}
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
Headers required on mutations:
|
|
257
|
-
|
|
258
|
-
- `Content-Type: application/json`
|
|
259
|
-
- `Idempotency-Key: <uuid>` (treat as required — return 400 if absent)
|
|
260
|
-
- `Authorization: Bearer <token>` (when token configured)
|
|
261
|
-
|
|
262
|
-
The ops, applied in order (atomic — all succeed or all roll back):
|
|
263
|
-
|
|
264
|
-
#### Block ops
|
|
265
|
-
|
|
266
|
-
```json
|
|
267
|
-
{ "type": "block.replace", "ref": "b7f2c1", "markdown": "New content." }
|
|
268
|
-
{ "type": "block.insertAfter", "ref": "b7f2c1", "markdown": "## New section\n\nBody." }
|
|
269
|
-
{ "type": "block.insertBefore","ref": "b7f2c1", "markdown": "..." }
|
|
270
|
-
{ "type": "block.delete", "ref": "b7f2c1" }
|
|
271
|
-
{ "type": "block.append", "markdown": "..." } // appended to file end, no ref
|
|
272
|
-
{ "type": "block.prepend", "markdown": "..." } // prepended to file start, no ref
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
`markdown` may contain multiple top-level blocks. They are inserted in order at the target position.
|
|
276
|
-
|
|
277
|
-
If the op originates from an AI agent (`by` starts with `ai:`), the **inserted text content is wrapped in a `<proof-span>` mark** automatically (see §5.3). The agent doesn't construct the span; the server does. Agents MAY supply additional metadata:
|
|
278
|
-
|
|
279
|
-
```json
|
|
280
|
-
{
|
|
281
|
-
"type": "block.insertAfter",
|
|
282
|
-
"ref": "b7f2c1",
|
|
283
|
-
"markdown": "The team will focus on three pillars...",
|
|
284
|
-
"basis": "described",
|
|
285
|
-
"basisDetail": "user asked for opening paragraph",
|
|
286
|
-
"inResponseTo": "c4a1"
|
|
287
|
-
}
|
|
288
|
-
```
|
|
289
|
-
|
|
290
|
-
`basis` ∈ `"described" | "inferred" | "suggested"`. Defaults to `"inferred"` for AI ops.
|
|
291
|
-
|
|
292
|
-
#### Comment ops
|
|
293
|
-
|
|
294
|
-
```json
|
|
295
|
-
{ "type": "comment.add", "ref": "b7f2c1", "text": "Why end of June?" }
|
|
296
|
-
{ "type": "comment.reply", "commentId": "c4a1", "text": "Because of API freeze." }
|
|
297
|
-
{ "type": "comment.resolve", "commentId": "c4a1" }
|
|
298
|
-
{ "type": "comment.reopen", "commentId": "c4a1" }
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
`by` is taken from the request body's top-level `by` field.
|
|
302
|
-
|
|
303
|
-
#### Suggestion ops
|
|
304
|
-
|
|
305
|
-
```json
|
|
306
|
-
{
|
|
307
|
-
"type": "suggestion.add",
|
|
308
|
-
"ref": "b7f2c1",
|
|
309
|
-
"kind": "replace",
|
|
310
|
-
"markdown": "New content.",
|
|
311
|
-
"basis": "described",
|
|
312
|
-
"basisDetail": "user asked for clarity",
|
|
313
|
-
"status": "pending"
|
|
314
|
-
}
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
- `kind` ∈ `"replace" | "insertAfter" | "insertBefore" | "delete"`
|
|
318
|
-
- `status` defaults to `"pending"`. If passed as `"accepted"`, the server applies the change immediately in the same mutation (equivalent to adding then accepting in one shot — Proof-compatible behaviour).
|
|
319
|
-
|
|
320
|
-
```json
|
|
321
|
-
{ "type": "suggestion.accept", "suggestionId": "s3b2" }
|
|
322
|
-
{ "type": "suggestion.reject", "suggestionId": "s3b2" }
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
On accept: apply the suggestion as the corresponding `block.*` op, then mark suggestion `accepted` in sidecar. On reject: mark `rejected` and prune from active list (move to `archivedSuggestions` array, keep for event history but don't include in default snapshot).
|
|
326
|
-
|
|
327
|
-
### 4.5 Response codes
|
|
328
|
-
|
|
329
|
-
| Status | Code field | Meaning |
|
|
330
|
-
| ------ | -------------------------------------------- | ------------------------------------------------------------------------ |
|
|
331
|
-
| 200 | — | Success, body is new snapshot |
|
|
332
|
-
| 400 | `INVALID_PAYLOAD` | Malformed body / missing `Idempotency-Key` |
|
|
333
|
-
| 401 | `UNAUTHORIZED` | Bad / missing bearer token (when token configured) |
|
|
334
|
-
| 404 | `FILE_NOT_FOUND` | Path doesn't resolve under ROOT_DIR |
|
|
335
|
-
| 409 | `STALE_REVISION` | `baseRevision` doesn't match current. Response includes fresh snapshot. |
|
|
336
|
-
| 409 | `BLOCK_NOT_FOUND` | A block ref in an op no longer exists. Response includes fresh snapshot. |
|
|
337
|
-
| 409 | `COMMENT_NOT_FOUND` / `SUGGESTION_NOT_FOUND` | Same idea. |
|
|
338
|
-
| 422 | `INVALID_MARKDOWN` | Op's `markdown` field fails to parse cleanly |
|
|
339
|
-
| 429 | `RATE_LIMITED` | See §6.3 |
|
|
340
|
-
| 500 | `INTERNAL` | Unexpected. Log to server. |
|
|
341
|
-
|
|
342
|
-
`Idempotency-Key` replay: if the same key + same payload hash is seen within 5 minutes, return the cached response (same status code and body). If same key + different payload, return 409 with `IDEMPOTENCY_KEY_REUSED`.
|
|
343
|
-
|
|
344
|
-
### 4.6 Events
|
|
345
|
-
|
|
346
|
-
Every successful mutation emits one or more events appended to the sidecar's `events` array. Event IDs are monotonic per file (not global).
|
|
347
|
-
|
|
348
|
-
```json
|
|
349
|
-
{ "id": 48, "type": "block.replaced", "ref": "b7f2c1", "by": "ai:claude", "at": "...", "newRef": "b8f3d2" }
|
|
350
|
-
{ "id": 49, "type": "block.inserted", "after": "b7f2c1", "refs": ["b8f3d2", "b8f3d3"], "by": "ai:claude", "at": "..." }
|
|
351
|
-
{ "id": 50, "type": "block.deleted", "ref": "b9c104", "by": "human", "at": "..." }
|
|
352
|
-
{ "id": 51, "type": "comment.added", "commentId": "c4a1", "ref": "b7f2c1", "text": "...", "by": "human", "at": "..." }
|
|
353
|
-
{ "id": 52, "type": "comment.replied", "commentId": "c4a1", "text": "...", "by": "ai:claude", "at": "..." }
|
|
354
|
-
{ "id": 53, "type": "comment.resolved", "commentId": "c4a1", "by": "human", "at": "..." }
|
|
355
|
-
{ "id": 54, "type": "suggestion.added", "suggestionId": "s3b2", "by": "ai:claude", "at": "..." }
|
|
356
|
-
{ "id": 55, "type": "suggestion.accepted", "suggestionId": "s3b2", "by": "human", "at": "..." }
|
|
357
|
-
{ "id": 56, "type": "suggestion.rejected", "suggestionId": "s3b2", "by": "human", "at": "..." }
|
|
358
|
-
{ "id": 57, "type": "file.externallyEdited", "fingerprint": "sha256:...", "at": "..." }
|
|
359
|
-
{ "id": 58, "type": "span.accepted", "spanId": "p1", "by": "human", "at": "..." }
|
|
360
|
-
{ "id": 59, "type": "span.reverted", "spanId": "p1", "by": "human", "at": "..." }
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
`file.externallyEdited`: emitted when chokidar detects a file change that did NOT come from a mutation API call. This is how remote agents notice that the human edited in vim.
|
|
364
|
-
|
|
365
|
-
Poll: `GET /api/agent/events/<path>?after=47&limit=100`. Default limit 100, max 1000. Returns `{ "events": [...], "lastEventId": 59 }`.
|
|
366
|
-
|
|
367
|
-
Ack: `POST /api/agent/events/<path>` with body `{ "upToId": 59, "by": "ai:claude" }`. HTTP method dispatch: GET=poll, POST=ack.
|
|
368
|
-
|
|
369
|
-
> **Note:** Route paths restructured for Next 16 catch-all compliance; semantics unchanged. **Acks are purely advisory** — events are never deleted. The ack writes a `lastAck.<by>` cursor on the sidecar, for diagnostic purposes. This is intentional: events are a small append-only log, file sizes stay bounded by §6.5 trimming.
|
|
370
|
-
|
|
371
|
-
---
|
|
372
|
-
|
|
373
|
-
## 5. Detailed implementation
|
|
374
|
-
|
|
375
|
-
### 5.1 `src/lib/proof/types.ts`
|
|
376
|
-
|
|
377
|
-
```ts
|
|
378
|
-
export type BlockType =
|
|
379
|
-
| "heading"
|
|
380
|
-
| "paragraph"
|
|
381
|
-
| "bulletList"
|
|
382
|
-
| "orderedList"
|
|
383
|
-
| "taskList"
|
|
384
|
-
| "blockquote"
|
|
385
|
-
| "codeBlock"
|
|
386
|
-
| "table"
|
|
387
|
-
| "hr"
|
|
388
|
-
| "html";
|
|
389
|
-
|
|
390
|
-
export interface Block {
|
|
391
|
-
ref: string; // "b" + 6-hex
|
|
392
|
-
type: BlockType;
|
|
393
|
-
level?: number; // headings only
|
|
394
|
-
lang?: string; // codeBlock only
|
|
395
|
-
markdown: string; // canonical markdown for this block, trailing \n stripped
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
export interface ProvenanceMeta {
|
|
399
|
-
origin: "human" | "ai";
|
|
400
|
-
basis?: "described" | "inferred" | "suggested";
|
|
401
|
-
basisDetail?: string;
|
|
402
|
-
by?: string; // "ai:claude" or "human"
|
|
403
|
-
at?: string; // ISO 8601
|
|
404
|
-
spanId: string; // "p" + 4-hex
|
|
405
|
-
inResponseTo?: string; // comment id
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
export interface CommentTurn {
|
|
409
|
-
by: string; // "human" | "ai:claude"
|
|
410
|
-
text: string;
|
|
411
|
-
at: string;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
export interface Comment {
|
|
415
|
-
id: string; // "c" + 4-hex
|
|
416
|
-
ref: string; // block ref it's attached to
|
|
417
|
-
resolved: boolean;
|
|
418
|
-
createdAt: string;
|
|
419
|
-
turns: CommentTurn[];
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
export type SuggestionKind =
|
|
423
|
-
| "replace"
|
|
424
|
-
| "insertAfter"
|
|
425
|
-
| "insertBefore"
|
|
426
|
-
| "delete";
|
|
427
|
-
export type SuggestionStatus = "pending" | "accepted" | "rejected";
|
|
428
|
-
|
|
429
|
-
export interface Suggestion {
|
|
430
|
-
id: string; // "s" + 4-hex
|
|
431
|
-
ref: string;
|
|
432
|
-
kind: SuggestionKind;
|
|
433
|
-
status: SuggestionStatus;
|
|
434
|
-
by: string;
|
|
435
|
-
markdown?: string; // omitted for kind=delete
|
|
436
|
-
basis?: ProvenanceMeta["basis"];
|
|
437
|
-
basisDetail?: string;
|
|
438
|
-
createdAt: string;
|
|
439
|
-
resolvedAt?: string; // when accepted/rejected
|
|
440
|
-
resolvedBy?: string;
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
export interface ProofEvent {
|
|
444
|
-
id: number;
|
|
445
|
-
type: string; // see §4.6 list
|
|
446
|
-
at: string;
|
|
447
|
-
by: string;
|
|
448
|
-
[k: string]: unknown;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
export interface Sidecar {
|
|
452
|
-
schemaVersion: 1;
|
|
453
|
-
path: string;
|
|
454
|
-
revision: number;
|
|
455
|
-
createdAt: string;
|
|
456
|
-
updatedAt: string;
|
|
457
|
-
// Map of block.ref → current text fingerprint (sha256 of block markdown, first 12 hex).
|
|
458
|
-
// Used to detect external edits and to keep refs stable through round-trips.
|
|
459
|
-
refMap: Record<string, { textHash: string; lastSeenAt: string }>;
|
|
460
|
-
// History of ref renames. Old ref → new ref, kept for ONE generation.
|
|
461
|
-
refAliases: Record<string, string>;
|
|
462
|
-
comments: Comment[];
|
|
463
|
-
suggestions: Suggestion[];
|
|
464
|
-
archivedSuggestions: Suggestion[];
|
|
465
|
-
events: ProofEvent[];
|
|
466
|
-
nextEventId: number;
|
|
467
|
-
lastAck: Record<string, number>; // by → eventId
|
|
468
|
-
fingerprint: string; // last-known sha256 of the .md file
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
export type Op =
|
|
472
|
-
| {
|
|
473
|
-
type: "block.replace";
|
|
474
|
-
ref: string;
|
|
475
|
-
markdown: string;
|
|
476
|
-
basis?: string;
|
|
477
|
-
basisDetail?: string;
|
|
478
|
-
inResponseTo?: string;
|
|
479
|
-
}
|
|
480
|
-
| {
|
|
481
|
-
type: "block.insertAfter";
|
|
482
|
-
ref: string;
|
|
483
|
-
markdown: string;
|
|
484
|
-
basis?: string;
|
|
485
|
-
basisDetail?: string;
|
|
486
|
-
inResponseTo?: string;
|
|
487
|
-
}
|
|
488
|
-
| {
|
|
489
|
-
type: "block.insertBefore";
|
|
490
|
-
ref: string;
|
|
491
|
-
markdown: string;
|
|
492
|
-
basis?: string;
|
|
493
|
-
basisDetail?: string;
|
|
494
|
-
inResponseTo?: string;
|
|
495
|
-
}
|
|
496
|
-
| { type: "block.delete"; ref: string }
|
|
497
|
-
| {
|
|
498
|
-
type: "block.append";
|
|
499
|
-
markdown: string;
|
|
500
|
-
basis?: string;
|
|
501
|
-
basisDetail?: string;
|
|
502
|
-
inResponseTo?: string;
|
|
503
|
-
}
|
|
504
|
-
| {
|
|
505
|
-
type: "block.prepend";
|
|
506
|
-
markdown: string;
|
|
507
|
-
basis?: string;
|
|
508
|
-
basisDetail?: string;
|
|
509
|
-
inResponseTo?: string;
|
|
510
|
-
}
|
|
511
|
-
| { type: "comment.add"; ref: string; text: string }
|
|
512
|
-
| { type: "comment.reply"; commentId: string; text: string }
|
|
513
|
-
| { type: "comment.resolve"; commentId: string }
|
|
514
|
-
| { type: "comment.reopen"; commentId: string }
|
|
515
|
-
| {
|
|
516
|
-
type: "suggestion.add";
|
|
517
|
-
ref: string;
|
|
518
|
-
kind: SuggestionKind;
|
|
519
|
-
markdown?: string;
|
|
520
|
-
basis?: string;
|
|
521
|
-
basisDetail?: string;
|
|
522
|
-
status?: SuggestionStatus;
|
|
523
|
-
}
|
|
524
|
-
| { type: "suggestion.accept"; suggestionId: string }
|
|
525
|
-
| { type: "suggestion.reject"; suggestionId: string };
|
|
526
|
-
|
|
527
|
-
export interface Snapshot {
|
|
528
|
-
path: string;
|
|
529
|
-
revision: number;
|
|
530
|
-
createdAt: string;
|
|
531
|
-
updatedAt: string;
|
|
532
|
-
fingerprint: string;
|
|
533
|
-
blocks: Block[];
|
|
534
|
-
comments: Comment[]; // unresolved + resolved (separately by client)
|
|
535
|
-
suggestions: Suggestion[]; // pending only by default
|
|
536
|
-
lastEventId: number;
|
|
537
|
-
}
|
|
538
|
-
```
|
|
539
|
-
|
|
540
|
-
### 5.2 `src/lib/proof/blocks.ts` — markdown ↔ blocks
|
|
541
|
-
|
|
542
|
-
Reuse what's in `package.json`: `unified`, `remark-parse`, `remark-gfm`. **Do not add new deps.**
|
|
543
|
-
|
|
544
|
-
```ts
|
|
545
|
-
import { unified } from "unified";
|
|
546
|
-
import remarkParse from "remark-parse";
|
|
547
|
-
import remarkGfm from "remark-gfm";
|
|
548
|
-
import remarkStringify from "remark-stringify";
|
|
549
|
-
import type { Root, RootContent } from "mdast";
|
|
550
|
-
|
|
551
|
-
const PARSER = unified().use(remarkParse).use(remarkGfm);
|
|
552
|
-
const STRINGIFY = unified()
|
|
553
|
-
.use(remarkStringify, {
|
|
554
|
-
bullet: "-",
|
|
555
|
-
fence: "`",
|
|
556
|
-
fences: true,
|
|
557
|
-
listItemIndent: "one",
|
|
558
|
-
rule: "-",
|
|
559
|
-
emphasis: "*",
|
|
560
|
-
strong: "*",
|
|
561
|
-
})
|
|
562
|
-
.use(remarkGfm);
|
|
563
|
-
|
|
564
|
-
export function parseBlocks(markdown: string): RootContent[] {
|
|
565
|
-
const tree = PARSER.parse(markdown) as Root;
|
|
566
|
-
return tree.children;
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
export function blockToMarkdown(node: RootContent): string {
|
|
570
|
-
const tree: Root = { type: "root", children: [node] };
|
|
571
|
-
return (STRINGIFY.stringify(tree) as string).replace(/\n+$/, "");
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
export function blocksToMarkdown(nodes: RootContent[]): string {
|
|
575
|
-
const tree: Root = { type: "root", children: nodes };
|
|
576
|
-
return (STRINGIFY.stringify(tree) as string).replace(/\n+$/, "") + "\n";
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
export function blockType(node: RootContent): {
|
|
580
|
-
type: BlockType;
|
|
581
|
-
level?: number;
|
|
582
|
-
lang?: string;
|
|
583
|
-
} {
|
|
584
|
-
switch (node.type) {
|
|
585
|
-
case "heading":
|
|
586
|
-
return { type: "heading", level: (node as any).depth };
|
|
587
|
-
case "paragraph":
|
|
588
|
-
return { type: "paragraph" };
|
|
589
|
-
case "list": {
|
|
590
|
-
const n = node as any;
|
|
591
|
-
if (
|
|
592
|
-
n.children?.some(
|
|
593
|
-
(li: any) => li.checked !== null && li.checked !== undefined,
|
|
594
|
-
)
|
|
595
|
-
)
|
|
596
|
-
return { type: "taskList" };
|
|
597
|
-
return { type: n.ordered ? "orderedList" : "bulletList" };
|
|
598
|
-
}
|
|
599
|
-
case "blockquote":
|
|
600
|
-
return { type: "blockquote" };
|
|
601
|
-
case "code":
|
|
602
|
-
return { type: "codeBlock", lang: (node as any).lang ?? undefined };
|
|
603
|
-
case "table":
|
|
604
|
-
return { type: "table" };
|
|
605
|
-
case "thematicBreak":
|
|
606
|
-
return { type: "hr" };
|
|
607
|
-
case "html":
|
|
608
|
-
return { type: "html" };
|
|
609
|
-
default:
|
|
610
|
-
return { type: "paragraph" }; // fallback
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
```
|
|
614
|
-
|
|
615
|
-
**Important roundtrip note:** `<proof-span>` elements inside paragraphs are parsed as raw HTML by remark and preserved in the AST. They survive `parse → stringify` unchanged. **Verify this with a unit test** (see §7).
|
|
616
|
-
|
|
617
|
-
### 5.3 `src/lib/proof/proof-span.ts` — provenance marks
|
|
618
|
-
|
|
619
|
-
Format (inline HTML, valid in CommonMark and GFM, ignored by markdown renderers, decorated by TipTap):
|
|
620
|
-
|
|
621
|
-
```html
|
|
622
|
-
<proof-span
|
|
623
|
-
id="p4a1"
|
|
624
|
-
origin="ai"
|
|
625
|
-
basis="described"
|
|
626
|
-
by="ai:claude"
|
|
627
|
-
at="2026-05-29T10:00:00Z"
|
|
628
|
-
in-response-to="c4a1"
|
|
629
|
-
>The text the AI wrote.</proof-span
|
|
630
|
-
>
|
|
631
|
-
```
|
|
632
|
-
|
|
633
|
-
```ts
|
|
634
|
-
export interface SpanAttrs {
|
|
635
|
-
spanId: string;
|
|
636
|
-
origin: "ai" | "human";
|
|
637
|
-
basis?: string;
|
|
638
|
-
basisDetail?: string;
|
|
639
|
-
by: string;
|
|
640
|
-
at: string;
|
|
641
|
-
inResponseTo?: string;
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
const SPAN_OPEN = /<proof-span\b([^>]*)>/g;
|
|
645
|
-
|
|
646
|
-
export function wrapAsProofSpan(markdown: string, attrs: SpanAttrs): string {
|
|
647
|
-
// Find first non-whitespace leading content; wrap whole block's text content.
|
|
648
|
-
// The simplest correct approach: stringify the block, then wrap the
|
|
649
|
-
// *content portion* (everything after the leading markdown sigil) in a span.
|
|
650
|
-
//
|
|
651
|
-
// For paragraphs / list items: wrap the text content directly.
|
|
652
|
-
// For headings: wrap text after the leading "#"s.
|
|
653
|
-
// For code blocks, tables, hr: do NOT wrap — emit a separate sidecar-only
|
|
654
|
-
// provenance record (see §5.4 ProvenanceMeta on sidecar).
|
|
655
|
-
//
|
|
656
|
-
// The wrapper takes care of escaping " in attrs via ".
|
|
657
|
-
...
|
|
658
|
-
}
|
|
659
|
-
```
|
|
660
|
-
|
|
661
|
-
Wrap rules (be conservative — if in doubt, don't wrap and record in sidecar instead):
|
|
662
|
-
|
|
663
|
-
| Block type | Wrap inline? |
|
|
664
|
-
| ----------------------------------- | ------------------------------------------------------- |
|
|
665
|
-
| paragraph | yes, wrap text content |
|
|
666
|
-
| heading | yes, wrap text after `#`s |
|
|
667
|
-
| bulletList / orderedList / taskList | yes, wrap each list item's text |
|
|
668
|
-
| blockquote | yes, wrap text content |
|
|
669
|
-
| codeBlock | no — store provenance in sidecar `blockProvenance[ref]` |
|
|
670
|
-
| table | no — sidecar only |
|
|
671
|
-
| hr | no — sidecar only |
|
|
672
|
-
| html | no — sidecar only |
|
|
673
|
-
|
|
674
|
-
Add to `Sidecar` interface:
|
|
675
|
-
|
|
676
|
-
```ts
|
|
677
|
-
blockProvenance?: Record<string, SpanAttrs>; // for blocks we can't wrap inline
|
|
678
|
-
```
|
|
679
|
-
|
|
680
|
-
`unwrap` is straightforward: strip `<proof-span ...>` and `</proof-span>` from a markdown string for "Accept" (keep content, drop attribution mark). For "Revert" (delete content): the editor passes the span ID, server looks up the block, replaces span content with empty string, prunes empty paragraph if result is empty.
|
|
681
|
-
|
|
682
|
-
### 5.4 `src/lib/proof/block-refs.ts` — ref stability
|
|
683
|
-
|
|
684
|
-
Goal: when the agent has snapshot at revision 47 and posts `block.replace ref="b7f2c1"` based on it, the server must find that block even if the content has changed slightly (it shouldn't have, but defense-in-depth) and must give the new resulting block a stable new ref.
|
|
685
|
-
|
|
686
|
-
Algorithm (one-shot, no LSM, no complex CRDT):
|
|
687
|
-
|
|
688
|
-
1. **First parse** (no sidecar exists): for each block, compute `textHash = sha256(blockMarkdown).slice(0,12)`. Ref = `"b" + textHash.slice(0,6)`. Collision? Append `_<position>`.
|
|
689
|
-
2. **Subsequent parse** (sidecar exists with refMap): compute `textHash` per current block. For each block:
|
|
690
|
-
- If `textHash` matches an existing ref's `textHash` → reuse that ref.
|
|
691
|
-
- Else: this is a new block. Mint a new ref the same way as (1).
|
|
692
|
-
3. **After applying ops**: regenerate refMap from the new block list. For any old ref that was replaced/changed, record in `refAliases[oldRef] = newRef`. Aliases only persist ONE revision — they're flushed on the next mutation.
|
|
693
|
-
|
|
694
|
-
This gives "refs follow content when content barely changed" and "in-flight agent retries within one revision still resolve" without true content-addressable storage.
|
|
695
|
-
|
|
696
|
-
Block resolution for ops:
|
|
697
|
-
|
|
698
|
-
```ts
|
|
699
|
-
function resolveRef(
|
|
700
|
-
sidecar: Sidecar,
|
|
701
|
-
ref: string,
|
|
702
|
-
currentRefs: Set<string>,
|
|
703
|
-
): string | null {
|
|
704
|
-
if (currentRefs.has(ref)) return ref;
|
|
705
|
-
if (sidecar.refAliases[ref] && currentRefs.has(sidecar.refAliases[ref])) {
|
|
706
|
-
return sidecar.refAliases[ref];
|
|
707
|
-
}
|
|
708
|
-
return null;
|
|
709
|
-
}
|
|
710
|
-
```
|
|
711
|
-
|
|
712
|
-
### 5.5 `src/lib/proof/sidecar.ts`
|
|
713
|
-
|
|
714
|
-
```ts
|
|
715
|
-
export function sidecarPath(rootDir: string, mdPath: string): string {
|
|
716
|
-
return path.join(rootDir, ".proof", mdPath + ".json");
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
export async function readSidecar(rootDir: string, mdPath: string): Promise<Sidecar | null> { ... }
|
|
720
|
-
export async function writeSidecar(rootDir: string, mdPath: string, sc: Sidecar): Promise<void> {
|
|
721
|
-
await mkdir(path.dirname(sidecarPath(rootDir, mdPath)), { recursive: true });
|
|
722
|
-
// Atomic-ish write: write to .tmp, fsync, rename.
|
|
723
|
-
const tmp = sidecarPath(rootDir, mdPath) + ".tmp";
|
|
724
|
-
await writeFile(tmp, JSON.stringify(sc, null, 2), "utf-8");
|
|
725
|
-
await rename(tmp, sidecarPath(rootDir, mdPath));
|
|
726
|
-
}
|
|
727
|
-
export function emptySidecar(mdPath: string): Sidecar { ... }
|
|
728
|
-
```
|
|
729
|
-
|
|
730
|
-
Schema migrations: keep `schemaVersion: 1`. On read, if a different version → fail loudly (not silently). One-shot project, accept the constraint.
|
|
731
|
-
|
|
732
|
-
### 5.6 `src/lib/proof/ops-applier.ts` — the heart
|
|
733
|
-
|
|
734
|
-
Single entry point:
|
|
735
|
-
|
|
736
|
-
```ts
|
|
737
|
-
export async function applyOps(args: {
|
|
738
|
-
rootDir: string;
|
|
739
|
-
mdPath: string;
|
|
740
|
-
baseRevision: number;
|
|
741
|
-
by: string;
|
|
742
|
-
ops: Op[];
|
|
743
|
-
}): Promise<
|
|
744
|
-
| { ok: true; snapshot: Snapshot; emittedEvents: ProofEvent[] }
|
|
745
|
-
| {
|
|
746
|
-
ok: false;
|
|
747
|
-
status: number;
|
|
748
|
-
code: string;
|
|
749
|
-
message: string;
|
|
750
|
-
snapshot?: Snapshot;
|
|
751
|
-
}
|
|
752
|
-
>;
|
|
753
|
-
```
|
|
754
|
-
|
|
755
|
-
Implementation outline (pseudo):
|
|
756
|
-
|
|
757
|
-
```
|
|
758
|
-
acquire perFileMutex(mdPath)
|
|
759
|
-
try:
|
|
760
|
-
read .md from disk
|
|
761
|
-
fingerprint = sha256(.md)
|
|
762
|
-
sidecar = readSidecar() ?? emptySidecar(mdPath)
|
|
763
|
-
|
|
764
|
-
if sidecar.fingerprint && sidecar.fingerprint !== fingerprint:
|
|
765
|
-
// External edit happened. Re-parse blocks (refs auto-stabilize via §5.4),
|
|
766
|
-
// emit "file.externallyEdited", bump revision, persist, then continue.
|
|
767
|
-
|
|
768
|
-
blocks = parseBlocks(.md)
|
|
769
|
-
assignedBlocks = assignRefs(blocks, sidecar) // returns Block[] with refs
|
|
770
|
-
|
|
771
|
-
if baseRevision !== sidecar.revision:
|
|
772
|
-
return STALE_REVISION + freshSnapshot
|
|
773
|
-
|
|
774
|
-
workingBlocks = [...assignedBlocks]
|
|
775
|
-
workingSidecar = clone(sidecar)
|
|
776
|
-
workingEvents = []
|
|
777
|
-
|
|
778
|
-
for op of ops:
|
|
779
|
-
apply op:
|
|
780
|
-
block.* → mutate workingBlocks, regen refs for changed/new, append events
|
|
781
|
-
comment.* → mutate workingSidecar.comments, append events
|
|
782
|
-
suggestion.add → push to workingSidecar.suggestions
|
|
783
|
-
if status === "accepted": apply as block.* in-place + log accepted
|
|
784
|
-
suggestion.accept → look up suggestion, apply as block op, mark accepted
|
|
785
|
-
suggestion.reject → move to archivedSuggestions, log rejected
|
|
786
|
-
if op fails (ref not found etc.): rollback, return appropriate 409
|
|
787
|
-
|
|
788
|
-
newMarkdown = blocksToMarkdown(workingBlocks)
|
|
789
|
-
newFingerprint = sha256(newMarkdown)
|
|
790
|
-
workingSidecar.revision += 1
|
|
791
|
-
workingSidecar.updatedAt = now()
|
|
792
|
-
workingSidecar.fingerprint = newFingerprint
|
|
793
|
-
workingSidecar.refMap = recomputeRefMap(workingBlocks)
|
|
794
|
-
workingSidecar.refAliases = collectedAliases // from this op batch only
|
|
795
|
-
workingSidecar.events.push(...workingEvents)
|
|
796
|
-
workingSidecar.nextEventId += workingEvents.length
|
|
797
|
-
|
|
798
|
-
writeFile(.md, newMarkdown)
|
|
799
|
-
writeSidecar(workingSidecar)
|
|
800
|
-
|
|
801
|
-
return { ok: true, snapshot, emittedEvents }
|
|
802
|
-
finally:
|
|
803
|
-
release mutex
|
|
804
|
-
```
|
|
805
|
-
|
|
806
|
-
**Important: AI wrapping happens here**, not in routes. When applying `block.insertAfter` from an `ai:*` actor:
|
|
807
|
-
|
|
808
|
-
- Parse the op's `markdown` into block nodes
|
|
809
|
-
- For each text-bearing node, wrap text in a freshly-minted `<proof-span>` with attrs from the op (`origin: "ai"`, `basis`, `basisDetail`, `by`, `at`, `spanId`, `inResponseTo`)
|
|
810
|
-
- For non-wrappable block types (code, table, hr, html), record into `sidecar.blockProvenance[newRef]` instead
|
|
811
|
-
- Stringify back to markdown for insertion
|
|
812
|
-
|
|
813
|
-
When `by` is `"human"` (or any non-`ai:*` value), no wrap. Human edits are unmarked by default.
|
|
814
|
-
|
|
815
|
-
### 5.7 Routes — `src/app/api/agent/files/[...path]/route.ts`
|
|
816
|
-
|
|
817
|
-
```ts
|
|
818
|
-
export const runtime = "nodejs";
|
|
819
|
-
|
|
820
|
-
import { NextResponse } from "next/server";
|
|
821
|
-
import { getRootDir, safeRootPath } from "@/lib/root-dir";
|
|
822
|
-
import { checkAuth } from "@/lib/proof/auth";
|
|
823
|
-
import { applyOps } from "@/lib/proof/ops-applier";
|
|
824
|
-
import { readSnapshot } from "@/lib/proof/ops-applier"; // pure read variant
|
|
825
|
-
import { idempotency } from "@/lib/proof/idempotency";
|
|
826
|
-
|
|
827
|
-
export async function GET(
|
|
828
|
-
req: Request,
|
|
829
|
-
{ params }: { params: { path: string[] } },
|
|
830
|
-
) {
|
|
831
|
-
const auth = checkAuth(req);
|
|
832
|
-
if (!auth.ok)
|
|
833
|
-
return NextResponse.json({ error: "UNAUTHORIZED" }, { status: 401 });
|
|
834
|
-
|
|
835
|
-
const rel = params.path.map(decodeURIComponent).join("/");
|
|
836
|
-
const abs = safeRootPath(rel);
|
|
837
|
-
if (!abs)
|
|
838
|
-
return NextResponse.json({ error: "INVALID_PATH" }, { status: 400 });
|
|
839
|
-
|
|
840
|
-
const snap = await readSnapshot(getRootDir()!, rel);
|
|
841
|
-
if (!snap)
|
|
842
|
-
return NextResponse.json({ error: "FILE_NOT_FOUND" }, { status: 404 });
|
|
843
|
-
return NextResponse.json(snap);
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
export async function POST(req: Request, { params }) {
|
|
847
|
-
const auth = checkAuth(req);
|
|
848
|
-
if (!auth.ok)
|
|
849
|
-
return NextResponse.json({ error: "UNAUTHORIZED" }, { status: 401 });
|
|
850
|
-
|
|
851
|
-
const idemKey = req.headers.get("idempotency-key");
|
|
852
|
-
if (!idemKey)
|
|
853
|
-
return NextResponse.json(
|
|
854
|
-
{ error: "INVALID_PAYLOAD", message: "Idempotency-Key required" },
|
|
855
|
-
{ status: 400 },
|
|
856
|
-
);
|
|
857
|
-
|
|
858
|
-
const body = await req.json();
|
|
859
|
-
const rel = params.path.map(decodeURIComponent).join("/");
|
|
860
|
-
const payloadHash = sha256(JSON.stringify({ rel, body }));
|
|
861
|
-
|
|
862
|
-
const cached = idempotency.get(idemKey);
|
|
863
|
-
if (cached) {
|
|
864
|
-
if (cached.payloadHash !== payloadHash) {
|
|
865
|
-
return NextResponse.json(
|
|
866
|
-
{ error: "IDEMPOTENCY_KEY_REUSED" },
|
|
867
|
-
{ status: 409 },
|
|
868
|
-
);
|
|
869
|
-
}
|
|
870
|
-
return new NextResponse(cached.body, {
|
|
871
|
-
status: cached.status,
|
|
872
|
-
headers: { "content-type": "application/json" },
|
|
873
|
-
});
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
const result = await applyOps({
|
|
877
|
-
rootDir: getRootDir()!,
|
|
878
|
-
mdPath: rel,
|
|
879
|
-
baseRevision: body.baseRevision,
|
|
880
|
-
by: body.by,
|
|
881
|
-
ops: body.ops,
|
|
882
|
-
});
|
|
883
|
-
|
|
884
|
-
const status = result.ok ? 200 : result.status;
|
|
885
|
-
const respBody = JSON.stringify(
|
|
886
|
-
result.ok
|
|
887
|
-
? result.snapshot
|
|
888
|
-
: {
|
|
889
|
-
error: result.code,
|
|
890
|
-
message: result.message,
|
|
891
|
-
snapshot: result.snapshot,
|
|
892
|
-
},
|
|
893
|
-
);
|
|
894
|
-
idempotency.set(idemKey, { payloadHash, status, body: respBody });
|
|
895
|
-
|
|
896
|
-
return new NextResponse(respBody, {
|
|
897
|
-
status,
|
|
898
|
-
headers: { "content-type": "application/json" },
|
|
899
|
-
});
|
|
900
|
-
}
|
|
901
|
-
```
|
|
902
|
-
|
|
903
|
-
Events route: `src/app/api/agent/events/[...path]/route.ts` — GET polls `sidecar.events.filter(e => e.id > after).slice(0, limit)`; POST bumps `sidecar.lastAck[by] = upToId` and persists. (Route paths restructured for Next 16 catch-all compliance; semantics unchanged.)
|
|
904
|
-
|
|
905
|
-
### 5.8 `src/lib/proof/auth.ts`
|
|
906
|
-
|
|
907
|
-
```ts
|
|
908
|
-
import { readFileSync, existsSync } from "node:fs";
|
|
909
|
-
import path from "node:path";
|
|
910
|
-
import os from "node:os";
|
|
911
|
-
|
|
912
|
-
const TOKEN_PATH = path.join(os.homedir(), ".wiki-viewer", "agent-token");
|
|
913
|
-
|
|
914
|
-
function configuredToken(): string | null {
|
|
915
|
-
if (process.env.AGENT_BEARER_TOKEN) return process.env.AGENT_BEARER_TOKEN;
|
|
916
|
-
try {
|
|
917
|
-
if (existsSync(TOKEN_PATH))
|
|
918
|
-
return readFileSync(TOKEN_PATH, "utf-8").trim() || null;
|
|
919
|
-
} catch {}
|
|
920
|
-
return null;
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
export function checkAuth(
|
|
924
|
-
req: Request,
|
|
925
|
-
): { ok: true; localhost: boolean } | { ok: false } {
|
|
926
|
-
const token = configuredToken();
|
|
927
|
-
|
|
928
|
-
// Inspect client origin. Trust X-Forwarded-For only if explicitly behind a proxy we control.
|
|
929
|
-
// For localhost: check Host header is localhost / 127.0.0.1 / ::1.
|
|
930
|
-
const host = req.headers.get("host") ?? "";
|
|
931
|
-
const isLocal =
|
|
932
|
-
host.startsWith("localhost") ||
|
|
933
|
-
host.startsWith("127.0.0.1") ||
|
|
934
|
-
host.startsWith("[::1]");
|
|
935
|
-
|
|
936
|
-
if (!token) {
|
|
937
|
-
// No token configured. Allow only local requests.
|
|
938
|
-
return isLocal ? { ok: true, localhost: true } : { ok: false };
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
const auth = req.headers.get("authorization") ?? "";
|
|
942
|
-
const presented = auth.startsWith("Bearer ") ? auth.slice(7) : "";
|
|
943
|
-
if (presented && timingSafeEqualStr(presented, token)) {
|
|
944
|
-
return { ok: true, localhost: isLocal };
|
|
945
|
-
}
|
|
946
|
-
return { ok: false };
|
|
947
|
-
}
|
|
948
|
-
```
|
|
949
|
-
|
|
950
|
-
`timingSafeEqualStr`: pad both to equal length, use Node `crypto.timingSafeEqual`.
|
|
951
|
-
|
|
952
|
-
### 5.9 `src/lib/proof/idempotency.ts`
|
|
953
|
-
|
|
954
|
-
```ts
|
|
955
|
-
// Tiny in-memory LRU. Lives for process lifetime. Acceptable: idempotency
|
|
956
|
-
// guards retries within seconds, not days.
|
|
957
|
-
const MAX = 1000;
|
|
958
|
-
const TTL_MS = 5 * 60 * 1000;
|
|
959
|
-
|
|
960
|
-
interface Entry {
|
|
961
|
-
payloadHash: string;
|
|
962
|
-
status: number;
|
|
963
|
-
body: string;
|
|
964
|
-
expiresAt: number;
|
|
965
|
-
}
|
|
966
|
-
const store = new Map<string, Entry>();
|
|
967
|
-
|
|
968
|
-
export const idempotency = {
|
|
969
|
-
get(key: string): Entry | null {
|
|
970
|
-
const e = store.get(key);
|
|
971
|
-
if (!e) return null;
|
|
972
|
-
if (e.expiresAt < Date.now()) {
|
|
973
|
-
store.delete(key);
|
|
974
|
-
return null;
|
|
975
|
-
}
|
|
976
|
-
return e;
|
|
977
|
-
},
|
|
978
|
-
set(key: string, value: Omit<Entry, "expiresAt">): void {
|
|
979
|
-
if (store.size >= MAX) {
|
|
980
|
-
const first = store.keys().next().value;
|
|
981
|
-
if (first) store.delete(first);
|
|
982
|
-
}
|
|
983
|
-
store.set(key, { ...value, expiresAt: Date.now() + TTL_MS });
|
|
984
|
-
},
|
|
985
|
-
};
|
|
986
|
-
```
|
|
987
|
-
|
|
988
|
-
### 5.10 `src/lib/proof/mutex.ts`
|
|
989
|
-
|
|
990
|
-
```ts
|
|
991
|
-
const locks = new Map<string, Promise<void>>();
|
|
992
|
-
|
|
993
|
-
export async function withFileMutex<T>(
|
|
994
|
-
path: string,
|
|
995
|
-
fn: () => Promise<T>,
|
|
996
|
-
): Promise<T> {
|
|
997
|
-
while (locks.has(path)) await locks.get(path);
|
|
998
|
-
let release!: () => void;
|
|
999
|
-
const p = new Promise<void>((r) => {
|
|
1000
|
-
release = r;
|
|
1001
|
-
});
|
|
1002
|
-
locks.set(path, p);
|
|
1003
|
-
try {
|
|
1004
|
-
return await fn();
|
|
1005
|
-
} finally {
|
|
1006
|
-
locks.delete(path);
|
|
1007
|
-
release();
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
```
|
|
1011
|
-
|
|
1012
|
-
### 5.11 Browser side — TipTap mark `src/components/editor/extensions/proof-span.ts`
|
|
1013
|
-
|
|
1014
|
-
```ts
|
|
1015
|
-
import { Mark, mergeAttributes } from "@tiptap/core";
|
|
1016
|
-
|
|
1017
|
-
export const ProofSpan = Mark.create({
|
|
1018
|
-
name: "proofSpan",
|
|
1019
|
-
priority: 900,
|
|
1020
|
-
inclusive: false,
|
|
1021
|
-
keepOnSplit: false,
|
|
1022
|
-
|
|
1023
|
-
addAttributes() {
|
|
1024
|
-
return {
|
|
1025
|
-
spanId: {
|
|
1026
|
-
default: null,
|
|
1027
|
-
parseHTML: (el) => el.getAttribute("id"),
|
|
1028
|
-
renderHTML: (a) => ({ id: a.spanId }),
|
|
1029
|
-
},
|
|
1030
|
-
origin: {
|
|
1031
|
-
default: "ai",
|
|
1032
|
-
parseHTML: (el) => el.getAttribute("origin"),
|
|
1033
|
-
renderHTML: (a) => ({ origin: a.origin }),
|
|
1034
|
-
},
|
|
1035
|
-
basis: {
|
|
1036
|
-
default: null,
|
|
1037
|
-
parseHTML: (el) => el.getAttribute("basis"),
|
|
1038
|
-
renderHTML: (a) => ({ basis: a.basis }),
|
|
1039
|
-
},
|
|
1040
|
-
basisDetail: {
|
|
1041
|
-
default: null,
|
|
1042
|
-
parseHTML: (el) => el.getAttribute("basis-detail"),
|
|
1043
|
-
renderHTML: (a) => ({ "basis-detail": a.basisDetail }),
|
|
1044
|
-
},
|
|
1045
|
-
by: {
|
|
1046
|
-
default: null,
|
|
1047
|
-
parseHTML: (el) => el.getAttribute("by"),
|
|
1048
|
-
renderHTML: (a) => ({ by: a.by }),
|
|
1049
|
-
},
|
|
1050
|
-
at: {
|
|
1051
|
-
default: null,
|
|
1052
|
-
parseHTML: (el) => el.getAttribute("at"),
|
|
1053
|
-
renderHTML: (a) => ({ at: a.at }),
|
|
1054
|
-
},
|
|
1055
|
-
inResponseTo: {
|
|
1056
|
-
default: null,
|
|
1057
|
-
parseHTML: (el) => el.getAttribute("in-response-to"),
|
|
1058
|
-
renderHTML: (a) => ({ "in-response-to": a.inResponseTo }),
|
|
1059
|
-
},
|
|
1060
|
-
};
|
|
1061
|
-
},
|
|
1062
|
-
parseHTML() {
|
|
1063
|
-
return [{ tag: "proof-span" }];
|
|
1064
|
-
},
|
|
1065
|
-
renderHTML({ HTMLAttributes }) {
|
|
1066
|
-
return [
|
|
1067
|
-
"proof-span",
|
|
1068
|
-
mergeAttributes(HTMLAttributes, { class: "proof-span" }),
|
|
1069
|
-
0,
|
|
1070
|
-
];
|
|
1071
|
-
},
|
|
1072
|
-
});
|
|
1073
|
-
```
|
|
1074
|
-
|
|
1075
|
-
CSS in `globals.css`:
|
|
1076
|
-
|
|
1077
|
-
```css
|
|
1078
|
-
proof-span.proof-span,
|
|
1079
|
-
.proof-span {
|
|
1080
|
-
background: linear-gradient(
|
|
1081
|
-
to right,
|
|
1082
|
-
rgba(165, 180, 252, 0.08),
|
|
1083
|
-
rgba(165, 180, 252, 0)
|
|
1084
|
-
);
|
|
1085
|
-
border-left: 2px solid rgb(165, 180, 252);
|
|
1086
|
-
padding-left: 4px;
|
|
1087
|
-
border-radius: 2px;
|
|
1088
|
-
cursor: pointer;
|
|
1089
|
-
transition: background 120ms ease;
|
|
1090
|
-
}
|
|
1091
|
-
proof-span.proof-span[origin="human"] {
|
|
1092
|
-
border-left-color: rgb(110, 231, 183);
|
|
1093
|
-
background: linear-gradient(
|
|
1094
|
-
to right,
|
|
1095
|
-
rgba(110, 231, 183, 0.08),
|
|
1096
|
-
rgba(110, 231, 183, 0)
|
|
1097
|
-
);
|
|
1098
|
-
}
|
|
1099
|
-
proof-span.proof-span:hover {
|
|
1100
|
-
background: linear-gradient(
|
|
1101
|
-
to right,
|
|
1102
|
-
rgba(165, 180, 252, 0.18),
|
|
1103
|
-
rgba(165, 180, 252, 0.04)
|
|
1104
|
-
);
|
|
1105
|
-
}
|
|
1106
|
-
.dark proof-span.proof-span {
|
|
1107
|
-
background: linear-gradient(
|
|
1108
|
-
to right,
|
|
1109
|
-
rgba(165, 180, 252, 0.14),
|
|
1110
|
-
rgba(165, 180, 252, 0)
|
|
1111
|
-
);
|
|
1112
|
-
}
|
|
1113
|
-
```
|
|
1114
|
-
|
|
1115
|
-
### 5.12 Turndown rule — `src/lib/markdown/to-markdown.ts` (EDIT)
|
|
1116
|
-
|
|
1117
|
-
Add a turndown rule preserving `<proof-span>` with attrs (similar to existing `styledSpan` rule):
|
|
1118
|
-
|
|
1119
|
-
```ts
|
|
1120
|
-
turndown.addRule("proofSpan", {
|
|
1121
|
-
filter: (node) => node.nodeName === "PROOF-SPAN",
|
|
1122
|
-
replacement: (content, node) => {
|
|
1123
|
-
const el = node as HTMLElement;
|
|
1124
|
-
const attrs: string[] = [];
|
|
1125
|
-
for (const a of Array.from(el.attributes)) {
|
|
1126
|
-
attrs.push(`${a.name}="${a.value.replace(/"/g, """)}"`);
|
|
1127
|
-
}
|
|
1128
|
-
return `<proof-span${attrs.length ? " " + attrs.join(" ") : ""}>${content}</proof-span>`;
|
|
1129
|
-
},
|
|
1130
|
-
});
|
|
1131
|
-
```
|
|
1132
|
-
|
|
1133
|
-
Also: lowercase `<proof-span>` is preserved through DOM uppercasing (`nodeName === "PROOF-SPAN"` because DOM uppercases). Verify with a test.
|
|
1134
|
-
|
|
1135
|
-
### 5.13 Editor UI
|
|
1136
|
-
|
|
1137
|
-
#### `proof-span-popover.tsx`
|
|
1138
|
-
|
|
1139
|
-
On hover over a `.proof-span` element, show a small floating card:
|
|
1140
|
-
|
|
1141
|
-
```
|
|
1142
|
-
┌────────────────────────────────────┐
|
|
1143
|
-
│ ai:claude · described · 12s ago │
|
|
1144
|
-
│ "user asked for opening paragraph" │
|
|
1145
|
-
│ │
|
|
1146
|
-
│ [Accept] [Revert] [Comment] │
|
|
1147
|
-
└────────────────────────────────────┘
|
|
1148
|
-
```
|
|
1149
|
-
|
|
1150
|
-
- Accept → POST to a special internal endpoint that strips the span from current file (server unwraps and rewrites .md, emits `span.accepted` event)
|
|
1151
|
-
- Revert → same endpoint with action=revert (deletes the wrapped content)
|
|
1152
|
-
- Comment → opens `comment-thread.tsx` in "new comment" mode anchored to the parent block
|
|
1153
|
-
|
|
1154
|
-
Use a dedicated internal route `POST /api/agent/internal/span` (body: `{ path, spanId, action: "accept"|"revert" }`). Only accept localhost requests on this route (it's UI-only, not part of the agent surface).
|
|
1155
|
-
|
|
1156
|
-
#### `comment-pip.tsx` + `comment-thread.tsx`
|
|
1157
|
-
|
|
1158
|
-
Margin pip rendered via a TipTap decoration plugin. Reads `useProofStore` for comments by ref. State pip icon based on:
|
|
1159
|
-
|
|
1160
|
-
- ● dot (filled) — open thread, last turn is AI
|
|
1161
|
-
- ○ ring — open thread, last turn is human
|
|
1162
|
-
- ✓ check — resolved (faded)
|
|
1163
|
-
|
|
1164
|
-
Click pip → opens thread popover anchored to the block. Reply form at bottom. Resolve / Reopen buttons in header.
|
|
1165
|
-
|
|
1166
|
-
#### `suggestion-card.tsx`
|
|
1167
|
-
|
|
1168
|
-
Pending suggestion shown inline above/below its target block, depending on `kind`:
|
|
1169
|
-
|
|
1170
|
-
```
|
|
1171
|
-
┌──────────────────────────────────────────┐
|
|
1172
|
-
│ claude suggests replacing this block ▸ │
|
|
1173
|
-
│ │
|
|
1174
|
-
│ ─ current ────────────────────────────── │
|
|
1175
|
-
│ Ship the rewrite by end of June. │
|
|
1176
|
-
│ │
|
|
1177
|
-
│ ─ proposed ───────────────────────────── │
|
|
1178
|
-
│ Ship the rewrite by July 15. │
|
|
1179
|
-
│ │
|
|
1180
|
-
│ Reason: user mentioned slippage in chat │
|
|
1181
|
-
│ │
|
|
1182
|
-
│ [Accept] [Reject] │
|
|
1183
|
-
└──────────────────────────────────────────┘
|
|
1184
|
-
```
|
|
1185
|
-
|
|
1186
|
-
Renders via a TipTap decoration. Buttons hit `POST /api/agent/files/<path>` with `suggestion.accept` / `suggestion.reject`.
|
|
1187
|
-
|
|
1188
|
-
### 5.14 AI Panel — `src/components/ai-panel/ai-panel.tsx`
|
|
1189
|
-
|
|
1190
|
-
Right-side slide-in drawer (similar layout to existing dialog/popover patterns).
|
|
1191
|
-
|
|
1192
|
-
```
|
|
1193
|
-
┌────────────────────────────────────┐
|
|
1194
|
-
│ Agents ✕ │
|
|
1195
|
-
├────────────────────────────────────┤
|
|
1196
|
-
│ Bridge endpoint │
|
|
1197
|
-
│ http://localhost:3000 │
|
|
1198
|
-
│ [copy curl] │
|
|
1199
|
-
│ │
|
|
1200
|
-
│ Token │
|
|
1201
|
-
│ ••••••••cf12 [show] [copy] │
|
|
1202
|
-
│ [regenerate] │
|
|
1203
|
-
│ │
|
|
1204
|
-
│ Active connections (last 5m) │
|
|
1205
|
-
│ • ai:claude 12 ops / 3 msgs │
|
|
1206
|
-
│ • ai:cursor (idle) │
|
|
1207
|
-
│ │
|
|
1208
|
-
│ Recent activity │
|
|
1209
|
-
│ plan.md │
|
|
1210
|
-
│ claude replaced b7f2c1 12s │
|
|
1211
|
-
│ claude commented on b9c104 2m │
|
|
1212
|
-
│ specs.md │
|
|
1213
|
-
│ claude inserted section 8m │
|
|
1214
|
-
│ │
|
|
1215
|
-
│ Open AGENTS.md guide ↗ │
|
|
1216
|
-
└────────────────────────────────────┘
|
|
1217
|
-
```
|
|
1218
|
-
|
|
1219
|
-
Data:
|
|
1220
|
-
|
|
1221
|
-
- Connections: derived from sidecar event timestamps in the last 5 minutes, grouped by `by`.
|
|
1222
|
-
- Activity: paginated event tail across all sidecars (scan `.proof/`).
|
|
1223
|
-
- Token: pulled from `/api/agent/settings`.
|
|
1224
|
-
|
|
1225
|
-
This is read-only v1. No chat UI. Wire `useAIPanelStore` to actually drive panel open/close.
|
|
1226
|
-
|
|
1227
|
-
### 5.15 Stores — `src/stores/proof-store.ts`
|
|
1228
|
-
|
|
1229
|
-
```ts
|
|
1230
|
-
interface ProofState {
|
|
1231
|
-
byPath: Record<
|
|
1232
|
-
string,
|
|
1233
|
-
{
|
|
1234
|
-
sidecar: Sidecar | null;
|
|
1235
|
-
snapshotRevision: number;
|
|
1236
|
-
lastEventId: number;
|
|
1237
|
-
}
|
|
1238
|
-
>;
|
|
1239
|
-
loadSidecar(path: string): Promise<void>;
|
|
1240
|
-
applyEvent(path: string, e: ProofEvent): void;
|
|
1241
|
-
// For the editor, on focus / file open / SSE notification, refetch sidecar.
|
|
1242
|
-
}
|
|
1243
|
-
```
|
|
1244
|
-
|
|
1245
|
-
The chokidar SSE feed (existing) already broadcasts file changes. Subscribe in the editor: when our file changes, refetch sidecar via `GET /api/agent/sidecar/<path>`.
|
|
1246
|
-
|
|
1247
|
-
### 5.16 Settings UI
|
|
1248
|
-
|
|
1249
|
-
Minimal addition to existing settings (if any) or a new modal:
|
|
1250
|
-
|
|
1251
|
-
- Toggle: Allow remote agents (controls bind-to-host but most users will just `--host 0.0.0.0` from CLI)
|
|
1252
|
-
- Token display + regenerate
|
|
1253
|
-
- Rate limit (default 60 ops/min per file)
|
|
1254
|
-
- List of recent agent identities seen (purely informational, derived from event log)
|
|
1255
|
-
|
|
1256
|
-
---
|
|
1257
|
-
|
|
1258
|
-
## 6. Edge cases and contract details
|
|
1259
|
-
|
|
1260
|
-
### 6.1 External edits (user edits in vim)
|
|
1261
|
-
|
|
1262
|
-
Detected via fingerprint mismatch on next mutation OR via chokidar (already running). When detected:
|
|
1263
|
-
|
|
1264
|
-
1. Re-parse blocks
|
|
1265
|
-
2. Re-assign refs (preserving as many as possible via §5.4)
|
|
1266
|
-
3. Bump revision (counts as a system mutation)
|
|
1267
|
-
4. Emit `file.externallyEdited` event
|
|
1268
|
-
5. Persist sidecar
|
|
1269
|
-
6. For any block that was deleted by the external edit, related comments/suggestions stay with their now-orphan ref. They are still returned in snapshots with `"ref": "<orphan>"`. UI shows them in an "orphaned" panel.
|
|
1270
|
-
|
|
1271
|
-
### 6.2 Concurrent mutations
|
|
1272
|
-
|
|
1273
|
-
The per-file mutex (§5.10) serializes mutations. The revision check (§4.5 `STALE_REVISION`) catches the case where two agents both held revision N and one beat the other.
|
|
1274
|
-
|
|
1275
|
-
### 6.3 Rate limiting
|
|
1276
|
-
|
|
1277
|
-
In-memory token bucket per `by` value:
|
|
1278
|
-
|
|
1279
|
-
- Bucket size 60, refill 1 op/sec
|
|
1280
|
-
- Apply to mutation ops only (block._ + comment._ + suggestion.\*), not snapshot GETs / event polls
|
|
1281
|
-
- Hit limit → 429 with `Retry-After: <seconds>` header
|
|
1282
|
-
|
|
1283
|
-
### 6.4 Sidecar size management
|
|
1284
|
-
|
|
1285
|
-
Events grow unbounded. Trim policy:
|
|
1286
|
-
|
|
1287
|
-
- Keep last 1000 events per sidecar
|
|
1288
|
-
- When trimming, retain ALL events newer than the oldest `lastAck.*` cursor, even if it exceeds 1000
|
|
1289
|
-
- Trim on every 100th mutation to amortize
|
|
1290
|
-
|
|
1291
|
-
### 6.5 Path traversal
|
|
1292
|
-
|
|
1293
|
-
Use existing `safeRootPath` from `src/lib/root-dir.ts`. Reject any path resolving to a directory above root. Reject `.proof` itself as a content path (reserved).
|
|
1294
|
-
|
|
1295
|
-
### 6.6 Non-markdown files
|
|
1296
|
-
|
|
1297
|
-
Reject mutations on non-`.md` files. Snapshot also rejects. Path must end in `.md` or `.markdown`.
|
|
1298
|
-
|
|
1299
|
-
### 6.7 Span IDs survive edits
|
|
1300
|
-
|
|
1301
|
-
When `block.replace` is applied:
|
|
1302
|
-
|
|
1303
|
-
- Existing `<proof-span>` elements inside the old block content that are NOT changed should retain their IDs
|
|
1304
|
-
- New content gets a fresh span if the op's `by` is ai
|
|
1305
|
-
- This is approximate. If the agent passes wholesale-replaced markdown, the old spans are gone. That's fine.
|
|
1306
|
-
|
|
1307
|
-
### 6.8 Suggestion conflicts
|
|
1308
|
-
|
|
1309
|
-
If two pending suggestions target the same ref, both stay pending. Accepting one does NOT auto-reject the other — but accepting one bumps the revision and may invalidate the other's ref via aliasing. On accept of #1, #2 stays pending but its ref might be aliased. UI must handle: surface `suggestion.ref-stale` state.
|
|
1310
|
-
|
|
1311
|
-
Keep it simple: when applying `suggestion.accept`, also iterate other pending suggestions with same ref and mark them as `status: rejected` with `resolvedBy: "system"` and event `suggestion.rejected` (reason: superseded). Document this in code comments.
|
|
1312
|
-
|
|
1313
|
-
### 6.9 Markdown roundtrip fidelity
|
|
1314
|
-
|
|
1315
|
-
Wiki-viewer has a non-trivial roundtrip (turndown + custom rules for wiki-links, embeds, lucide icons, etc.). The agent protocol bypasses that: it operates directly on the raw markdown file, not on the TipTap HTML. The block parser uses remark only. **This means: the agent never sees the editor's HTML.** It sees raw markdown with raw `<proof-span>` tags.
|
|
1316
|
-
|
|
1317
|
-
When the editor saves a file (via existing PUT `/api/wiki/content`), the turndown rule (§5.12) ensures `<proof-span>` is preserved. Verify with an end-to-end test.
|
|
1318
|
-
|
|
1319
|
-
### 6.10 Wiki-links and proof-spans
|
|
1320
|
-
|
|
1321
|
-
`[[slug]]` syntax inside a proof-span is fine. Remark parses link before HTML wrapping; turndown emits wiki-link rule before proof-span rule (because turndown processes children first). Verify.
|
|
1322
|
-
|
|
1323
|
-
### 6.11 Empty file / new file
|
|
1324
|
-
|
|
1325
|
-
`GET` on missing file → 404. Agent cannot create files through this protocol (that's a separate concern). If you want create-via-agent: extend later. Not in scope.
|
|
1326
|
-
|
|
1327
|
-
### 6.12 Hot reload during dev
|
|
1328
|
-
|
|
1329
|
-
When source changes trigger Next reload, in-memory idempotency cache and mutex map are wiped. Acceptable. Disk state (sidecars) survives. Tests should not depend on cache.
|
|
1330
|
-
|
|
1331
|
-
---
|
|
1332
|
-
|
|
1333
|
-
## 7. Tests
|
|
1334
|
-
|
|
1335
|
-
Minimum suite (place in `src/tests/proof/`). No test framework dependency required if `package.json` doesn't already have one — use `node --test` (built-in since Node 18) and `node:assert`.
|
|
1336
|
-
|
|
1337
|
-
### 7.1 Unit: blocks.test.ts
|
|
1338
|
-
|
|
1339
|
-
- parse → stringify roundtrip preserves `<proof-span>` exactly
|
|
1340
|
-
- list with checkboxes detected as `taskList`
|
|
1341
|
-
- code block with lang preserved
|
|
1342
|
-
- table preserved
|
|
1343
|
-
|
|
1344
|
-
### 7.2 Unit: block-refs.test.ts
|
|
1345
|
-
|
|
1346
|
-
- First parse assigns refs deterministically
|
|
1347
|
-
- Re-parse with same content reuses refs
|
|
1348
|
-
- Edit one block, others keep refs
|
|
1349
|
-
- Insert a block in middle, others keep refs
|
|
1350
|
-
- Aliases populated after replace, cleared after next mutation
|
|
1351
|
-
|
|
1352
|
-
### 7.3 Unit: proof-span.test.ts
|
|
1353
|
-
|
|
1354
|
-
- wrapAsProofSpan on paragraph wraps text content
|
|
1355
|
-
- wrapAsProofSpan on heading wraps text after `#`s
|
|
1356
|
-
- wrapAsProofSpan on code block returns content unchanged + records in sidecar
|
|
1357
|
-
- Special chars in basisDetail escaped properly
|
|
1358
|
-
|
|
1359
|
-
### 7.4 Integration: ops-applier.test.ts
|
|
1360
|
-
|
|
1361
|
-
For each op type:
|
|
1362
|
-
|
|
1363
|
-
- Happy path: apply, verify resulting markdown + sidecar
|
|
1364
|
-
- STALE_REVISION returned when baseRevision wrong
|
|
1365
|
-
- BLOCK_NOT_FOUND returned for stale ref
|
|
1366
|
-
- Idempotency: same key returns cached response
|
|
1367
|
-
|
|
1368
|
-
### 7.5 Integration: routes.test.ts
|
|
1369
|
-
|
|
1370
|
-
Use `next` dev server in a temp ROOT_DIR. Hit routes with fetch.
|
|
1371
|
-
|
|
1372
|
-
- GET snapshot of a real .md file
|
|
1373
|
-
- POST insertAfter → verify file content changed
|
|
1374
|
-
- Poll events → see new event
|
|
1375
|
-
- Ack events → cursor advances
|
|
1376
|
-
|
|
1377
|
-
### 7.6 Roundtrip: editor-save.test.ts
|
|
1378
|
-
|
|
1379
|
-
- Create .md with `<proof-span>` inline
|
|
1380
|
-
- Load through `markdownToHtml` → TipTap state
|
|
1381
|
-
- Serialize back through `htmlToMarkdown`
|
|
1382
|
-
- Verify the `<proof-span>` is byte-identical
|
|
1383
|
-
|
|
1384
|
-
Critical: this test catches the silent failure where Tiptap mangles the mark on save.
|
|
1385
|
-
|
|
1386
|
-
---
|
|
1387
|
-
|
|
1388
|
-
## 8. Implementation order
|
|
1389
|
-
|
|
1390
|
-
Sub-agent that picks this up: execute phases in order. Do not skip ahead. Each phase ends with a working green test set.
|
|
1391
|
-
|
|
1392
|
-
### Phase A — Foundation (no UI yet)
|
|
1393
|
-
|
|
1394
|
-
1. Create `src/lib/proof/types.ts`
|
|
1395
|
-
2. Create `src/lib/proof/blocks.ts`, get tests green
|
|
1396
|
-
3. Create `src/lib/proof/block-refs.ts`, get tests green
|
|
1397
|
-
4. Create `src/lib/proof/proof-span.ts`, get tests green
|
|
1398
|
-
5. Create `src/lib/proof/sidecar.ts`
|
|
1399
|
-
6. Create `src/lib/proof/mutex.ts`, `idempotency.ts`, `auth.ts`
|
|
1400
|
-
7. Create `src/lib/proof/ops-applier.ts`, get tests green
|
|
1401
|
-
8. Add `<proof-span>` turndown rule in `src/lib/markdown/to-markdown.ts`
|
|
1402
|
-
|
|
1403
|
-
**Acceptance:** All Phase A tests pass. No HTTP yet. No UI yet.
|
|
1404
|
-
|
|
1405
|
-
### Phase B — HTTP surface
|
|
1406
|
-
|
|
1407
|
-
1. Create `src/app/api/agent/files/[...path]/route.ts` (GET + POST)
|
|
1408
|
-
2. Create `src/app/api/agent/events/[...path]/route.ts` (GET=poll, POST=ack)
|
|
1409
|
-
3. Create `src/app/api/agent/sidecar/[...path]/route.ts`
|
|
1410
|
-
4. Create `src/app/api/agent/settings/route.ts` + `settings/token/regenerate/route.ts`
|
|
1411
|
-
5. Create `src/app/api/agent/internal/span/route.ts` (accept/revert)
|
|
1412
|
-
|
|
1413
|
-
**Acceptance:** Integration tests pass. `curl` workflow from §10 works.
|
|
1414
|
-
|
|
1415
|
-
### Phase C — Editor wiring
|
|
1416
|
-
|
|
1417
|
-
1. Create `src/components/editor/extensions/proof-span.ts` TipTap mark
|
|
1418
|
-
2. Register in `extensions.ts`
|
|
1419
|
-
3. Add CSS in `globals.css`
|
|
1420
|
-
4. Create `src/stores/proof-store.ts`
|
|
1421
|
-
5. Update editor to load sidecar on file open, subscribe to chokidar SSE for sidecar refresh
|
|
1422
|
-
6. Create `proof-span-popover.tsx` (Accept / Revert / Comment)
|
|
1423
|
-
|
|
1424
|
-
**Acceptance:** Load a .md with `<proof-span>` inline → see decoration. Hover → popover works. Accept removes mark.
|
|
1425
|
-
|
|
1426
|
-
### Phase D — Comments
|
|
1427
|
-
|
|
1428
|
-
1. Create comment pip decoration plugin
|
|
1429
|
-
2. Create `comment-thread.tsx`
|
|
1430
|
-
3. Create comment composer (for human-initiated comments)
|
|
1431
|
-
4. Wire to `POST /api/agent/files/<path>` with `comment.add` / `comment.reply` / `comment.resolve`
|
|
1432
|
-
|
|
1433
|
-
**Acceptance:** Human can create a comment on a block. Agent reply (simulated via curl) appears. Resolve works.
|
|
1434
|
-
|
|
1435
|
-
### Phase E — Suggestions
|
|
1436
|
-
|
|
1437
|
-
1. Create `suggestion-card.tsx` decoration
|
|
1438
|
-
2. Wire Accept / Reject buttons
|
|
1439
|
-
|
|
1440
|
-
**Acceptance:** Agent POSTs `suggestion.add`. Card appears inline. Accept → block content changes, card disappears, event emitted.
|
|
1441
|
-
|
|
1442
|
-
### Phase F — AI Panel
|
|
1443
|
-
|
|
1444
|
-
1. Create `ai-panel.tsx` + sub-components
|
|
1445
|
-
2. Update `ai-panel-store.ts` (remove stub, wire to data)
|
|
1446
|
-
3. Add panel trigger button to layout header
|
|
1447
|
-
4. Build activity aggregator (scans `.proof/` for recent events)
|
|
1448
|
-
|
|
1449
|
-
**Acceptance:** Panel opens, shows token, shows connections, shows recent activity. Copy-curl button works.
|
|
1450
|
-
|
|
1451
|
-
### Phase G — Polish
|
|
1452
|
-
|
|
1453
|
-
1. Rate limiting in middleware
|
|
1454
|
-
2. Sidecar size trimming
|
|
1455
|
-
3. Settings UI
|
|
1456
|
-
4. README updates: agent protocol section
|
|
1457
|
-
5. Manual smoke test of full flow (see §10)
|
|
1458
|
-
|
|
1459
|
-
**Acceptance:** §10 smoke test passes end-to-end.
|
|
1460
|
-
|
|
1461
|
-
---
|
|
1462
|
-
|
|
1463
|
-
## 9. Code style guardrails
|
|
1464
|
-
|
|
1465
|
-
- TypeScript strict mode. No `any` except at boundaries with non-typed deps.
|
|
1466
|
-
- Tabs for indentation (project convention — see existing files).
|
|
1467
|
-
- Functions over classes. No inheritance hierarchies.
|
|
1468
|
-
- Async/await, never raw `.then`.
|
|
1469
|
-
- Error responses always `{ error: "CODE", message?: string }`. Never bare strings.
|
|
1470
|
-
- All routes mark `export const runtime = "nodejs"` (filesystem access).
|
|
1471
|
-
- No console.log in committed code. Use a logger or remove.
|
|
1472
|
-
- No `// TODO` / `// FIXME` in committed code. File issues instead.
|
|
1473
|
-
- No em-dashes in any user-facing copy or docs. Use commas / sentence restructuring (per user convention).
|
|
1474
|
-
- No AI / agent attribution in commit messages or PR descriptions (per user convention).
|
|
1475
|
-
- Test files mirror source layout under `src/tests/proof/`.
|
|
1476
|
-
|
|
1477
|
-
---
|
|
1478
|
-
|
|
1479
|
-
## 10. Smoke test (manual)
|
|
1480
|
-
|
|
1481
|
-
Run after Phase G. This is the acceptance ritual.
|
|
1482
|
-
|
|
1483
|
-
```bash
|
|
1484
|
-
# 0. setup
|
|
1485
|
-
export ROOT_DIR=/tmp/wiki-smoke
|
|
1486
|
-
mkdir -p $ROOT_DIR
|
|
1487
|
-
echo "# Plan\n\nShip the rewrite by June." > $ROOT_DIR/plan.md
|
|
1488
|
-
pnpm dev
|
|
1489
|
-
|
|
1490
|
-
# 1. snapshot (no token, localhost)
|
|
1491
|
-
curl -s http://localhost:3000/api/agent/files/plan.md | jq
|
|
1492
|
-
|
|
1493
|
-
# Expect: blocks[] with two refs, revision: 0, comments: [], suggestions: []
|
|
1494
|
-
|
|
1495
|
-
# 2. agent inserts a paragraph
|
|
1496
|
-
REF=$(curl -s http://localhost:3000/api/agent/files/plan.md | jq -r '.blocks[1].ref')
|
|
1497
|
-
|
|
1498
|
-
curl -s -X POST http://localhost:3000/api/agent/files/plan.md \
|
|
1499
|
-
-H "Content-Type: application/json" \
|
|
1500
|
-
-H "Idempotency-Key: $(uuidgen)" \
|
|
1501
|
-
-d "{
|
|
1502
|
-
\"baseRevision\": 0,
|
|
1503
|
-
\"by\": \"ai:claude\",
|
|
1504
|
-
\"ops\": [
|
|
1505
|
-
{ \"type\": \"block.insertAfter\", \"ref\": \"$REF\",
|
|
1506
|
-
\"markdown\": \"The team will focus on three pillars: infra, tooling, launch.\",
|
|
1507
|
-
\"basis\": \"described\",
|
|
1508
|
-
\"basisDetail\": \"user asked for opening\" }
|
|
1509
|
-
]
|
|
1510
|
-
}" | jq
|
|
1511
|
-
|
|
1512
|
-
# Expect: revision: 1, new block in snapshot, file on disk has <proof-span> around inserted text
|
|
1513
|
-
|
|
1514
|
-
cat $ROOT_DIR/plan.md
|
|
1515
|
-
# Expect:
|
|
1516
|
-
# # Plan
|
|
1517
|
-
#
|
|
1518
|
-
# Ship the rewrite by June.
|
|
1519
|
-
#
|
|
1520
|
-
# <proof-span id="p..." origin="ai" basis="described" by="ai:claude" at="..." basis-detail="user asked for opening">The team will focus on three pillars: infra, tooling, launch.</proof-span>
|
|
1521
|
-
|
|
1522
|
-
# 3. browser: open http://localhost:3000, navigate to plan.md
|
|
1523
|
-
# expect: lavender left-border decoration on the inserted paragraph
|
|
1524
|
-
# hover → popover shows "ai:claude · described · just now" + Accept/Revert/Comment
|
|
1525
|
-
|
|
1526
|
-
# 4. human comment via UI (right-click block → Comment)
|
|
1527
|
-
# type: "Are launch and tooling overlapping?"
|
|
1528
|
-
|
|
1529
|
-
# 5. agent polls events
|
|
1530
|
-
curl -s http://localhost:3000/api/agent/events/plan.md?after=0 | jq
|
|
1531
|
-
# Expect: events array with the comment.added event
|
|
1532
|
-
|
|
1533
|
-
# 6. agent replies
|
|
1534
|
-
COMMENT_ID=<from event>
|
|
1535
|
-
curl -s -X POST http://localhost:3000/api/agent/files/plan.md \
|
|
1536
|
-
-H "Content-Type: application/json" \
|
|
1537
|
-
-H "Idempotency-Key: $(uuidgen)" \
|
|
1538
|
-
-d "{ \"baseRevision\": 1, \"by\": \"ai:claude\",
|
|
1539
|
-
\"ops\": [{ \"type\": \"comment.reply\", \"commentId\": \"$COMMENT_ID\", \"text\": \"No, tooling is internal-only.\" }] }"
|
|
1540
|
-
|
|
1541
|
-
# 7. browser: open thread on the block, see agent's reply
|
|
1542
|
-
|
|
1543
|
-
# 8. agent proposes a suggestion
|
|
1544
|
-
curl -s -X POST http://localhost:3000/api/agent/files/plan.md \
|
|
1545
|
-
-H "Content-Type: application/json" \
|
|
1546
|
-
-H "Idempotency-Key: $(uuidgen)" \
|
|
1547
|
-
-d "{ \"baseRevision\": 2, \"by\": \"ai:claude\",
|
|
1548
|
-
\"ops\": [{ \"type\": \"suggestion.add\", \"ref\": \"$REF\",
|
|
1549
|
-
\"kind\": \"replace\",
|
|
1550
|
-
\"markdown\": \"Ship the rewrite by July 15.\",
|
|
1551
|
-
\"basis\": \"described\",
|
|
1552
|
-
\"basisDetail\": \"deadline slipped\" }] }"
|
|
1553
|
-
|
|
1554
|
-
# 9. browser: see inline suggestion card. Click Accept.
|
|
1555
|
-
# expect: block content changes, card disappears, file on disk updated
|
|
1556
|
-
|
|
1557
|
-
# 10. token flow
|
|
1558
|
-
# stop server, set AGENT_BEARER_TOKEN=test123, restart
|
|
1559
|
-
# curl without token → 401
|
|
1560
|
-
# curl with -H "Authorization: Bearer test123" → 200
|
|
1561
|
-
|
|
1562
|
-
# 11. external edit
|
|
1563
|
-
# while server running:
|
|
1564
|
-
# echo "appended line" >> $ROOT_DIR/plan.md
|
|
1565
|
-
# immediately:
|
|
1566
|
-
# curl /api/agent/events/plan.md?after=<last>
|
|
1567
|
-
# expect: file.externallyEdited event
|
|
1568
|
-
```
|
|
1569
|
-
|
|
1570
|
-
If any step fails, fix before declaring done.
|
|
1571
|
-
|
|
1572
|
-
---
|
|
1573
|
-
|
|
1574
|
-
## 11. What to ask the user before starting
|
|
1575
|
-
|
|
1576
|
-
Before implementing, agent should re-confirm only these specifics:
|
|
1577
|
-
|
|
1578
|
-
1. The bearer-token storage path `~/.wiki-viewer/agent-token` is fine, OR prefer env-only?
|
|
1579
|
-
2. The localhost-without-token default OK, OR token always required?
|
|
1580
|
-
3. Rate limit default of 60 ops/min/agent OK, OR different?
|
|
1581
|
-
|
|
1582
|
-
Everything else: follow this plan as-written. Do not deviate from the API shape, op vocabulary, error codes, or sidecar schema. If a real blocker emerges during implementation, surface it as a question with concrete options rather than improvising.
|
|
1583
|
-
|
|
1584
|
-
---
|
|
1585
|
-
|
|
1586
|
-
## 12. Out of scope (do not build)
|
|
1587
|
-
|
|
1588
|
-
To prevent scope creep, the following are **explicitly out** even if they seem like natural additions:
|
|
1589
|
-
|
|
1590
|
-
- File create / rename / delete via agent protocol (use the existing `/api/wiki/*` for that)
|
|
1591
|
-
- Multi-file ops (one file per request, period)
|
|
1592
|
-
- Diff visualizer beyond the suggestion card's two-pane view
|
|
1593
|
-
- @-mentions, agent-to-agent messaging
|
|
1594
|
-
- Webhooks pushing to agent URLs (agents poll)
|
|
1595
|
-
- Realtime cursors / presence indicators per agent in the editor body
|
|
1596
|
-
- Persisting idempotency across server restarts
|
|
1597
|
-
- Auth scopes beyond all-or-nothing
|
|
1598
|
-
- Per-file ACLs
|
|
1599
|
-
- Export / import of sidecars
|
|
1600
|
-
- Migrations from / to Proof's hosted format
|
|
1601
|
-
|
|
1602
|
-
All of these can be added later as separate features without conflicting with this design.
|
|
1603
|
-
|
|
1604
|
-
---
|
|
1605
|
-
|
|
1606
|
-
## 13. Definition of done
|
|
1607
|
-
|
|
1608
|
-
- All phases A–G complete
|
|
1609
|
-
- All tests in §7 green
|
|
1610
|
-
- §10 smoke test passes end-to-end
|
|
1611
|
-
- README updated with "Working with agents" section pointing at the agent protocol
|
|
1612
|
-
- No console.logs, no TODOs, no commented-out code shipped
|
|
1613
|
-
- Manual review by a `reviewer` agent run via `/feature` chain
|
|
1614
|
-
|
|
1615
|
-
When done: notify the user, paste the final smoke test output as evidence.
|