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,973 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Phase 2 — Tier-1 Raw FS API tests.
|
|
3
|
-
*
|
|
4
|
-
* Covers: GET bytes/ETag/Range, PUT create/overwrite/If-Match, create-collision,
|
|
5
|
-
* .md PUT triggers file.rawWritten+reconcile, DELETE scope+If-Match, ls scope-filter/limits,
|
|
6
|
-
* move+sidecar, search grep/glob+binary-skip+scope, traversal+symlink-escape rejection.
|
|
7
|
-
*/
|
|
8
|
-
import { test, before, after } from "node:test";
|
|
9
|
-
import assert from "node:assert/strict";
|
|
10
|
-
import {
|
|
11
|
-
mkdtemp, rm, writeFile, readFile, mkdir, symlink,
|
|
12
|
-
} from "node:fs/promises";
|
|
13
|
-
import { tmpdir } from "node:os";
|
|
14
|
-
import path from "node:path";
|
|
15
|
-
import { createHash } from "node:crypto";
|
|
16
|
-
import { randomBytes } from "node:crypto";
|
|
17
|
-
|
|
18
|
-
import { setRootDir } from "../../lib/root-dir.js";
|
|
19
|
-
import { ensureRegistry, addAgent, hashToken } from "../../lib/proof/registry.js";
|
|
20
|
-
import { writeSidecar, readSidecar, emptySidecar } from "../../lib/proof/sidecar.js";
|
|
21
|
-
import { _resetAuditDb } from "../../lib/proof/audit.js";
|
|
22
|
-
|
|
23
|
-
// Route handlers (loaded after env is set)
|
|
24
|
-
let fileGET: (req: Request, ctx: { params: Promise<{ path: string[] }> }) => Promise<Response>;
|
|
25
|
-
let filePUT: (req: Request, ctx: { params: Promise<{ path: string[] }> }) => Promise<Response>;
|
|
26
|
-
let filePATCH: (req: Request, ctx: { params: Promise<{ path: string[] }> }) => Promise<Response>;
|
|
27
|
-
let fileDELETE: (req: Request, ctx: { params: Promise<{ path: string[] }> }) => Promise<Response>;
|
|
28
|
-
let lsGET: (req: Request, ctx: { params: Promise<{ path?: string[] }> }) => Promise<Response>;
|
|
29
|
-
let movePOST: (req: Request) => Promise<Response>;
|
|
30
|
-
let searchPOST: (req: Request) => Promise<Response>;
|
|
31
|
-
|
|
32
|
-
let tmpHome: string;
|
|
33
|
-
let tmpRoot: string;
|
|
34
|
-
|
|
35
|
-
// Tokens
|
|
36
|
-
let READ_TOKEN: string;
|
|
37
|
-
let MUTATE_TOKEN: string;
|
|
38
|
-
let DELETE_TOKEN: string;
|
|
39
|
-
let RESTRICTED_TOKEN: string;
|
|
40
|
-
|
|
41
|
-
function sha256(buf: Buffer): string {
|
|
42
|
-
return "sha256:" + createHash("sha256").update(buf).digest("hex");
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function agentHeaders(token: string, id: string): Record<string, string> {
|
|
46
|
-
return {
|
|
47
|
-
Authorization: `Bearer ${token}`,
|
|
48
|
-
"X-Agent-Id": id,
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
before(async () => {
|
|
53
|
-
tmpHome = await mkdtemp(path.join(tmpdir(), "agent-fs-home-"));
|
|
54
|
-
tmpRoot = await mkdtemp(path.join(tmpdir(), "agent-fs-root-"));
|
|
55
|
-
|
|
56
|
-
process.env.HOME = tmpHome;
|
|
57
|
-
setRootDir(tmpRoot);
|
|
58
|
-
_resetAuditDb();
|
|
59
|
-
|
|
60
|
-
await ensureRegistry();
|
|
61
|
-
|
|
62
|
-
READ_TOKEN = randomBytes(32).toString("hex");
|
|
63
|
-
MUTATE_TOKEN = randomBytes(32).toString("hex");
|
|
64
|
-
DELETE_TOKEN = randomBytes(32).toString("hex");
|
|
65
|
-
RESTRICTED_TOKEN = randomBytes(32).toString("hex");
|
|
66
|
-
|
|
67
|
-
await addAgent({
|
|
68
|
-
id: "ai:read-agent",
|
|
69
|
-
displayName: "Read Agent",
|
|
70
|
-
tokenHash: hashToken(READ_TOKEN),
|
|
71
|
-
scope: { paths: ["**/*"], ops: ["read"] },
|
|
72
|
-
createdAt: new Date().toISOString(),
|
|
73
|
-
lastSeen: new Date().toISOString(),
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
await addAgent({
|
|
77
|
-
id: "ai:mutate-agent",
|
|
78
|
-
displayName: "Mutate Agent",
|
|
79
|
-
tokenHash: hashToken(MUTATE_TOKEN),
|
|
80
|
-
scope: { paths: ["**/*"], ops: ["read", "mutate"] },
|
|
81
|
-
createdAt: new Date().toISOString(),
|
|
82
|
-
lastSeen: new Date().toISOString(),
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
await addAgent({
|
|
86
|
-
id: "ai:delete-agent",
|
|
87
|
-
displayName: "Delete Agent",
|
|
88
|
-
tokenHash: hashToken(DELETE_TOKEN),
|
|
89
|
-
scope: { paths: ["**/*"], ops: ["read", "mutate", "delete"] },
|
|
90
|
-
createdAt: new Date().toISOString(),
|
|
91
|
-
lastSeen: new Date().toISOString(),
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
await addAgent({
|
|
95
|
-
id: "ai:restricted",
|
|
96
|
-
displayName: "Restricted",
|
|
97
|
-
tokenHash: hashToken(RESTRICTED_TOKEN),
|
|
98
|
-
scope: { paths: ["allowed/**"], ops: ["read", "mutate", "delete"] },
|
|
99
|
-
createdAt: new Date().toISOString(),
|
|
100
|
-
lastSeen: new Date().toISOString(),
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
// Load route handlers after env is set
|
|
104
|
-
const fileRoute = await import("../../app/api/agent/fs/file/[...path]/route.js");
|
|
105
|
-
fileGET = fileRoute.GET;
|
|
106
|
-
filePUT = fileRoute.PUT;
|
|
107
|
-
filePATCH = fileRoute.PATCH;
|
|
108
|
-
fileDELETE = fileRoute.DELETE;
|
|
109
|
-
|
|
110
|
-
const lsRoute = await import("../../app/api/agent/fs/ls/[[...path]]/route.js");
|
|
111
|
-
lsGET = lsRoute.GET;
|
|
112
|
-
|
|
113
|
-
const moveRoute = await import("../../app/api/agent/fs/move/route.js");
|
|
114
|
-
movePOST = moveRoute.POST;
|
|
115
|
-
|
|
116
|
-
const searchRoute = await import("../../app/api/agent/fs/search/route.js");
|
|
117
|
-
searchPOST = searchRoute.POST;
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
after(async () => {
|
|
121
|
-
await rm(tmpHome, { recursive: true, force: true });
|
|
122
|
-
await rm(tmpRoot, { recursive: true, force: true });
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
126
|
-
|
|
127
|
-
function makeCtx(segments: string[]): { params: Promise<{ path: string[] }> } {
|
|
128
|
-
return { params: Promise.resolve({ path: segments }) };
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function makeLsCtx(segments?: string[]): { params: Promise<{ path?: string[] }> } {
|
|
132
|
-
return { params: Promise.resolve({ path: segments }) };
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function fileUrl(rel: string, qs = ""): string {
|
|
136
|
-
return `http://localhost/api/agent/fs/file/${rel}${qs}`;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function lsUrl(rel: string, qs = ""): string {
|
|
140
|
-
return `http://localhost/api/agent/fs/ls/${rel}${qs}`;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// ── GET tests ────────────────────────────────────────────────────────────────
|
|
144
|
-
|
|
145
|
-
test("GET: returns raw bytes with ETag, X-File-Size, X-File-Mtime, Content-Type", async () => {
|
|
146
|
-
const content = Buffer.from("hello world");
|
|
147
|
-
await writeFile(path.join(tmpRoot, "hello.txt"), content);
|
|
148
|
-
|
|
149
|
-
const req = new Request(fileUrl("hello.txt"), {
|
|
150
|
-
headers: agentHeaders(READ_TOKEN, "ai:read-agent"),
|
|
151
|
-
});
|
|
152
|
-
const res = await fileGET(req, makeCtx(["hello.txt"]));
|
|
153
|
-
assert.equal(res.status, 200);
|
|
154
|
-
|
|
155
|
-
const body = Buffer.from(await res.arrayBuffer());
|
|
156
|
-
assert.deepEqual(body, content);
|
|
157
|
-
|
|
158
|
-
const etag = res.headers.get("etag");
|
|
159
|
-
assert.ok(etag, "ETag header present");
|
|
160
|
-
assert.ok(etag!.includes(createHash("sha256").update(content).digest("hex")), "ETag contains sha256 hex");
|
|
161
|
-
|
|
162
|
-
assert.equal(res.headers.get("x-file-size"), String(content.length));
|
|
163
|
-
assert.ok(res.headers.get("x-file-mtime"), "X-File-Mtime present");
|
|
164
|
-
assert.ok(res.headers.get("content-type")?.includes("text/plain"), "Content-Type text/plain for .txt");
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
test("GET: 404 for missing file", async () => {
|
|
168
|
-
const req = new Request(fileUrl("missing.txt"), {
|
|
169
|
-
headers: agentHeaders(READ_TOKEN, "ai:read-agent"),
|
|
170
|
-
});
|
|
171
|
-
const res = await fileGET(req, makeCtx(["missing.txt"]));
|
|
172
|
-
assert.equal(res.status, 404);
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
test("GET: supports HTTP Range", async () => {
|
|
176
|
-
const content = Buffer.from("0123456789");
|
|
177
|
-
await writeFile(path.join(tmpRoot, "range.txt"), content);
|
|
178
|
-
|
|
179
|
-
const req = new Request(fileUrl("range.txt"), {
|
|
180
|
-
headers: {
|
|
181
|
-
...agentHeaders(READ_TOKEN, "ai:read-agent"),
|
|
182
|
-
Range: "bytes=2-5",
|
|
183
|
-
},
|
|
184
|
-
});
|
|
185
|
-
const res = await fileGET(req, makeCtx(["range.txt"]));
|
|
186
|
-
assert.equal(res.status, 206);
|
|
187
|
-
|
|
188
|
-
const slice = Buffer.from(await res.arrayBuffer());
|
|
189
|
-
assert.deepEqual(slice, Buffer.from("2345"));
|
|
190
|
-
assert.ok(res.headers.get("content-range")?.startsWith("bytes 2-5/"), "Content-Range header");
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
test("GET: rejects path traversal", async () => {
|
|
194
|
-
const req = new Request(fileUrl("../etc/passwd"), {
|
|
195
|
-
headers: agentHeaders(READ_TOKEN, "ai:read-agent"),
|
|
196
|
-
});
|
|
197
|
-
const res = await fileGET(req, makeCtx(["../etc/passwd"]));
|
|
198
|
-
assert.equal(res.status, 400);
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
test("GET: rejects .proof path", async () => {
|
|
202
|
-
const req = new Request(fileUrl(".proof/something.json"), {
|
|
203
|
-
headers: agentHeaders(READ_TOKEN, "ai:read-agent"),
|
|
204
|
-
});
|
|
205
|
-
const res = await fileGET(req, makeCtx([".proof", "something.json"]));
|
|
206
|
-
assert.equal(res.status, 400);
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
test("GET: 401 without auth", async () => {
|
|
210
|
-
const req = new Request(fileUrl("hello.txt"));
|
|
211
|
-
const res = await fileGET(req, makeCtx(["hello.txt"]));
|
|
212
|
-
assert.equal(res.status, 401);
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
test("GET: scope-filtered — restricted agent denied out-of-scope path", async () => {
|
|
216
|
-
await writeFile(path.join(tmpRoot, "secret.txt"), "top secret");
|
|
217
|
-
|
|
218
|
-
const req = new Request(fileUrl("secret.txt"), {
|
|
219
|
-
headers: agentHeaders(RESTRICTED_TOKEN, "ai:restricted"),
|
|
220
|
-
});
|
|
221
|
-
const res = await fileGET(req, makeCtx(["secret.txt"]));
|
|
222
|
-
assert.equal(res.status, 403);
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
// ── PUT tests ────────────────────────────────────────────────────────────────
|
|
226
|
-
|
|
227
|
-
test("PUT: create new file (no If-Match)", async () => {
|
|
228
|
-
const content = Buffer.from("brand new file");
|
|
229
|
-
const req = new Request(fileUrl("new-file.txt"), {
|
|
230
|
-
method: "PUT",
|
|
231
|
-
headers: agentHeaders(MUTATE_TOKEN, "ai:mutate-agent"),
|
|
232
|
-
body: content,
|
|
233
|
-
});
|
|
234
|
-
const res = await filePUT(req, makeCtx(["new-file.txt"]));
|
|
235
|
-
assert.equal(res.status, 200);
|
|
236
|
-
const json = await res.json() as { path: string; created: boolean; sha256: string };
|
|
237
|
-
assert.equal(json.path, "new-file.txt");
|
|
238
|
-
assert.equal(json.created, true);
|
|
239
|
-
|
|
240
|
-
const written = await readFile(path.join(tmpRoot, "new-file.txt"));
|
|
241
|
-
assert.deepEqual(written, content);
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
test("PUT: overwrite requires If-Match", async () => {
|
|
245
|
-
await writeFile(path.join(tmpRoot, "existing.txt"), "original");
|
|
246
|
-
|
|
247
|
-
const req = new Request(fileUrl("existing.txt"), {
|
|
248
|
-
method: "PUT",
|
|
249
|
-
headers: agentHeaders(MUTATE_TOKEN, "ai:mutate-agent"),
|
|
250
|
-
body: Buffer.from("updated"),
|
|
251
|
-
});
|
|
252
|
-
const res = await filePUT(req, makeCtx(["existing.txt"]));
|
|
253
|
-
assert.equal(res.status, 412);
|
|
254
|
-
const json = await res.json() as { error: string };
|
|
255
|
-
assert.equal(json.error, "PRECONDITION_REQUIRED");
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
test("PUT: create-collision — file exists, no If-Match → 412", async () => {
|
|
259
|
-
await writeFile(path.join(tmpRoot, "collision.txt"), "existing content");
|
|
260
|
-
// Sending PUT without If-Match to an existing file = 412 PRECONDITION_REQUIRED
|
|
261
|
-
const req = new Request(fileUrl("collision.txt"), {
|
|
262
|
-
method: "PUT",
|
|
263
|
-
headers: agentHeaders(MUTATE_TOKEN, "ai:mutate-agent"),
|
|
264
|
-
body: Buffer.from("new content"),
|
|
265
|
-
});
|
|
266
|
-
const res = await filePUT(req, makeCtx(["collision.txt"]));
|
|
267
|
-
assert.equal(res.status, 412);
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
test("PUT: overwrite with correct If-Match succeeds", async () => {
|
|
271
|
-
const original = Buffer.from("original content");
|
|
272
|
-
await writeFile(path.join(tmpRoot, "ifmatch.txt"), original);
|
|
273
|
-
const sha = sha256(original);
|
|
274
|
-
|
|
275
|
-
const req = new Request(fileUrl("ifmatch.txt"), {
|
|
276
|
-
method: "PUT",
|
|
277
|
-
headers: {
|
|
278
|
-
...agentHeaders(MUTATE_TOKEN, "ai:mutate-agent"),
|
|
279
|
-
"If-Match": `"${sha}"`,
|
|
280
|
-
},
|
|
281
|
-
body: Buffer.from("updated content"),
|
|
282
|
-
});
|
|
283
|
-
const res = await filePUT(req, makeCtx(["ifmatch.txt"]));
|
|
284
|
-
assert.equal(res.status, 200);
|
|
285
|
-
const json = await res.json() as { created: boolean };
|
|
286
|
-
assert.equal(json.created, false);
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
test("PUT: If-Match mismatch → 412 PRECONDITION_FAILED", async () => {
|
|
290
|
-
await writeFile(path.join(tmpRoot, "mismatch.txt"), "content");
|
|
291
|
-
|
|
292
|
-
const req = new Request(fileUrl("mismatch.txt"), {
|
|
293
|
-
method: "PUT",
|
|
294
|
-
headers: {
|
|
295
|
-
...agentHeaders(MUTATE_TOKEN, "ai:mutate-agent"),
|
|
296
|
-
"If-Match": `"sha256:${"0".repeat(64)}"`,
|
|
297
|
-
},
|
|
298
|
-
body: Buffer.from("new"),
|
|
299
|
-
});
|
|
300
|
-
const res = await filePUT(req, makeCtx(["mismatch.txt"]));
|
|
301
|
-
assert.equal(res.status, 412);
|
|
302
|
-
const json = await res.json() as { error: string };
|
|
303
|
-
assert.equal(json.error, "PRECONDITION_FAILED");
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
test("PUT: ?force=true bypasses If-Match for overwrite", async () => {
|
|
307
|
-
await writeFile(path.join(tmpRoot, "force.txt"), "original");
|
|
308
|
-
|
|
309
|
-
const req = new Request(fileUrl("force.txt", "?force=true"), {
|
|
310
|
-
method: "PUT",
|
|
311
|
-
headers: agentHeaders(MUTATE_TOKEN, "ai:mutate-agent"),
|
|
312
|
-
body: Buffer.from("forced update"),
|
|
313
|
-
});
|
|
314
|
-
const res = await filePUT(req, makeCtx(["force.txt"]));
|
|
315
|
-
assert.equal(res.status, 200);
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
test("PUT: ?mkdirs=true creates intermediate directories", async () => {
|
|
319
|
-
const req = new Request(fileUrl("deep/nested/dir/file.txt", "?mkdirs=true"), {
|
|
320
|
-
method: "PUT",
|
|
321
|
-
headers: agentHeaders(MUTATE_TOKEN, "ai:mutate-agent"),
|
|
322
|
-
body: Buffer.from("deep file"),
|
|
323
|
-
});
|
|
324
|
-
const res = await filePUT(req, makeCtx(["deep", "nested", "dir", "file.txt"]));
|
|
325
|
-
assert.equal(res.status, 200);
|
|
326
|
-
const written = await readFile(path.join(tmpRoot, "deep/nested/dir/file.txt"));
|
|
327
|
-
assert.deepEqual(written, Buffer.from("deep file"));
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
test("PUT: parent dir missing without ?mkdirs → 400", async () => {
|
|
331
|
-
const req = new Request(fileUrl("nonexistent-dir/file.txt"), {
|
|
332
|
-
method: "PUT",
|
|
333
|
-
headers: agentHeaders(MUTATE_TOKEN, "ai:mutate-agent"),
|
|
334
|
-
body: Buffer.from("content"),
|
|
335
|
-
});
|
|
336
|
-
const res = await filePUT(req, makeCtx(["nonexistent-dir", "file.txt"]));
|
|
337
|
-
assert.equal(res.status, 400);
|
|
338
|
-
const json = await res.json() as { error: string };
|
|
339
|
-
assert.equal(json.error, "PARENT_NOT_FOUND");
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
test("PUT: .md write triggers file.rawWritten event and reconcile", async () => {
|
|
343
|
-
const mdContent = "# Hello\n\nThis is a test.\n";
|
|
344
|
-
const mdPath = "raw-written.md";
|
|
345
|
-
|
|
346
|
-
const req = new Request(fileUrl(mdPath), {
|
|
347
|
-
method: "PUT",
|
|
348
|
-
headers: agentHeaders(MUTATE_TOKEN, "ai:mutate-agent"),
|
|
349
|
-
body: Buffer.from(mdContent),
|
|
350
|
-
});
|
|
351
|
-
const res = await filePUT(req, makeCtx([mdPath]));
|
|
352
|
-
assert.equal(res.status, 200);
|
|
353
|
-
|
|
354
|
-
// Sidecar should be created with file.rawWritten event
|
|
355
|
-
const sidecar = await readSidecar(tmpRoot, mdPath);
|
|
356
|
-
assert.ok(sidecar, "sidecar created after .md PUT");
|
|
357
|
-
const rawWrittenEvents = sidecar!.events.filter((e) => e.type === "file.rawWritten");
|
|
358
|
-
assert.ok(rawWrittenEvents.length > 0, "file.rawWritten event in sidecar");
|
|
359
|
-
assert.equal(rawWrittenEvents[0]!.by, "ai:mutate-agent");
|
|
360
|
-
// Fingerprint set correctly
|
|
361
|
-
const fileBuf = await readFile(path.join(tmpRoot, mdPath));
|
|
362
|
-
const expectedSha = sha256(fileBuf);
|
|
363
|
-
assert.equal(sidecar!.fingerprint, expectedSha);
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
test("PUT: .md overwrite updates sidecar fingerprint eagerly (R2 no lazy mismatch)", async () => {
|
|
367
|
-
const mdPath = "eager-reconcile.md";
|
|
368
|
-
const initial = "# Initial\n";
|
|
369
|
-
const updated = "# Updated\n\nNew paragraph.\n";
|
|
370
|
-
|
|
371
|
-
// Create with initial content
|
|
372
|
-
await writeFile(path.join(tmpRoot, mdPath), initial);
|
|
373
|
-
const sc = emptySidecar(mdPath);
|
|
374
|
-
sc.fingerprint = sha256(Buffer.from(initial));
|
|
375
|
-
await writeSidecar(tmpRoot, mdPath, sc);
|
|
376
|
-
|
|
377
|
-
// PUT updated content with If-Match
|
|
378
|
-
const initialSha = sha256(Buffer.from(initial));
|
|
379
|
-
const req = new Request(fileUrl(mdPath), {
|
|
380
|
-
method: "PUT",
|
|
381
|
-
headers: {
|
|
382
|
-
...agentHeaders(MUTATE_TOKEN, "ai:mutate-agent"),
|
|
383
|
-
"If-Match": `"${initialSha}"`,
|
|
384
|
-
},
|
|
385
|
-
body: Buffer.from(updated),
|
|
386
|
-
});
|
|
387
|
-
const res = await filePUT(req, makeCtx([mdPath]));
|
|
388
|
-
assert.equal(res.status, 200);
|
|
389
|
-
|
|
390
|
-
const sidecar = await readSidecar(tmpRoot, mdPath);
|
|
391
|
-
const newFileSha = sha256(Buffer.from(updated));
|
|
392
|
-
// Fingerprint MUST equal new sha (not old), proving eager reconcile ran
|
|
393
|
-
assert.equal(sidecar!.fingerprint, newFileSha, "fingerprint updated eagerly");
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
test("PUT: mutate scope required (read-only agent rejected)", async () => {
|
|
397
|
-
const req = new Request(fileUrl("some.txt"), {
|
|
398
|
-
method: "PUT",
|
|
399
|
-
headers: agentHeaders(READ_TOKEN, "ai:read-agent"),
|
|
400
|
-
body: Buffer.from("data"),
|
|
401
|
-
});
|
|
402
|
-
const res = await filePUT(req, makeCtx(["some.txt"]));
|
|
403
|
-
assert.equal(res.status, 403);
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
// ── DELETE tests ──────────────────────────────────────────────────────────────
|
|
407
|
-
|
|
408
|
-
test("DELETE: requires delete scope — mutate-only agent gets 403", async () => {
|
|
409
|
-
await writeFile(path.join(tmpRoot, "del-scope.txt"), "content");
|
|
410
|
-
const buf = await readFile(path.join(tmpRoot, "del-scope.txt"));
|
|
411
|
-
const sha = sha256(buf);
|
|
412
|
-
|
|
413
|
-
const req = new Request(fileUrl("del-scope.txt"), {
|
|
414
|
-
method: "DELETE",
|
|
415
|
-
headers: {
|
|
416
|
-
...agentHeaders(MUTATE_TOKEN, "ai:mutate-agent"),
|
|
417
|
-
"If-Match": `"${sha}"`,
|
|
418
|
-
},
|
|
419
|
-
});
|
|
420
|
-
const res = await fileDELETE(req, makeCtx(["del-scope.txt"]));
|
|
421
|
-
assert.equal(res.status, 403);
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
test("DELETE: requires If-Match header", async () => {
|
|
425
|
-
await writeFile(path.join(tmpRoot, "del-nomatch.txt"), "content");
|
|
426
|
-
const req = new Request(fileUrl("del-nomatch.txt"), {
|
|
427
|
-
method: "DELETE",
|
|
428
|
-
headers: agentHeaders(DELETE_TOKEN, "ai:delete-agent"),
|
|
429
|
-
});
|
|
430
|
-
const res = await fileDELETE(req, makeCtx(["del-nomatch.txt"]));
|
|
431
|
-
assert.equal(res.status, 412);
|
|
432
|
-
const json = await res.json() as { error: string };
|
|
433
|
-
assert.equal(json.error, "PRECONDITION_REQUIRED");
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
test("DELETE: If-Match mismatch → 412", async () => {
|
|
437
|
-
await writeFile(path.join(tmpRoot, "del-mismatch.txt"), "content");
|
|
438
|
-
const req = new Request(fileUrl("del-mismatch.txt"), {
|
|
439
|
-
method: "DELETE",
|
|
440
|
-
headers: {
|
|
441
|
-
...agentHeaders(DELETE_TOKEN, "ai:delete-agent"),
|
|
442
|
-
"If-Match": `"sha256:${"0".repeat(64)}"`,
|
|
443
|
-
},
|
|
444
|
-
});
|
|
445
|
-
const res = await fileDELETE(req, makeCtx(["del-mismatch.txt"]));
|
|
446
|
-
assert.equal(res.status, 412);
|
|
447
|
-
});
|
|
448
|
-
|
|
449
|
-
test("DELETE: file with correct If-Match deleted", async () => {
|
|
450
|
-
const content = Buffer.from("to be deleted");
|
|
451
|
-
await writeFile(path.join(tmpRoot, "deleteme.txt"), content);
|
|
452
|
-
const sha = sha256(content);
|
|
453
|
-
|
|
454
|
-
const req = new Request(fileUrl("deleteme.txt"), {
|
|
455
|
-
method: "DELETE",
|
|
456
|
-
headers: {
|
|
457
|
-
...agentHeaders(DELETE_TOKEN, "ai:delete-agent"),
|
|
458
|
-
"If-Match": `"${sha}"`,
|
|
459
|
-
},
|
|
460
|
-
});
|
|
461
|
-
const res = await fileDELETE(req, makeCtx(["deleteme.txt"]));
|
|
462
|
-
assert.equal(res.status, 200);
|
|
463
|
-
|
|
464
|
-
// File gone
|
|
465
|
-
await assert.rejects(
|
|
466
|
-
() => readFile(path.join(tmpRoot, "deleteme.txt")),
|
|
467
|
-
{ code: "ENOENT" },
|
|
468
|
-
);
|
|
469
|
-
});
|
|
470
|
-
|
|
471
|
-
test("DELETE: .md deletion also removes sidecar", async () => {
|
|
472
|
-
const mdPath = "delete-with-sidecar.md";
|
|
473
|
-
const content = Buffer.from("# Delete me\n");
|
|
474
|
-
await writeFile(path.join(tmpRoot, mdPath), content);
|
|
475
|
-
const sc = emptySidecar(mdPath);
|
|
476
|
-
await writeSidecar(tmpRoot, mdPath, sc);
|
|
477
|
-
const sha = sha256(content);
|
|
478
|
-
|
|
479
|
-
const req = new Request(fileUrl(mdPath), {
|
|
480
|
-
method: "DELETE",
|
|
481
|
-
headers: {
|
|
482
|
-
...agentHeaders(DELETE_TOKEN, "ai:delete-agent"),
|
|
483
|
-
"If-Match": `"${sha}"`,
|
|
484
|
-
},
|
|
485
|
-
});
|
|
486
|
-
const res = await fileDELETE(req, makeCtx([mdPath]));
|
|
487
|
-
assert.equal(res.status, 200);
|
|
488
|
-
|
|
489
|
-
// Both file and sidecar gone
|
|
490
|
-
await assert.rejects(
|
|
491
|
-
() => readFile(path.join(tmpRoot, mdPath)),
|
|
492
|
-
{ code: "ENOENT" },
|
|
493
|
-
);
|
|
494
|
-
const sidecarAfter = await readSidecar(tmpRoot, mdPath);
|
|
495
|
-
assert.equal(sidecarAfter, null, "sidecar deleted alongside .md");
|
|
496
|
-
});
|
|
497
|
-
|
|
498
|
-
test("DELETE: directory without ?recursive → 400", async () => {
|
|
499
|
-
await mkdir(path.join(tmpRoot, "nodirdel"), { recursive: true });
|
|
500
|
-
const req = new Request(fileUrl("nodirdel"), {
|
|
501
|
-
method: "DELETE",
|
|
502
|
-
headers: {
|
|
503
|
-
...agentHeaders(DELETE_TOKEN, "ai:delete-agent"),
|
|
504
|
-
"If-Match": '"anything"',
|
|
505
|
-
},
|
|
506
|
-
});
|
|
507
|
-
const res = await fileDELETE(req, makeCtx(["nodirdel"]));
|
|
508
|
-
assert.equal(res.status, 400);
|
|
509
|
-
const json = await res.json() as { error: string };
|
|
510
|
-
assert.equal(json.error, "IS_DIRECTORY");
|
|
511
|
-
});
|
|
512
|
-
|
|
513
|
-
test("DELETE: directory with ?recursive=true succeeds", async () => {
|
|
514
|
-
await mkdir(path.join(tmpRoot, "recdel"), { recursive: true });
|
|
515
|
-
await writeFile(path.join(tmpRoot, "recdel/file.txt"), "hi");
|
|
516
|
-
const req = new Request(fileUrl("recdel", "?recursive=true"), {
|
|
517
|
-
method: "DELETE",
|
|
518
|
-
headers: agentHeaders(DELETE_TOKEN, "ai:delete-agent"),
|
|
519
|
-
});
|
|
520
|
-
const res = await fileDELETE(req, makeCtx(["recdel"]));
|
|
521
|
-
assert.equal(res.status, 200);
|
|
522
|
-
});
|
|
523
|
-
|
|
524
|
-
// ── ls tests ──────────────────────────────────────────────────────────────────
|
|
525
|
-
|
|
526
|
-
test("ls: lists files in directory", async () => {
|
|
527
|
-
await mkdir(path.join(tmpRoot, "ls-dir"), { recursive: true });
|
|
528
|
-
await writeFile(path.join(tmpRoot, "ls-dir/a.txt"), "a");
|
|
529
|
-
await writeFile(path.join(tmpRoot, "ls-dir/b.md"), "# b");
|
|
530
|
-
|
|
531
|
-
const req = new Request(lsUrl("ls-dir"), {
|
|
532
|
-
headers: agentHeaders(READ_TOKEN, "ai:read-agent"),
|
|
533
|
-
});
|
|
534
|
-
const res = await lsGET(req, makeLsCtx(["ls-dir"]));
|
|
535
|
-
assert.equal(res.status, 200);
|
|
536
|
-
const json = await res.json() as { entries: Array<{ name: string }> };
|
|
537
|
-
const names = json.entries.map((e) => e.name).sort();
|
|
538
|
-
assert.deepEqual(names, ["a.txt", "b.md"]);
|
|
539
|
-
});
|
|
540
|
-
|
|
541
|
-
test("ls: excludes .proof/ from results", async () => {
|
|
542
|
-
// .proof dir may already exist from other tests — just check it's not listed
|
|
543
|
-
await mkdir(path.join(tmpRoot, ".proof"), { recursive: true });
|
|
544
|
-
await writeFile(path.join(tmpRoot, ".proof/test.json"), "{}");
|
|
545
|
-
|
|
546
|
-
const req = new Request(lsUrl(""), {
|
|
547
|
-
headers: agentHeaders(READ_TOKEN, "ai:read-agent"),
|
|
548
|
-
});
|
|
549
|
-
const res = await lsGET(req, makeLsCtx([]));
|
|
550
|
-
assert.equal(res.status, 200);
|
|
551
|
-
const json = await res.json() as { entries: Array<{ name: string }> };
|
|
552
|
-
const hasProof = json.entries.some((e) => e.name === ".proof");
|
|
553
|
-
assert.equal(hasProof, false, ".proof should be excluded");
|
|
554
|
-
});
|
|
555
|
-
|
|
556
|
-
test("ls: scope-filters entries — restricted agent only sees allowed/", async () => {
|
|
557
|
-
await mkdir(path.join(tmpRoot, "allowed"), { recursive: true });
|
|
558
|
-
await writeFile(path.join(tmpRoot, "allowed/ok.txt"), "yes");
|
|
559
|
-
await writeFile(path.join(tmpRoot, "not-allowed.txt"), "no");
|
|
560
|
-
|
|
561
|
-
const req = new Request(lsUrl(""), {
|
|
562
|
-
headers: agentHeaders(RESTRICTED_TOKEN, "ai:restricted"),
|
|
563
|
-
});
|
|
564
|
-
const res = await lsGET(req, makeLsCtx([]));
|
|
565
|
-
assert.equal(res.status, 200);
|
|
566
|
-
const json = await res.json() as { entries: Array<{ name: string; path: string }> };
|
|
567
|
-
// Restricted agent has scope "allowed/**" — not-allowed.txt should be absent
|
|
568
|
-
const notAllowed = json.entries.find((e) => e.name === "not-allowed.txt");
|
|
569
|
-
assert.equal(notAllowed, undefined, "out-of-scope file hidden from restricted agent");
|
|
570
|
-
});
|
|
571
|
-
|
|
572
|
-
test("ls: recursive=true traverses subdirs", async () => {
|
|
573
|
-
await mkdir(path.join(tmpRoot, "rec/sub"), { recursive: true });
|
|
574
|
-
await writeFile(path.join(tmpRoot, "rec/top.txt"), "top");
|
|
575
|
-
await writeFile(path.join(tmpRoot, "rec/sub/deep.txt"), "deep");
|
|
576
|
-
|
|
577
|
-
const req = new Request(lsUrl("rec", "?recursive"), {
|
|
578
|
-
headers: agentHeaders(READ_TOKEN, "ai:read-agent"),
|
|
579
|
-
});
|
|
580
|
-
const res = await lsGET(req, makeLsCtx(["rec"]));
|
|
581
|
-
assert.equal(res.status, 200);
|
|
582
|
-
const json = await res.json() as { entries: Array<{ path: string }> };
|
|
583
|
-
const paths = json.entries.map((e) => e.path);
|
|
584
|
-
assert.ok(paths.some((p) => p.includes("deep.txt")), "deep.txt found in recursive listing");
|
|
585
|
-
});
|
|
586
|
-
|
|
587
|
-
test("ls: limit is respected", async () => {
|
|
588
|
-
await mkdir(path.join(tmpRoot, "limit-dir"), { recursive: true });
|
|
589
|
-
for (let i = 0; i < 10; i++) {
|
|
590
|
-
await writeFile(path.join(tmpRoot, `limit-dir/f${i}.txt`), String(i));
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
const req = new Request(lsUrl("limit-dir", "?limit=3"), {
|
|
594
|
-
headers: agentHeaders(READ_TOKEN, "ai:read-agent"),
|
|
595
|
-
});
|
|
596
|
-
const res = await lsGET(req, makeLsCtx(["limit-dir"]));
|
|
597
|
-
assert.equal(res.status, 200);
|
|
598
|
-
const json = await res.json() as { entries: unknown[]; truncated: boolean };
|
|
599
|
-
assert.equal(json.entries.length, 3);
|
|
600
|
-
assert.equal(json.truncated, true);
|
|
601
|
-
});
|
|
602
|
-
|
|
603
|
-
// ── move tests ────────────────────────────────────────────────────────────────
|
|
604
|
-
|
|
605
|
-
test("move: renames a file", async () => {
|
|
606
|
-
await writeFile(path.join(tmpRoot, "move-src.txt"), "data");
|
|
607
|
-
|
|
608
|
-
const req = new Request("http://localhost/api/agent/fs/move", {
|
|
609
|
-
method: "POST",
|
|
610
|
-
headers: {
|
|
611
|
-
...agentHeaders(MUTATE_TOKEN, "ai:mutate-agent"),
|
|
612
|
-
"Content-Type": "application/json",
|
|
613
|
-
},
|
|
614
|
-
body: JSON.stringify({ from: "move-src.txt", to: "move-dst.txt" }),
|
|
615
|
-
});
|
|
616
|
-
const res = await movePOST(req);
|
|
617
|
-
assert.equal(res.status, 200);
|
|
618
|
-
|
|
619
|
-
const dst = await readFile(path.join(tmpRoot, "move-dst.txt"), "utf-8");
|
|
620
|
-
assert.equal(dst, "data");
|
|
621
|
-
await assert.rejects(
|
|
622
|
-
() => readFile(path.join(tmpRoot, "move-src.txt")),
|
|
623
|
-
{ code: "ENOENT" },
|
|
624
|
-
);
|
|
625
|
-
});
|
|
626
|
-
|
|
627
|
-
test("move: moves sidecar alongside .md", async () => {
|
|
628
|
-
const fromMd = "move-from.md";
|
|
629
|
-
const toMd = "move-to.md";
|
|
630
|
-
await writeFile(path.join(tmpRoot, fromMd), "# From\n");
|
|
631
|
-
const sc = emptySidecar(fromMd);
|
|
632
|
-
sc.revision = 5;
|
|
633
|
-
await writeSidecar(tmpRoot, fromMd, sc);
|
|
634
|
-
|
|
635
|
-
const req = new Request("http://localhost/api/agent/fs/move", {
|
|
636
|
-
method: "POST",
|
|
637
|
-
headers: {
|
|
638
|
-
...agentHeaders(MUTATE_TOKEN, "ai:mutate-agent"),
|
|
639
|
-
"Content-Type": "application/json",
|
|
640
|
-
},
|
|
641
|
-
body: JSON.stringify({ from: fromMd, to: toMd }),
|
|
642
|
-
});
|
|
643
|
-
const res = await movePOST(req);
|
|
644
|
-
assert.equal(res.status, 200);
|
|
645
|
-
|
|
646
|
-
// Source sidecar gone, dest sidecar present with correct data
|
|
647
|
-
const fromSidecar = await readSidecar(tmpRoot, fromMd);
|
|
648
|
-
assert.equal(fromSidecar, null, "source sidecar removed");
|
|
649
|
-
const toSidecar = await readSidecar(tmpRoot, toMd);
|
|
650
|
-
assert.ok(toSidecar, "destination sidecar present");
|
|
651
|
-
assert.equal(toSidecar!.revision, 5);
|
|
652
|
-
});
|
|
653
|
-
|
|
654
|
-
test("move: If-Match mismatch → 412", async () => {
|
|
655
|
-
await writeFile(path.join(tmpRoot, "move-ifmatch.txt"), "content");
|
|
656
|
-
|
|
657
|
-
const req = new Request("http://localhost/api/agent/fs/move", {
|
|
658
|
-
method: "POST",
|
|
659
|
-
headers: {
|
|
660
|
-
...agentHeaders(MUTATE_TOKEN, "ai:mutate-agent"),
|
|
661
|
-
"Content-Type": "application/json",
|
|
662
|
-
},
|
|
663
|
-
body: JSON.stringify({ from: "move-ifmatch.txt", to: "move-ifmatch-dst.txt", ifMatch: "sha256:" + "0".repeat(64) }),
|
|
664
|
-
});
|
|
665
|
-
const res = await movePOST(req);
|
|
666
|
-
assert.equal(res.status, 412);
|
|
667
|
-
});
|
|
668
|
-
|
|
669
|
-
test("move: 404 when source doesn't exist", async () => {
|
|
670
|
-
const req = new Request("http://localhost/api/agent/fs/move", {
|
|
671
|
-
method: "POST",
|
|
672
|
-
headers: {
|
|
673
|
-
...agentHeaders(MUTATE_TOKEN, "ai:mutate-agent"),
|
|
674
|
-
"Content-Type": "application/json",
|
|
675
|
-
},
|
|
676
|
-
body: JSON.stringify({ from: "no-such-file.txt", to: "dst.txt" }),
|
|
677
|
-
});
|
|
678
|
-
const res = await movePOST(req);
|
|
679
|
-
assert.equal(res.status, 404);
|
|
680
|
-
});
|
|
681
|
-
|
|
682
|
-
// ── search tests ──────────────────────────────────────────────────────────────
|
|
683
|
-
|
|
684
|
-
test("search grep: finds pattern in files", async () => {
|
|
685
|
-
await mkdir(path.join(tmpRoot, "grep-dir"), { recursive: true });
|
|
686
|
-
await writeFile(path.join(tmpRoot, "grep-dir/alpha.txt"), "hello world\nfoo bar\n");
|
|
687
|
-
await writeFile(path.join(tmpRoot, "grep-dir/beta.txt"), "no match here\nhello again\n");
|
|
688
|
-
|
|
689
|
-
const req = new Request("http://localhost/api/agent/fs/search", {
|
|
690
|
-
method: "POST",
|
|
691
|
-
headers: {
|
|
692
|
-
...agentHeaders(READ_TOKEN, "ai:read-agent"),
|
|
693
|
-
"Content-Type": "application/json",
|
|
694
|
-
},
|
|
695
|
-
body: JSON.stringify({ kind: "grep", query: "hello", path: "grep-dir" }),
|
|
696
|
-
});
|
|
697
|
-
const res = await searchPOST(req);
|
|
698
|
-
assert.equal(res.status, 200);
|
|
699
|
-
const json = await res.json() as { matches: Array<{ path: string; line: number }> };
|
|
700
|
-
assert.ok(json.matches.length >= 2, "both matches found");
|
|
701
|
-
assert.ok(json.matches.every((m) => m.path.startsWith("grep-dir/")));
|
|
702
|
-
});
|
|
703
|
-
|
|
704
|
-
test("search grep: skips binary files", async () => {
|
|
705
|
-
await mkdir(path.join(tmpRoot, "bin-dir"), { recursive: true });
|
|
706
|
-
// Binary file (has null byte)
|
|
707
|
-
const binBuf = Buffer.from([0x00, 0x01, 0x02, 0x68, 0x65, 0x6c, 0x6c, 0x6f]); // null + "hello"
|
|
708
|
-
await writeFile(path.join(tmpRoot, "bin-dir/binary.bin"), binBuf);
|
|
709
|
-
await writeFile(path.join(tmpRoot, "bin-dir/text.txt"), "hello from text");
|
|
710
|
-
|
|
711
|
-
const req = new Request("http://localhost/api/agent/fs/search", {
|
|
712
|
-
method: "POST",
|
|
713
|
-
headers: {
|
|
714
|
-
...agentHeaders(READ_TOKEN, "ai:read-agent"),
|
|
715
|
-
"Content-Type": "application/json",
|
|
716
|
-
},
|
|
717
|
-
body: JSON.stringify({ kind: "grep", query: "hello", path: "bin-dir" }),
|
|
718
|
-
});
|
|
719
|
-
const res = await searchPOST(req);
|
|
720
|
-
assert.equal(res.status, 200);
|
|
721
|
-
const json = await res.json() as { matches: Array<{ path: string }> };
|
|
722
|
-
// Binary file skipped, only text.txt matches
|
|
723
|
-
assert.ok(json.matches.every((m) => !m.path.endsWith(".bin")), "binary file skipped");
|
|
724
|
-
assert.ok(json.matches.some((m) => m.path.endsWith("text.txt")), "text file found");
|
|
725
|
-
});
|
|
726
|
-
|
|
727
|
-
test("search glob: finds files matching pattern", async () => {
|
|
728
|
-
await mkdir(path.join(tmpRoot, "glob-dir"), { recursive: true });
|
|
729
|
-
await writeFile(path.join(tmpRoot, "glob-dir/a.md"), "# A");
|
|
730
|
-
await writeFile(path.join(tmpRoot, "glob-dir/b.ts"), "const x = 1;");
|
|
731
|
-
await writeFile(path.join(tmpRoot, "glob-dir/c.md"), "# C");
|
|
732
|
-
|
|
733
|
-
const req = new Request("http://localhost/api/agent/fs/search", {
|
|
734
|
-
method: "POST",
|
|
735
|
-
headers: {
|
|
736
|
-
...agentHeaders(READ_TOKEN, "ai:read-agent"),
|
|
737
|
-
"Content-Type": "application/json",
|
|
738
|
-
},
|
|
739
|
-
body: JSON.stringify({ kind: "glob", query: "*.md", path: "glob-dir" }),
|
|
740
|
-
});
|
|
741
|
-
const res = await searchPOST(req);
|
|
742
|
-
assert.equal(res.status, 200);
|
|
743
|
-
const json = await res.json() as { matches: Array<{ path: string }> };
|
|
744
|
-
assert.ok(json.matches.length >= 2, "both .md files found");
|
|
745
|
-
assert.ok(json.matches.every((m) => m.path.endsWith(".md")), "only .md files returned");
|
|
746
|
-
});
|
|
747
|
-
|
|
748
|
-
test("search: scope-rechecks each result — restricted agent only sees allowed/ files", async () => {
|
|
749
|
-
await mkdir(path.join(tmpRoot, "allowed/search-sub"), { recursive: true });
|
|
750
|
-
await writeFile(path.join(tmpRoot, "allowed/search-sub/in-scope.txt"), "find me");
|
|
751
|
-
await writeFile(path.join(tmpRoot, "out-of-scope-search.txt"), "find me too");
|
|
752
|
-
|
|
753
|
-
const req = new Request("http://localhost/api/agent/fs/search", {
|
|
754
|
-
method: "POST",
|
|
755
|
-
headers: {
|
|
756
|
-
...agentHeaders(RESTRICTED_TOKEN, "ai:restricted"),
|
|
757
|
-
"Content-Type": "application/json",
|
|
758
|
-
},
|
|
759
|
-
body: JSON.stringify({ kind: "grep", query: "find me" }),
|
|
760
|
-
});
|
|
761
|
-
const res = await searchPOST(req);
|
|
762
|
-
assert.equal(res.status, 200);
|
|
763
|
-
const json = await res.json() as { matches: Array<{ path: string }> };
|
|
764
|
-
// out-of-scope-search.txt must not appear
|
|
765
|
-
const hasOutOfScope = json.matches.some((m) => m.path.includes("out-of-scope-search"));
|
|
766
|
-
assert.equal(hasOutOfScope, false, "out-of-scope file hidden from restricted agent");
|
|
767
|
-
});
|
|
768
|
-
|
|
769
|
-
test("search: invalid regex → 400", async () => {
|
|
770
|
-
const req = new Request("http://localhost/api/agent/fs/search", {
|
|
771
|
-
method: "POST",
|
|
772
|
-
headers: {
|
|
773
|
-
...agentHeaders(READ_TOKEN, "ai:read-agent"),
|
|
774
|
-
"Content-Type": "application/json",
|
|
775
|
-
},
|
|
776
|
-
body: JSON.stringify({ kind: "grep", query: "[invalid" }),
|
|
777
|
-
});
|
|
778
|
-
const res = await searchPOST(req);
|
|
779
|
-
assert.equal(res.status, 400);
|
|
780
|
-
});
|
|
781
|
-
|
|
782
|
-
// ── Path safety (traversal + symlink) ────────────────────────────────────────
|
|
783
|
-
|
|
784
|
-
test("traversal: PUT rejects ../ path", async () => {
|
|
785
|
-
const req = new Request(fileUrl("../outside.txt"), {
|
|
786
|
-
method: "PUT",
|
|
787
|
-
headers: agentHeaders(MUTATE_TOKEN, "ai:mutate-agent"),
|
|
788
|
-
body: Buffer.from("escape attempt"),
|
|
789
|
-
});
|
|
790
|
-
const res = await filePUT(req, makeCtx(["../outside.txt"]));
|
|
791
|
-
assert.equal(res.status, 400);
|
|
792
|
-
});
|
|
793
|
-
|
|
794
|
-
test("symlink escape: PUT rejects symlink pointing outside root", async () => {
|
|
795
|
-
// Create a symlink inside root pointing outside
|
|
796
|
-
const escapedDir = await mkdtemp(path.join(tmpdir(), "escape-target-"));
|
|
797
|
-
try {
|
|
798
|
-
const symlinkPath = path.join(tmpRoot, "escape-link");
|
|
799
|
-
try {
|
|
800
|
-
await symlink(escapedDir, symlinkPath);
|
|
801
|
-
} catch {
|
|
802
|
-
// symlink already exists from prior run
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
const req = new Request(fileUrl("escape-link/secret.txt"), {
|
|
806
|
-
method: "PUT",
|
|
807
|
-
headers: agentHeaders(MUTATE_TOKEN, "ai:mutate-agent"),
|
|
808
|
-
body: Buffer.from("escaped!"),
|
|
809
|
-
});
|
|
810
|
-
const res = await filePUT(req, makeCtx(["escape-link", "secret.txt"]));
|
|
811
|
-
// Should reject: symlink escapes root
|
|
812
|
-
assert.equal(res.status, 400);
|
|
813
|
-
} finally {
|
|
814
|
-
await rm(escapedDir, { recursive: true, force: true });
|
|
815
|
-
}
|
|
816
|
-
});
|
|
817
|
-
|
|
818
|
-
test("traversal: .proof/ denied on PUT", async () => {
|
|
819
|
-
const req = new Request(fileUrl(".proof/inject.json"), {
|
|
820
|
-
method: "PUT",
|
|
821
|
-
headers: agentHeaders(MUTATE_TOKEN, "ai:mutate-agent"),
|
|
822
|
-
body: Buffer.from("{}"),
|
|
823
|
-
});
|
|
824
|
-
const res = await filePUT(req, makeCtx([".proof", "inject.json"]));
|
|
825
|
-
assert.equal(res.status, 400);
|
|
826
|
-
});
|
|
827
|
-
|
|
828
|
-
// ── PATCH (server-side str-replace) ──────────────────────────────────────────
|
|
829
|
-
|
|
830
|
-
test("PATCH: str-replace on .md succeeds, sends only find/replace", async () => {
|
|
831
|
-
const orig = Buffer.from("# Title\n\nHello world. Keep this.\n");
|
|
832
|
-
await writeFile(path.join(tmpRoot, "patch1.md"), orig);
|
|
833
|
-
const req = new Request(fileUrl("patch1.md"), {
|
|
834
|
-
method: "PATCH",
|
|
835
|
-
headers: { ...agentHeaders(MUTATE_TOKEN, "ai:mutate-agent"), "Content-Type": "application/json", "If-Match": sha256(orig) },
|
|
836
|
-
body: JSON.stringify({ find: "Hello world", replace: "Goodbye moon" }),
|
|
837
|
-
});
|
|
838
|
-
const res = await filePATCH(req, makeCtx(["patch1.md"]));
|
|
839
|
-
assert.equal(res.status, 200, await res.text());
|
|
840
|
-
const out = await readFile(path.join(tmpRoot, "patch1.md"), "utf-8");
|
|
841
|
-
assert.equal(out, "# Title\n\nGoodbye moon. Keep this.\n");
|
|
842
|
-
});
|
|
843
|
-
|
|
844
|
-
test("PATCH: missing If-Match → 412", async () => {
|
|
845
|
-
const orig = Buffer.from("alpha beta gamma");
|
|
846
|
-
await writeFile(path.join(tmpRoot, "patch2.txt"), orig);
|
|
847
|
-
const req = new Request(fileUrl("patch2.txt"), {
|
|
848
|
-
method: "PATCH",
|
|
849
|
-
headers: { ...agentHeaders(MUTATE_TOKEN, "ai:mutate-agent"), "Content-Type": "application/json" },
|
|
850
|
-
body: JSON.stringify({ find: "beta", replace: "BETA" }),
|
|
851
|
-
});
|
|
852
|
-
const res = await filePATCH(req, makeCtx(["patch2.txt"]));
|
|
853
|
-
assert.equal(res.status, 412);
|
|
854
|
-
});
|
|
855
|
-
|
|
856
|
-
test("PATCH: If-Match mismatch → 412", async () => {
|
|
857
|
-
const orig = Buffer.from("alpha beta gamma");
|
|
858
|
-
await writeFile(path.join(tmpRoot, "patch3.txt"), orig);
|
|
859
|
-
const req = new Request(fileUrl("patch3.txt"), {
|
|
860
|
-
method: "PATCH",
|
|
861
|
-
headers: { ...agentHeaders(MUTATE_TOKEN, "ai:mutate-agent"), "Content-Type": "application/json", "If-Match": "sha256:deadbeef" },
|
|
862
|
-
body: JSON.stringify({ find: "beta", replace: "BETA" }),
|
|
863
|
-
});
|
|
864
|
-
const res = await filePATCH(req, makeCtx(["patch3.txt"]));
|
|
865
|
-
assert.equal(res.status, 412);
|
|
866
|
-
});
|
|
867
|
-
|
|
868
|
-
test("PATCH: zero matches → 422 MATCH_COUNT_MISMATCH", async () => {
|
|
869
|
-
const orig = Buffer.from("alpha beta gamma");
|
|
870
|
-
await writeFile(path.join(tmpRoot, "patch4.txt"), orig);
|
|
871
|
-
const req = new Request(fileUrl("patch4.txt"), {
|
|
872
|
-
method: "PATCH",
|
|
873
|
-
headers: { ...agentHeaders(MUTATE_TOKEN, "ai:mutate-agent"), "Content-Type": "application/json", "If-Match": sha256(orig) },
|
|
874
|
-
body: JSON.stringify({ find: "nonexistent", replace: "x" }),
|
|
875
|
-
});
|
|
876
|
-
const res = await filePATCH(req, makeCtx(["patch4.txt"]));
|
|
877
|
-
assert.equal(res.status, 422);
|
|
878
|
-
const j = await res.json();
|
|
879
|
-
assert.equal(j.error, "MATCH_COUNT_MISMATCH");
|
|
880
|
-
assert.equal(j.found, 0);
|
|
881
|
-
});
|
|
882
|
-
|
|
883
|
-
test("PATCH: multiple matches but expected 1 → 422", async () => {
|
|
884
|
-
const orig = Buffer.from("foo bar foo baz foo");
|
|
885
|
-
await writeFile(path.join(tmpRoot, "patch5.txt"), orig);
|
|
886
|
-
const req = new Request(fileUrl("patch5.txt"), {
|
|
887
|
-
method: "PATCH",
|
|
888
|
-
headers: { ...agentHeaders(MUTATE_TOKEN, "ai:mutate-agent"), "Content-Type": "application/json", "If-Match": sha256(orig) },
|
|
889
|
-
body: JSON.stringify({ find: "foo", replace: "X" }),
|
|
890
|
-
});
|
|
891
|
-
const res = await filePATCH(req, makeCtx(["patch5.txt"]));
|
|
892
|
-
assert.equal(res.status, 422);
|
|
893
|
-
const j = await res.json();
|
|
894
|
-
assert.equal(j.found, 3);
|
|
895
|
-
});
|
|
896
|
-
|
|
897
|
-
test("PATCH: expectedOccurrences matching multi replaces all", async () => {
|
|
898
|
-
const orig = Buffer.from("foo bar foo baz foo");
|
|
899
|
-
await writeFile(path.join(tmpRoot, "patch6.txt"), orig);
|
|
900
|
-
const req = new Request(fileUrl("patch6.txt"), {
|
|
901
|
-
method: "PATCH",
|
|
902
|
-
headers: { ...agentHeaders(MUTATE_TOKEN, "ai:mutate-agent"), "Content-Type": "application/json", "If-Match": sha256(orig) },
|
|
903
|
-
body: JSON.stringify({ find: "foo", replace: "X", expectedOccurrences: 3 }),
|
|
904
|
-
});
|
|
905
|
-
const res = await filePATCH(req, makeCtx(["patch6.txt"]));
|
|
906
|
-
assert.equal(res.status, 200, await res.text());
|
|
907
|
-
const out = await readFile(path.join(tmpRoot, "patch6.txt"), "utf-8");
|
|
908
|
-
assert.equal(out, "X bar X baz X");
|
|
909
|
-
});
|
|
910
|
-
|
|
911
|
-
test("PATCH: empty find → 400", async () => {
|
|
912
|
-
const orig = Buffer.from("abc");
|
|
913
|
-
await writeFile(path.join(tmpRoot, "patch7.txt"), orig);
|
|
914
|
-
const req = new Request(fileUrl("patch7.txt"), {
|
|
915
|
-
method: "PATCH",
|
|
916
|
-
headers: { ...agentHeaders(MUTATE_TOKEN, "ai:mutate-agent"), "Content-Type": "application/json", "If-Match": sha256(orig) },
|
|
917
|
-
body: JSON.stringify({ find: "", replace: "x" }),
|
|
918
|
-
});
|
|
919
|
-
const res = await filePATCH(req, makeCtx(["patch7.txt"]));
|
|
920
|
-
assert.equal(res.status, 400);
|
|
921
|
-
});
|
|
922
|
-
|
|
923
|
-
test("PATCH: binary file → 415", async () => {
|
|
924
|
-
const orig = Buffer.from([0x00, 0x01, 0x02, 0xff, 0xfe, 0x00, 0x10]);
|
|
925
|
-
await writeFile(path.join(tmpRoot, "patch8.bin"), orig);
|
|
926
|
-
const req = new Request(fileUrl("patch8.bin"), {
|
|
927
|
-
method: "PATCH",
|
|
928
|
-
headers: { ...agentHeaders(MUTATE_TOKEN, "ai:mutate-agent"), "Content-Type": "application/json", "If-Match": sha256(orig) },
|
|
929
|
-
body: JSON.stringify({ find: "x", replace: "y" }),
|
|
930
|
-
});
|
|
931
|
-
const res = await filePATCH(req, makeCtx(["patch8.bin"]));
|
|
932
|
-
assert.equal(res.status, 415);
|
|
933
|
-
});
|
|
934
|
-
|
|
935
|
-
test("PATCH: nonexistent file → 404 (no create)", async () => {
|
|
936
|
-
const req = new Request(fileUrl("patch-missing.txt"), {
|
|
937
|
-
method: "PATCH",
|
|
938
|
-
headers: { ...agentHeaders(MUTATE_TOKEN, "ai:mutate-agent"), "Content-Type": "application/json", "If-Match": "sha256:abc" },
|
|
939
|
-
body: JSON.stringify({ find: "a", replace: "b" }),
|
|
940
|
-
});
|
|
941
|
-
const res = await filePATCH(req, makeCtx(["patch-missing.txt"]));
|
|
942
|
-
assert.equal(res.status, 404);
|
|
943
|
-
});
|
|
944
|
-
|
|
945
|
-
test("PATCH: .md emits file.rawWritten + reconciles (same path as PUT)", async () => {
|
|
946
|
-
const orig = Buffer.from("# H\n\noriginal paragraph here\n");
|
|
947
|
-
await writeFile(path.join(tmpRoot, "patch-md.md"), orig);
|
|
948
|
-
const req = new Request(fileUrl("patch-md.md"), {
|
|
949
|
-
method: "PATCH",
|
|
950
|
-
headers: { ...agentHeaders(MUTATE_TOKEN, "ai:mutate-agent"), "Content-Type": "application/json", "If-Match": sha256(orig) },
|
|
951
|
-
body: JSON.stringify({ find: "original", replace: "updated" }),
|
|
952
|
-
});
|
|
953
|
-
const res = await filePATCH(req, makeCtx(["patch-md.md"]));
|
|
954
|
-
assert.equal(res.status, 200, await res.text());
|
|
955
|
-
const sidecar = await readSidecar(tmpRoot, "patch-md.md");
|
|
956
|
-
assert.ok(sidecar, "sidecar created");
|
|
957
|
-
const events = sidecar!.events.filter((e) => e.type === "file.rawWritten");
|
|
958
|
-
assert.ok(events.length > 0, "file.rawWritten event present");
|
|
959
|
-
const newContent = await readFile(path.join(tmpRoot, "patch-md.md"), "utf-8");
|
|
960
|
-
assert.equal(sidecar!.fingerprint, sha256(Buffer.from(newContent)), "fingerprint matches new content (eager reconcile)");
|
|
961
|
-
});
|
|
962
|
-
|
|
963
|
-
test("PATCH: requires mutate scope", async () => {
|
|
964
|
-
const orig = Buffer.from("hello");
|
|
965
|
-
await writeFile(path.join(tmpRoot, "patch-scope.txt"), orig);
|
|
966
|
-
const req = new Request(fileUrl("patch-scope.txt"), {
|
|
967
|
-
method: "PATCH",
|
|
968
|
-
headers: { ...agentHeaders(READ_TOKEN, "ai:read-agent"), "Content-Type": "application/json", "If-Match": sha256(orig) },
|
|
969
|
-
body: JSON.stringify({ find: "hello", replace: "hi" }),
|
|
970
|
-
});
|
|
971
|
-
const res = await filePATCH(req, makeCtx(["patch-scope.txt"]));
|
|
972
|
-
assert.equal(res.status, 403);
|
|
973
|
-
});
|