copilot-operator 0.1.12 → 0.1.15

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.
Files changed (80) hide show
  1. package/README.md +9 -14
  2. package/dist/src/api/operator.controller.js +31 -42
  3. package/dist/src/api/operator.service.js +692 -364
  4. package/dist/src/api/saveToDesktop.js +1 -1
  5. package/dist/src/api/security.js +40 -10
  6. package/dist/src/api/server.js +75 -12
  7. package/dist/src/api/settings.js +156 -15
  8. package/dist/src/cli.js +108 -97
  9. package/dist/src/config/handWritten.js +36 -0
  10. package/dist/src/config/layout.js +13 -4
  11. package/dist/src/config/lockedPolicy.js +56 -22
  12. package/dist/src/config/schema.js +13 -84
  13. package/dist/src/context/desktopDir.js +44 -0
  14. package/dist/src/exec/authorizer.js +9 -1
  15. package/dist/src/exec/checks.js +240 -23
  16. package/dist/src/exec/dangerous.js +29 -5
  17. package/dist/src/exec/network.js +105 -28
  18. package/dist/src/exec/redaction.js +52 -9
  19. package/dist/src/exec/reportFile.js +4 -5
  20. package/dist/src/exec/runner.js +146 -13
  21. package/dist/src/exec/shells.js +41 -6
  22. package/dist/src/exec/workDir.js +4 -4
  23. package/dist/src/orchestrator/derivedChecks.js +1 -0
  24. package/dist/src/orchestrator/review.js +25 -3
  25. package/dist/src/orchestrator/taskRunner.js +482 -150
  26. package/dist/src/plan/brief.js +56 -44
  27. package/dist/src/plan/importPlan.js +18 -7
  28. package/dist/src/plan/schema.js +90 -31
  29. package/dist/src/plan/script.js +79 -90
  30. package/dist/src/plan/systemGuide.js +39 -182
  31. package/dist/src/protocol/parser.js +5 -1
  32. package/dist/src/session/debugExport.js +1 -1
  33. package/dist/src/session/exportRecord.js +1 -1
  34. package/dist/src/session/exports.js +52 -9
  35. package/dist/src/session/store.js +215 -80
  36. package/dist/src/vcs/git.js +77 -2
  37. package/dist/src/vcs/scope.js +29 -6
  38. package/dist/src/vcs/taskVcs.js +149 -23
  39. package/dist/web/404.html +2 -2
  40. package/dist/web/_next/static/chunks/36-94660f1cde2da529.js +1 -0
  41. package/dist/web/_next/static/chunks/549-9548d9a132c8c7d4.js +1 -0
  42. package/dist/web/_next/static/chunks/867-8b95ae984befc0e2.js +1 -0
  43. package/dist/web/_next/static/chunks/app/defaults/page-71a43ed69134b48d.js +1 -0
  44. package/dist/web/_next/static/chunks/app/history/page-d7314ac22d47a3ab.js +1 -0
  45. package/dist/web/_next/static/chunks/app/sessions/view/page-886f5fc99e146408.js +1 -0
  46. package/dist/web/_next/static/css/{a0642fdb8adf9edb.css → e2a2c9eb3c226313.css} +1 -1
  47. package/dist/web/appearance.html +2 -2
  48. package/dist/web/appearance.txt +12 -12
  49. package/dist/web/defaults.html +2 -2
  50. package/dist/web/defaults.txt +12 -12
  51. package/dist/web/history.html +2 -2
  52. package/dist/web/history.txt +12 -12
  53. package/dist/web/import.html +2 -2
  54. package/dist/web/import.txt +12 -12
  55. package/dist/web/index.html +2 -2
  56. package/dist/web/index.txt +12 -12
  57. package/dist/web/level1.html +2 -2
  58. package/dist/web/level1.txt +12 -12
  59. package/dist/web/presets.html +2 -2
  60. package/dist/web/presets.txt +12 -12
  61. package/dist/web/sessions/view.html +2 -2
  62. package/dist/web/sessions/view.txt +12 -12
  63. package/dist/web/system.html +2 -2
  64. package/dist/web/system.txt +12 -12
  65. package/package.json +18 -10
  66. package/prompts/level1.md +2 -1
  67. package/prompts/organisation.bg.md +0 -1
  68. package/prompts/organisation.en.md +0 -1
  69. package/dist/src/context/contextFiles.js +0 -295
  70. package/dist/src/context/desktopMirror.js +0 -135
  71. package/dist/src/context/projectMirror.js +0 -432
  72. package/dist/web/_next/static/chunks/295-04151ded0741d18d.js +0 -1
  73. package/dist/web/_next/static/chunks/36-c3dd06c6a459c114.js +0 -1
  74. package/dist/web/_next/static/chunks/549-a5a59a94f92bb3eb.js +0 -1
  75. package/dist/web/_next/static/chunks/867-b64c436f5b25f39e.js +0 -1
  76. package/dist/web/_next/static/chunks/app/defaults/page-5f180a88398be4c9.js +0 -1
  77. package/dist/web/_next/static/chunks/app/history/page-854e3dde672e38a2.js +0 -1
  78. package/dist/web/_next/static/chunks/app/sessions/view/page-cc99401da5173b36.js +0 -1
  79. /package/dist/web/_next/static/{bcBE4tY_aNh6kzLzN1luP → IwNZiBrEMAcXEUFj2miiU}/_buildManifest.js +0 -0
  80. /package/dist/web/_next/static/{bcBE4tY_aNh6kzLzN1luP → IwNZiBrEMAcXEUFj2miiU}/_ssgManifest.js +0 -0
@@ -1,25 +1,25 @@
1
1
  1:"$Sreact.fragment"
2
- 2:I[1205,["197","static/chunks/197-f7c33a0114fc3db3.js","867","static/chunks/867-b64c436f5b25f39e.js","549","static/chunks/549-a5a59a94f92bb3eb.js","177","static/chunks/app/layout-560bd6d173302e5a.js"],"themeScript"]
3
- 3:I[1205,["197","static/chunks/197-f7c33a0114fc3db3.js","867","static/chunks/867-b64c436f5b25f39e.js","549","static/chunks/549-a5a59a94f92bb3eb.js","177","static/chunks/app/layout-560bd6d173302e5a.js"],"AppearanceProvider"]
4
- 4:I[867,["197","static/chunks/197-f7c33a0114fc3db3.js","867","static/chunks/867-b64c436f5b25f39e.js","549","static/chunks/549-a5a59a94f92bb3eb.js","177","static/chunks/app/layout-560bd6d173302e5a.js"],"LanguageProvider"]
5
- 5:I[1035,["197","static/chunks/197-f7c33a0114fc3db3.js","867","static/chunks/867-b64c436f5b25f39e.js","549","static/chunks/549-a5a59a94f92bb3eb.js","177","static/chunks/app/layout-560bd6d173302e5a.js"],"DialogHost"]
6
- 6:I[8214,["197","static/chunks/197-f7c33a0114fc3db3.js","867","static/chunks/867-b64c436f5b25f39e.js","549","static/chunks/549-a5a59a94f92bb3eb.js","177","static/chunks/app/layout-560bd6d173302e5a.js"],"SkipLink"]
7
- 7:I[8197,["197","static/chunks/197-f7c33a0114fc3db3.js","867","static/chunks/867-b64c436f5b25f39e.js","549","static/chunks/549-a5a59a94f92bb3eb.js","177","static/chunks/app/layout-560bd6d173302e5a.js"],""]
8
- 8:I[8214,["197","static/chunks/197-f7c33a0114fc3db3.js","867","static/chunks/867-b64c436f5b25f39e.js","549","static/chunks/549-a5a59a94f92bb3eb.js","177","static/chunks/app/layout-560bd6d173302e5a.js"],"Nav"]
9
- 9:I[6657,["197","static/chunks/197-f7c33a0114fc3db3.js","867","static/chunks/867-b64c436f5b25f39e.js","549","static/chunks/549-a5a59a94f92bb3eb.js","177","static/chunks/app/layout-560bd6d173302e5a.js"],"GlobalApprovals"]
2
+ 2:I[1205,["197","static/chunks/197-f7c33a0114fc3db3.js","867","static/chunks/867-8b95ae984befc0e2.js","549","static/chunks/549-9548d9a132c8c7d4.js","177","static/chunks/app/layout-560bd6d173302e5a.js"],"themeScript"]
3
+ 3:I[1205,["197","static/chunks/197-f7c33a0114fc3db3.js","867","static/chunks/867-8b95ae984befc0e2.js","549","static/chunks/549-9548d9a132c8c7d4.js","177","static/chunks/app/layout-560bd6d173302e5a.js"],"AppearanceProvider"]
4
+ 4:I[867,["197","static/chunks/197-f7c33a0114fc3db3.js","867","static/chunks/867-8b95ae984befc0e2.js","549","static/chunks/549-9548d9a132c8c7d4.js","177","static/chunks/app/layout-560bd6d173302e5a.js"],"LanguageProvider"]
5
+ 5:I[1035,["197","static/chunks/197-f7c33a0114fc3db3.js","867","static/chunks/867-8b95ae984befc0e2.js","549","static/chunks/549-9548d9a132c8c7d4.js","177","static/chunks/app/layout-560bd6d173302e5a.js"],"DialogHost"]
6
+ 6:I[8214,["197","static/chunks/197-f7c33a0114fc3db3.js","867","static/chunks/867-8b95ae984befc0e2.js","549","static/chunks/549-9548d9a132c8c7d4.js","177","static/chunks/app/layout-560bd6d173302e5a.js"],"SkipLink"]
7
+ 7:I[8197,["197","static/chunks/197-f7c33a0114fc3db3.js","867","static/chunks/867-8b95ae984befc0e2.js","549","static/chunks/549-9548d9a132c8c7d4.js","177","static/chunks/app/layout-560bd6d173302e5a.js"],""]
8
+ 8:I[8214,["197","static/chunks/197-f7c33a0114fc3db3.js","867","static/chunks/867-8b95ae984befc0e2.js","549","static/chunks/549-9548d9a132c8c7d4.js","177","static/chunks/app/layout-560bd6d173302e5a.js"],"Nav"]
9
+ 9:I[6657,["197","static/chunks/197-f7c33a0114fc3db3.js","867","static/chunks/867-8b95ae984befc0e2.js","549","static/chunks/549-9548d9a132c8c7d4.js","177","static/chunks/app/layout-560bd6d173302e5a.js"],"GlobalApprovals"]
10
10
  a:I[5204,[],""]
11
11
  b:I[2642,[],""]
12
- c:I[7712,["197","static/chunks/197-f7c33a0114fc3db3.js","867","static/chunks/867-b64c436f5b25f39e.js","549","static/chunks/549-a5a59a94f92bb3eb.js","177","static/chunks/app/layout-560bd6d173302e5a.js"],"ScrollJump"]
12
+ c:I[7712,["197","static/chunks/197-f7c33a0114fc3db3.js","867","static/chunks/867-8b95ae984befc0e2.js","549","static/chunks/549-9548d9a132c8c7d4.js","177","static/chunks/app/layout-560bd6d173302e5a.js"],"ScrollJump"]
13
13
  d:I[7089,[],"ClientPageRoot"]
14
- e:I[3134,["867","static/chunks/867-b64c436f5b25f39e.js","549","static/chunks/549-a5a59a94f92bb3eb.js","186","static/chunks/app/system/page-3aa2a0fef5454e69.js"],"default"]
14
+ e:I[3134,["867","static/chunks/867-8b95ae984befc0e2.js","549","static/chunks/549-9548d9a132c8c7d4.js","186","static/chunks/app/system/page-3aa2a0fef5454e69.js"],"default"]
15
15
  11:I[433,[],"OutletBoundary"]
16
16
  13:I[9884,[],"AsyncMetadataOutlet"]
17
17
  15:I[433,[],"ViewportBoundary"]
18
18
  17:I[433,[],"MetadataBoundary"]
19
19
  18:"$Sreact.suspense"
20
20
  1a:I[3536,[],""]
21
- :HL["/_next/static/css/a0642fdb8adf9edb.css","style"]
22
- 0:{"P":null,"b":"bcBE4tY_aNh6kzLzN1luP","p":"","c":["","system"],"i":false,"f":[[["",{"children":["system",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/a0642fdb8adf9edb.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","suppressHydrationWarning":true,"data-theme":"light","data-textsize":"normal","data-contrast":"normal","data-motion":"normal","data-links":"plain","data-focus":"normal","children":[["$","head",null,{"children":["$","script",null,{"dangerouslySetInnerHTML":{"__html":"$2"}}]}],["$","body",null,{"children":["$","$L3",null,{"children":["$","$L4",null,{"children":["$","$L5",null,{"children":[["$","$L6",null,{}],["$","div",null,{"className":"wrap","children":[["$","header",null,{"className":"top","children":[["$","h1",null,{"children":["$","$L7",null,{"href":"/","children":"copilot-operator"}]}],["$","$L8",null,{}]]}],["$","$L9",null,{}],["$","main",null,{"id":"main","children":["$","$La",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$Lb",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]]}],["$","$Lc",null,{}]]}]}]}]}]]}]]}],{"children":["system",["$","$1","c",{"children":[null,["$","$La",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$Lb",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":["__PAGE__",["$","$1","c",{"children":[["$","$Ld",null,{"Component":"$e","searchParams":{},"params":{},"promises":["$@f","$@10"]}],null,["$","$L11",null,{"children":["$L12",["$","$L13",null,{"promise":"$@14"}]]}]]}],{},null,false]},null,false]},null,false],["$","$1","h",{"children":[null,[["$","$L15",null,{"children":"$L16"}],null],["$","$L17",null,{"children":["$","div",null,{"hidden":true,"children":["$","$18",null,{"fallback":null,"children":"$L19"}]}]}]]}],false]],"m":"$undefined","G":["$1a",[]],"s":false,"S":true}
21
+ :HL["/_next/static/css/e2a2c9eb3c226313.css","style"]
22
+ 0:{"P":null,"b":"IwNZiBrEMAcXEUFj2miiU","p":"","c":["","system"],"i":false,"f":[[["",{"children":["system",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/e2a2c9eb3c226313.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","suppressHydrationWarning":true,"data-theme":"light","data-textsize":"normal","data-contrast":"normal","data-motion":"normal","data-links":"plain","data-focus":"normal","children":[["$","head",null,{"children":["$","script",null,{"dangerouslySetInnerHTML":{"__html":"$2"}}]}],["$","body",null,{"children":["$","$L3",null,{"children":["$","$L4",null,{"children":["$","$L5",null,{"children":[["$","$L6",null,{}],["$","div",null,{"className":"wrap","children":[["$","header",null,{"className":"top","children":[["$","h1",null,{"children":["$","$L7",null,{"href":"/","children":"copilot-operator"}]}],["$","$L8",null,{}]]}],["$","$L9",null,{}],["$","main",null,{"id":"main","children":["$","$La",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$Lb",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]]}],["$","$Lc",null,{}]]}]}]}]}]]}]]}],{"children":["system",["$","$1","c",{"children":[null,["$","$La",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$Lb",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":["__PAGE__",["$","$1","c",{"children":[["$","$Ld",null,{"Component":"$e","searchParams":{},"params":{},"promises":["$@f","$@10"]}],null,["$","$L11",null,{"children":["$L12",["$","$L13",null,{"promise":"$@14"}]]}]]}],{},null,false]},null,false]},null,false],["$","$1","h",{"children":[null,[["$","$L15",null,{"children":"$L16"}],null],["$","$L17",null,{"children":["$","div",null,{"hidden":true,"children":["$","$18",null,{"fallback":null,"children":"$L19"}]}]}]]}],false]],"m":"$undefined","G":["$1a",[]],"s":false,"S":true}
23
23
  f:{}
24
24
  10:"$0:f:0:1:2:children:2:children:1:props:children:0:props:params"
25
25
  16:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "copilot-operator",
3
- "version": "0.1.12",
3
+ "version": "0.1.15",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Automates a Microsoft 365 Copilot chat: sends prompts, extracts commands, runs them, reports back.",
@@ -15,12 +15,10 @@
15
15
  "check:pacing": "tsx test/pacing.check.ts",
16
16
  "check:runner": "tsx test/runner.check.ts",
17
17
  "check:chat": "tsx test/chat.check.ts",
18
- "check:context": "tsx test/context.check.ts",
19
- "check:mirror": "tsx test/mirror.check.ts",
20
18
  "cop": "tsx src/cli.ts",
21
19
  "check:parser": "tsx test/parser.check.ts",
22
20
  "check:report": "tsx test/report.check.ts",
23
- "check": "npm run check:parser && npm run check:damage && npm run check:session && npm run check:report && npm run check:runner && npm run check:mirror && npm run check:mirrorsafety && npm run check:vcs && npm run check:plan && npm run check:checks && npm run check:workdir && npm run check:processes && npm run check:environment && npm run check:clock && npm run check:crash && npm run check:clipboard && npm run check:restart && npm run check:review && npm run check:chat && npm run check:context && npm run check:pacing && npm run check:shells && npm run check:save && npm run check:guide && npm run check:bundle && npm run check:runname && npm run check:dangerous && npm run check:programs && npm run check:manifest && npm run check:lock && npm run check:isolation && npm run check:security && npm run check:update && npm run check:persona && npm run check:script && npm run check:batch && npm run check:confinement && npm run check:network && npm run check:hardening && npm run check:package && npm run check:models && npm run check:continue && npm run check:startfrom && npm run check:diff && npm run check:integrity && npm run check:scope && npm run check:progress && npm run check:metrics && npm run check:modelpicker && npm run check:formatrepair && npm run check:intent && npm run check:sync && npm run check:desktop && npm run check:e2e-run && npm run check:e2e-continue && npm run check:e2e-interrupt && npm run check:e2e-vcs",
21
+ "check": "npm run check:parser && npm run check:damage && npm run check:session && npm run check:report && npm run check:runner && npm run check:vcs && npm run check:plan && npm run check:checks && npm run check:workdir && npm run check:processes && npm run check:environment && npm run check:clock && npm run check:crash && npm run check:clipboard && npm run check:restart && npm run check:review && npm run check:chat && npm run check:pacing && npm run check:shells && npm run check:save && npm run check:guide && npm run check:bundle && npm run check:runname && npm run check:dangerous && npm run check:programs && npm run check:manifest && npm run check:lock && npm run check:isolation && npm run check:security && npm run check:update && npm run check:persona && npm run check:script && npm run check:batch && npm run check:confinement && npm run check:network && npm run check:hardening && npm run check:package && npm run check:models && npm run check:continue && npm run check:startfrom && npm run check:diff && npm run check:integrity && npm run check:scope && npm run check:progress && npm run check:metrics && npm run check:modelpicker && npm run check:formatrepair && npm run check:intent && npm run check:sync && npm run check:e2e-run && npm run check:e2e-continue && npm run check:e2e-interrupt && npm run check:e2e-vcs && npm run check:noattach && npm run check:guard && npm run check:gate && npm run check:e2e-batch && npm run check:e2e-review && npm run check:e2e-branches && npm run check:e2e-loop && npm run check:transport && npm run check:e2e-plan && npm run check:exec && npm run check:pins",
24
22
  "probe": "tsx scripts/probe-locators.ts",
25
23
  "check:damage": "tsx test/damage.check.ts",
26
24
  "api": "npm run build && node dist/src/api/main.js",
@@ -74,21 +72,31 @@
74
72
  "check:formatrepair": "tsx test/formatrepair.check.ts",
75
73
  "check:intent": "tsx test/intent.check.ts",
76
74
  "check:sync": "tsx test/sync.check.ts",
77
- "check:desktop": "tsx test/desktop.check.ts",
78
75
  "check:e2e-run": "tsx test/e2e-run.check.ts",
79
76
  "check:e2e-continue": "tsx test/e2e-continue.check.ts",
80
77
  "check:e2e-interrupt": "tsx test/e2e-interrupt.check.ts",
81
- "check:ui": "node scripts/build-package.mjs && tsx test/ui.check.ts",
82
- "check:all": "npm run check && npm run check:ui",
83
- "check:mirrorsafety": "tsx test/mirrorsafety.check.ts",
84
- "check:e2e-vcs": "tsx test/e2e-vcs.check.ts"
78
+ "check:ui": "node scripts/build-package.mjs && tsx test/ui.check.ts && tsx test/ui-flows.check.ts",
79
+ "check:all": "npm run check && npm run check:ui && npm run check:release",
80
+ "check:e2e-vcs": "tsx test/e2e-vcs.check.ts",
81
+ "check:noattach": "tsx test/noattach.check.ts",
82
+ "check:guard": "tsx test/guard.check.ts",
83
+ "check:gate": "tsx test/gate.check.ts",
84
+ "check:e2e-batch": "tsx test/e2e-batch.check.ts",
85
+ "check:e2e-review": "tsx test/e2e-review.check.ts",
86
+ "check:e2e-branches": "tsx test/e2e-branches.check.ts",
87
+ "check:e2e-loop": "tsx test/e2e-loop.check.ts",
88
+ "check:transport": "tsx test/transport.check.ts",
89
+ "check:e2e-plan": "tsx test/e2e-plan.check.ts",
90
+ "check:exec": "tsx test/exec.check.ts",
91
+ "check:pins": "tsx test/pins.check.ts",
92
+ "check:release": "tsx test/release.check.ts",
93
+ "check:ui-flows": "tsx test/ui-flows.check.ts"
85
94
  },
86
95
  "dependencies": {
87
96
  "@nestjs/common": "^12.0.3",
88
97
  "@nestjs/core": "^12.0.3",
89
98
  "@nestjs/platform-express": "^12.0.3",
90
99
  "commander": "^15.0.0",
91
- "ignore": "^7.0.9",
92
100
  "playwright": "^1.63.0",
93
101
  "reflect-metadata": "^0.2.2",
94
102
  "rxjs": "^7.8.2",
package/prompts/level1.md CHANGED
@@ -20,7 +20,8 @@ wins, and you say so in `notes`.
20
20
  2. **Plan the task.** In your first reply to a task, before anything is changed, put a short
21
21
  numbered plan in `notes`: the stages you will go through, in order, and how you will verify
22
22
  the result. Your first reply still carries steps — begin with the read-only look at the
23
- project the plan needs. Keep to the plan; when it has to change, say so in `notes` and why.
23
+ project the plan needs. No file of the project is attached to this conversation: you read the
24
+ project by running commands in its folder (list it, open the files you need, look at the git log). Keep to the plan; when it has to change, say so in `notes` and why.
24
25
  When you are asked to **continue** a task, start from the plan: say in `notes` which stages
25
26
  are done, which one you are on, and carry on from there.
26
27
  3. **Plan a step.** Decide the next concrete thing to run. Prefer read-only diagnosis before
@@ -37,7 +37,6 @@
37
37
  "prerequisites": "какво трябва да е инсталирано, пуснато или зададено, преди да се строи: среди, услуги, променливи на средата",
38
38
  "runsWith": "командите, с които се строи, пуска и тества",
39
39
  "knownPitfalls": "какво се чупи тук, без да личи от кода, и какво да се направи тогава",
40
- "howToReachItFromTheChat": "прикачени файлове или огледалото на Desktop-а в Desktop/copilot-operator-context/<проект>/, където файл е именуван по пътя си с -- за всяка папка и .txt накрая, а отпред е префиксът, който приложението дава на този проект: example--src--main.ts.txt е src/main.ts",
41
40
  "versionControl": "дали runner-ът може да прави клонове и комити тук"
42
41
  }
43
42
  ]
@@ -37,7 +37,6 @@
37
37
  "prerequisites": "what must be installed, running or set before it builds: runtimes, services, environment variables",
38
38
  "runsWith": "the commands that build, start and test it",
39
39
  "knownPitfalls": "what goes wrong here that is not obvious from the code, and what to do about it",
40
- "howToReachItFromTheChat": "attached files, or the Desktop mirror at Desktop/copilot-operator-context/<project>/ where a file is named after its path with -- for each folder and .txt on the end, with the prefix the app gives this project in front: example--src--main.ts.txt is src/main.ts",
41
40
  "versionControl": "whether the runner may branch and commit here"
42
41
  }
43
42
  ]
@@ -1,295 +0,0 @@
1
- /**
2
- * Project files as chat context, via Context Picker.
3
- *
4
- * The user already maintains https://github.com/ivan-markov-666/context-picker, a VS Code
5
- * and Visual Studio extension for ticking the files a project should hand to an LLM. It is
6
- * not merely reusable here, it was clearly built with this exact case in mind. Its
7
- * `copySelectionToDir` already carries:
8
- *
9
- * appendTxtExtension "so that tools which block source extensions (e.g. Microsoft 365
10
- * Copilot) accept the uploads" - exactly the constraint we hit,
11
- * since the chat's upload input rejects .ps1, .ts and friends
12
- * pathInName flattens src/lib/app.ts into src--lib--app.ts, "lets a flat upload
13
- * (e.g. OneDrive) keep the original folder structure in the name"
14
- * syncOnly mirrors instead of wiping, "minimises churn for a synced folder
15
- * such as OneDrive"
16
- *
17
- * So this module does not reimplement any of that. It shells out to the extension's own
18
- * bridge CLI and then treats the export folder as the bot's context source.
19
- *
20
- * **Everything here happens in the file system, never through the browser.** The user points
21
- * at the project root, ticks files in the extension, and the extension copies them into one
22
- * folder on the Desktop. No Playwright, no upload form, no web picker in this path.
23
- *
24
- * **The folder is updated incrementally.** The extension's `syncOnly` mode writes only new
25
- * and changed files and deletes the ones no longer selected, so a re-export does not rewrite
26
- * a folder full of unchanged files. On top of that this module keeps a manifest of content
27
- * hashes, so the bot can tell what actually changed since the last run and avoid handing the
28
- * chat the same files again.
29
- *
30
- * If the Desktop happens to be backed up by OneDrive (Known Folder Move), the same folder
31
- * also ends up in the cloud at no extra cost. That is a bonus, not the mechanism:
32
- * `desktopIsSynced()` reports it, and nothing depends on it.
33
- *
34
- * Division of labour, which is the point of the request: **the human chooses the files** in
35
- * the extension's checkbox UI, so they know exactly what the chat can see. The bot never
36
- * decides what to expose.
37
- */
38
- import { spawn } from 'node:child_process';
39
- import { readdir, stat, readFile, writeFile } from 'node:fs/promises';
40
- import { existsSync } from 'node:fs';
41
- import { createHash } from 'node:crypto';
42
- import { join, extname, resolve, sep } from 'node:path';
43
- import { homedir } from 'node:os';
44
- /**
45
- * Extensions the Copilot upload input accepts, restricted to what a code project produces.
46
- * Taken from the live `accept` attribute of `#upload-file-button`.
47
- *
48
- * Note what is absent: .ts, .ps1, .sh, .go, .rb, .yml are not all there, which is precisely
49
- * why Context Picker's `appendTxtExtension` exists. Exported files end in `.txt` and keep
50
- * their original extension visible in the name, e.g. `src--app.ts.txt`.
51
- * The flattening itself lives in `projectMirror.ts`; this module only consumes the folder.
52
- */
53
- export const COPILOT_ACCEPTED = new Set([
54
- '.txt', '.md', '.json', '.csv', '.tsv', '.xml', '.log', '.ini', '.config',
55
- '.html', '.htm', '.css', '.js', '.jsx', '.py', '.java', '.cs', '.cpp', '.c', '.h',
56
- '.sql', '.php', '.pl', '.rs', '.lua', '.dart', '.yaml', '.sh', '.bash', '.tsx',
57
- '.pdf', '.docx', '.xlsx', '.pptx',
58
- ]);
59
- export const DEFAULT_MAX_FILES = 20;
60
- export const DEFAULT_MAX_TOTAL_BYTES = 25 * 1024 * 1024;
61
- /**
62
- * The local OneDrive root.
63
- *
64
- * Windows sets `OneDrive` for the signed-in account, plus `OneDriveCommercial` for a work
65
- * or school account when both are present. Work first: that is the tenant the chat lives in.
66
- */
67
- export function resolveOneDriveRoot(env = process.env) {
68
- const candidates = [env.OneDriveCommercial, env.OneDrive, env.OneDriveConsumer];
69
- for (const c of candidates) {
70
- if (c && c.trim())
71
- return c;
72
- }
73
- const guess = join(homedir(), 'OneDrive');
74
- return guess;
75
- }
76
- /**
77
- * The Desktop folder.
78
- *
79
- * Candidates are tried in order and the first that exists wins, which is what makes this
80
- * correct under OneDrive's Known Folder Move: when the Desktop is backed up, the real
81
- * Desktop is `<OneDrive>\Desktop` and `%USERPROFILE%\Desktop` may not exist at all. No
82
- * registry read is needed for that.
83
- */
84
- export function resolveDesktopDir(env = process.env) {
85
- const candidates = [
86
- env.OneDriveCommercial ? join(env.OneDriveCommercial, 'Desktop') : null,
87
- env.OneDrive ? join(env.OneDrive, 'Desktop') : null,
88
- join(env.USERPROFILE ?? homedir(), 'Desktop'),
89
- ].filter((c) => Boolean(c));
90
- for (const c of candidates) {
91
- if (existsSync(c))
92
- return c;
93
- }
94
- return candidates[candidates.length - 1];
95
- }
96
- /** Default export folder: one folder on the Desktop. */
97
- export function defaultExportDir(env = process.env) {
98
- return join(resolveDesktopDir(env), 'copilot-operator-context');
99
- }
100
- /**
101
- * Whether the resolved Desktop is inside OneDrive, i.e. whether the export folder also
102
- * reaches the cloud for free. Informational only: nothing in the bot depends on it.
103
- */
104
- export function desktopIsSynced(env = process.env) {
105
- return isInsideOneDrive(resolveDesktopDir(env), env);
106
- }
107
- /**
108
- * Whether a folder sits inside a local OneDrive root, i.e. whether saving into it will
109
- * actually reach the cloud. This is the whole mechanism, so it is worth checking loudly:
110
- * an export folder outside OneDrive silently produces local-only copies, and the mistake
111
- * only shows up later as a chat that cannot see the project.
112
- */
113
- export function isInsideOneDrive(dir, env = process.env) {
114
- const roots = [env.OneDriveCommercial, env.OneDrive, env.OneDriveConsumer].filter((r) => Boolean(r && r.trim()));
115
- const target = resolve(dir).toLowerCase();
116
- return roots.some((r) => {
117
- const root = resolve(r).toLowerCase();
118
- return target === root || target.startsWith(root + sep);
119
- });
120
- }
121
- /** Lists what is currently in the export folder, newest-agnostic, sorted by name. */
122
- export async function listContextFiles(exportDir) {
123
- let names;
124
- try {
125
- names = await readdir(exportDir);
126
- }
127
- catch {
128
- return [];
129
- }
130
- const out = [];
131
- for (const name of names.sort()) {
132
- const path = join(exportDir, name);
133
- const s = await stat(path).catch(() => null);
134
- if (!s?.isFile())
135
- continue;
136
- out.push({
137
- path,
138
- name,
139
- bytes: s.size,
140
- accepted: COPILOT_ACCEPTED.has(extname(name).toLowerCase()),
141
- });
142
- }
143
- return out;
144
- }
145
- /**
146
- * Validates an export folder before anything is attached. Refusing early is much better
147
- * than a half-uploaded selection, because a partial context makes Copilot answer
148
- * confidently about files it never saw.
149
- */
150
- export async function checkSelection(exportDir, cfg = {}) {
151
- const maxFiles = cfg.maxFiles ?? DEFAULT_MAX_FILES;
152
- const maxTotalBytes = cfg.maxTotalBytes ?? DEFAULT_MAX_TOTAL_BYTES;
153
- const files = await listContextFiles(exportDir);
154
- const totalBytes = files.reduce((n, f) => n + f.bytes, 0);
155
- const problems = [];
156
- if (files.length === 0) {
157
- problems.push(`No files in ${exportDir}. Export a selection from Context Picker first.`);
158
- }
159
- if (files.length > maxFiles) {
160
- problems.push(`${files.length} files exceeds maxFiles=${maxFiles}.`);
161
- }
162
- if (totalBytes > maxTotalBytes) {
163
- problems.push(`${(totalBytes / 1048576).toFixed(1)} MB exceeds maxTotalBytes.`);
164
- }
165
- const rejected = files.filter((f) => !f.accepted);
166
- if (rejected.length > 0) {
167
- problems.push(`The chat upload rejects ${rejected.length} file(s): ${rejected.map((f) => f.name).join(', ')}. ` +
168
- `Re-export with Context Picker's "append .txt" option.`);
169
- }
170
- const env = files.filter((f) => /(^|__|\.)env(\.|$)/i.test(f.name));
171
- if (env.length > 0) {
172
- problems.push(`Refusing to attach what look like env files: ${env.map((f) => f.name).join(', ')}.`);
173
- }
174
- const notes = [];
175
- if (!isInsideOneDrive(exportDir)) {
176
- // Not a problem: the Desktop is the target, and the bot reads it locally. It only means
177
- // the folder is not additionally backed up to the cloud.
178
- notes.push(`${exportDir} is not inside OneDrive, so these copies stay on this machine only. ` +
179
- `Turn on OneDrive's Desktop backup, or point exportDir under ` +
180
- `${resolveOneDriveRoot() ?? 'your OneDrive root'}, if you also want them in the cloud.`);
181
- }
182
- return { ok: problems.length === 0, files, totalBytes, problems, notes };
183
- }
184
- export const MANIFEST_NAME = '.cop-manifest.json';
185
- async function sha256OfFile(path) {
186
- const buf = await readFile(path);
187
- return createHash('sha256').update(buf).digest('hex');
188
- }
189
- export async function buildManifest(exportDir) {
190
- const files = {};
191
- for (const f of await listContextFiles(exportDir)) {
192
- if (f.name === MANIFEST_NAME)
193
- continue;
194
- files[f.name] = { sha256: await sha256OfFile(f.path), bytes: f.bytes };
195
- }
196
- return { updatedAt: new Date().toISOString(), exportDir: resolve(exportDir), files };
197
- }
198
- export async function readManifest(manifestPath) {
199
- try {
200
- return JSON.parse(await readFile(manifestPath, 'utf8'));
201
- }
202
- catch {
203
- return null;
204
- }
205
- }
206
- export async function writeManifest(manifestPath, manifest) {
207
- await writeFile(manifestPath, JSON.stringify(manifest, null, 2), 'utf8');
208
- }
209
- /** Compares the folder as it is now against the manifest from the previous run. */
210
- export function diffAgainstManifest(current, previous) {
211
- const prev = previous?.files ?? {};
212
- const added = [];
213
- const changed = [];
214
- const unchanged = [];
215
- for (const [name, info] of Object.entries(current.files)) {
216
- const before = prev[name];
217
- if (!before)
218
- added.push(name);
219
- else if (before.sha256 !== info.sha256)
220
- changed.push(name);
221
- else
222
- unchanged.push(name);
223
- }
224
- const removed = Object.keys(prev).filter((name) => !(name in current.files));
225
- return {
226
- added: added.sort(),
227
- changed: changed.sort(),
228
- removed: removed.sort(),
229
- unchanged: unchanged.sort(),
230
- toAttach: [...added, ...changed].sort(),
231
- };
232
- }
233
- /** One line for the log and for the covering message to Copilot. */
234
- export function describeDiff(diff) {
235
- if (diff.toAttach.length === 0 && diff.removed.length === 0) {
236
- return 'Project context unchanged since the last run.';
237
- }
238
- const bits = [];
239
- if (diff.added.length)
240
- bits.push(`${diff.added.length} new`);
241
- if (diff.changed.length)
242
- bits.push(`${diff.changed.length} changed`);
243
- if (diff.removed.length)
244
- bits.push(`${diff.removed.length} removed`);
245
- if (diff.unchanged.length)
246
- bits.push(`${diff.unchanged.length} unchanged`);
247
- return `Project context: ${bits.join(', ')}.`;
248
- }
249
- /**
250
- * Bridge mode: re-export a saved selection by invoking Context Picker's own CLI, so a run
251
- * always starts from current file contents without the user re-ticking anything.
252
- *
253
- * Defaults are chosen for this use case and should not be changed lightly:
254
- * appendTxt true the chat rejects source extensions
255
- * pathInName true a flat folder otherwise loses which `index.ts` is which
256
- * syncOnly true OneDrive should not re-upload files that did not change
257
- * includeEnvFiles false never ship secrets to a cloud chat
258
- */
259
- export async function exportSelection(cfg) {
260
- if (!cfg.bridgePath)
261
- throw new Error('contextPicker.bridgePath is not configured');
262
- if (!cfg.rootDir)
263
- throw new Error('contextPicker.rootDir is required when pathInName is used');
264
- const request = {
265
- mode: 'copyfiles',
266
- rootDir: resolve(cfg.rootDir),
267
- targetDir: resolve(cfg.exportDir),
268
- includedFiles: (cfg.includedFiles ?? []).map((f) => resolve(f)),
269
- appendTxt: true,
270
- pathInName: true,
271
- separator: '__',
272
- syncOnly: true,
273
- stripComments: cfg.stripComments ?? false,
274
- removeBlankLines: cfg.removeBlankLines ?? false,
275
- includeEnvFiles: cfg.includeEnvFiles ?? false,
276
- };
277
- return await new Promise((resolvePromise, reject) => {
278
- const child = spawn(process.execPath, [cfg.bridgePath], {
279
- stdio: ['pipe', 'pipe', 'pipe'],
280
- windowsHide: true,
281
- });
282
- let out = '';
283
- let err = '';
284
- child.stdout.on('data', (c) => (out += c.toString('utf8')));
285
- child.stderr.on('data', (c) => (err += c.toString('utf8')));
286
- child.on('error', reject);
287
- child.on('close', (code) => {
288
- if (code !== 0)
289
- return reject(new Error(`context-picker bridge exited ${code}: ${err.trim()}`));
290
- const written = Number.parseInt(out.trim(), 10);
291
- resolvePromise({ written: Number.isFinite(written) ? written : 0, raw: out.trim() });
292
- });
293
- child.stdin.end(JSON.stringify(request), 'utf8');
294
- });
295
- }
@@ -1,135 +0,0 @@
1
- /**
2
- * The projects on the Desktop: one folder each under `copilot-operator-context`.
3
- *
4
- * There used to be one flat folder for whatever the running session was mirroring. That was
5
- * right for one project and wrong for three: every session's sync deleted the previous
6
- * project's files ("no longer part of the selection"), and once the copies reached the chat
7
- * as attachments, `src--main.ts.txt` from the API and `src--main.ts.txt` from the web app
8
- * were the same name. So each project now has its own folder, named after it, and every
9
- * flattened file carries the project's name in front — a file is then identifiable on the
10
- * Desktop, in OneDrive and in the chat.
11
- *
12
- * Which projects: the ones on the Settings page — the default and the named others — each
13
- * with its own selection of directories. The switch for keeping them all on the Desktop is
14
- * `project.mirrorToDesktop`; a session's own mirror (the files attached to its first message)
15
- * uses the same folder for its project whether or not the switch is on.
16
- */
17
- import { basename, join, resolve } from 'node:path';
18
- import { readdir, rm, stat } from 'node:fs/promises';
19
- import { mirrorProject } from './projectMirror.js';
20
- import { defaultExportDir } from './contextFiles.js';
21
- const EMPTY_SELECTION = { includeDirs: [], excludeDirs: [], respectGitignore: true, includeEnvFiles: false };
22
- function sameFolder(a, b) {
23
- const norm = (p) => resolve(p).replace(/[\\/]+$/, '').toLowerCase();
24
- return a.trim() !== '' && b.trim() !== '' && norm(a) === norm(b);
25
- }
26
- /** A project's name as a folder: what the operator typed, made safe for a file system. */
27
- export function safeProjectName(name) {
28
- return name.replace(/[<>:"/\\|?*\u0000-\u001f]/g, '-').replace(/\s+/g, ' ').trim().replace(/\.+$/, '') || 'project';
29
- }
30
- /** The projects on the Settings page, the default first, each with its selection. */
31
- export function knownProjects(cfg) {
32
- const out = [];
33
- const root = (cfg.project?.rootDir ?? '').trim();
34
- if (root) {
35
- out.push({ name: (cfg.project?.name ?? '').trim() || basename(root) || 'project', rootDir: root, isDefault: true, mirror: cfg.project?.mirror ?? EMPTY_SELECTION, desktop: cfg.project?.desktop !== false });
36
- }
37
- for (const o of cfg.project?.others ?? [])
38
- out.push({ name: o.name, rootDir: o.rootDir, isDefault: false, mirror: o.mirror ?? EMPTY_SELECTION, desktop: o.desktop !== false });
39
- return out;
40
- }
41
- /** What to call the folder for a session's project: its Settings name, else the folder's own. */
42
- export function projectNameFor(rootDir, cfg) {
43
- const known = knownProjects(cfg).find((p) => sameFolder(p.rootDir, rootDir));
44
- return safeProjectName(known?.name ?? basename(resolve(rootDir)) ?? 'project');
45
- }
46
- /** The Desktop folder that holds one subfolder per project. */
47
- export function contextRoot(cfg) {
48
- return cfg.resolved.mirrorTargetDir ?? defaultExportDir();
49
- }
50
- export function projectTargetDir(cfg, projectName) {
51
- return join(contextRoot(cfg), safeProjectName(projectName));
52
- }
53
- /**
54
- * Files left directly in the context root by the flat layout this replaced. They would
55
- * otherwise sit in OneDrive as stale copies with no project in their name, which is exactly
56
- * the confusion the folders exist to end. Only files, only ours by the look of them.
57
- */
58
- export async function removeLegacyFlatMirror(cfg) {
59
- const root = contextRoot(cfg);
60
- const removed = [];
61
- for (const entry of await readdir(root).catch(() => [])) {
62
- const path = join(root, entry);
63
- const info = await stat(path).catch(() => null);
64
- if (!info?.isFile())
65
- continue;
66
- if (!/\.txt$/i.test(entry) && !entry.includes('--'))
67
- continue;
68
- await rm(path, { force: true }).catch(() => undefined);
69
- removed.push(entry);
70
- }
71
- return removed;
72
- }
73
- /** Brings one project's Desktop folder in line with its selection. */
74
- export async function mirrorKnownProject(cfg, project) {
75
- const name = safeProjectName(project.name);
76
- const targetDir = projectTargetDir(cfg, name);
77
- const result = await mirrorProject({
78
- rootDir: project.rootDir,
79
- includeDirs: project.mirror.includeDirs.length ? project.mirror.includeDirs : ['.'],
80
- excludeDirs: project.mirror.excludeDirs,
81
- targetDir,
82
- namePrefix: name,
83
- separator: cfg.projectMirror.separator,
84
- txtMode: cfg.projectMirror.txtMode,
85
- respectGitignore: project.mirror.respectGitignore,
86
- ignoreDirs: cfg.projectMirror.ignoreDirs,
87
- includeEnvFiles: project.mirror.includeEnvFiles,
88
- maxFileBytes: cfg.projectMirror.maxFileBytes,
89
- });
90
- return { name, rootDir: project.rootDir, targetDir, result };
91
- }
92
- /**
93
- * Every known project that is kept on the Desktop, when the switch is on; nothing when it is off.
94
- *
95
- * The switch is the master control and each project has its own tick under it. A project whose
96
- * tick is off has its Desktop folder removed here, for the same reason the switch going off
97
- * removes them all: a stale copy of a code base in the cloud is worse than none.
98
- */
99
- export async function mirrorAllProjects(cfg) {
100
- if (!cfg.project?.mirrorToDesktop)
101
- return [];
102
- await removeLegacyFlatMirror(cfg);
103
- const out = [];
104
- for (const p of knownProjects(cfg)) {
105
- // One project whose selection is wrong keeps its old copies and says so; the others are still refreshed.
106
- if (p.desktop) {
107
- out.push(await mirrorKnownProject(cfg, p).catch((e) => ({
108
- name: safeProjectName(p.name),
109
- rootDir: p.rootDir,
110
- targetDir: projectTargetDir(cfg, p.name),
111
- problem: e.message,
112
- })));
113
- }
114
- else
115
- await removeProjectFolder(cfg, p);
116
- }
117
- return out;
118
- }
119
- async function removeProjectFolder(cfg, p) {
120
- const dir = projectTargetDir(cfg, p.name);
121
- const info = await stat(dir).catch(() => null);
122
- if (!info?.isDirectory())
123
- return false;
124
- await rm(dir, { recursive: true, force: true });
125
- return true;
126
- }
127
- /** Removes every known project's folder from the Desktop. The switch going off. */
128
- export async function removeProjectMirrors(cfg) {
129
- const removed = [];
130
- for (const p of knownProjects(cfg)) {
131
- if (await removeProjectFolder(cfg, p))
132
- removed.push(projectTargetDir(cfg, p.name));
133
- }
134
- return removed;
135
- }