@myagentroam/node 0.9.62 → 0.9.65
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/LICENSE +202 -0
- package/NOTICE +4 -0
- package/dist/capabilities.js +110 -2
- package/dist/claude-agent-sdk.js +330 -2
- package/dist/claude-channel.js +44 -2
- package/dist/codex-app-server.js +510 -2
- package/dist/config.js +106 -2
- package/dist/connector/node-connector-options.js +1 -2
- package/dist/connector/node-operation-router.js +22 -2
- package/dist/connector.js +1736 -2
- package/dist/control-outbound-scheduler.js +153 -2
- package/dist/database.js +639 -2
- package/dist/fake-runner.js +129 -2
- package/dist/git-askpass.js +12 -0
- package/dist/git-execution-helper.js +266 -0
- package/dist/health.js +3 -2
- package/dist/main.js +57 -2
- package/dist/mcp-installation-model.js +1 -2
- package/dist/migrations/v004.js +55 -3
- package/dist/migrations/v005.js +13 -3
- package/dist/migrations/v006.js +90 -3
- package/dist/migrations/v007.js +22 -3
- package/dist/migrations/v008.js +18 -3
- package/dist/migrations/v009.js +70 -4
- package/dist/migrations/v010.js +43 -3
- package/dist/migrations/v011.js +20 -3
- package/dist/migrations/v012.js +8 -0
- package/dist/native-jsonl-reader.js +186 -2
- package/dist/native-session-history.js +1143 -2
- package/dist/opencode-runtime.js +80 -2
- package/dist/opencode-server.js +259 -2
- package/dist/operational.js +122 -2
- package/dist/process-tree.js +18 -2
- package/dist/ripgrep-binary.js +31 -2
- package/dist/rotating-log.js +67 -2
- package/dist/runner/abstract-runner.js +260 -2
- package/dist/runner/claude/managed-run-controller.js +355 -2
- package/dist/runner/claude/tool-normalizer.js +120 -2
- package/dist/runner/claude-code-runner.js +303 -2
- package/dist/runner/codex/composer-image-markers.js +8 -2
- package/dist/runner/codex/conversation-parser.js +913 -2
- package/dist/runner/codex/managed-run-controller.js +494 -2
- package/dist/runner/codex/tool-normalizer.js +214 -2
- package/dist/runner/codex-runner.js +585 -2
- package/dist/runner/fake-test-runner.js +47 -2
- package/dist/runner/mar-agent/conversation-normalizer.js +644 -2
- package/dist/runner/mar-agent/executor-stdio-client-transport.js +139 -2
- package/dist/runner/mar-agent/generated-images.js +41 -2
- package/dist/runner/mar-agent/history-projection.js +106 -2
- package/dist/runner/mar-agent/hosted-conversation-runtime.js +446 -2
- package/dist/runner/mar-agent/managed-run-controller.js +785 -2
- package/dist/runner/mar-agent/mcp-client-pool.js +233 -2
- package/dist/runner/mar-agent/runtime-model-factory.js +326 -2
- package/dist/runner/mar-agent-runner.js +458 -2
- package/dist/runner/opencode/conversation-parser.js +221 -2
- package/dist/runner/opencode/managed-run-controller.js +588 -2
- package/dist/runner/opencode/tool-normalizer.js +80 -2
- package/dist/runner/opencode-runner.js +426 -2
- package/dist/runner/runner-registry.js +87 -2
- package/dist/runner-command-engine.js +71 -2
- package/dist/runner-profiles.js +323 -2
- package/dist/runner-usage.js +190 -2
- package/dist/runtime-command-detector.js +73 -2
- package/dist/runtime-model.js +1 -2
- package/dist/runtime-state.js +1509 -2
- package/dist/service/attachment-domain-state.js +7 -2
- package/dist/service/bubblewrap-sandbox-executor.js +543 -2
- package/dist/service/continuous-run-coordinator.js +425 -2
- package/dist/service/conversation-history-service.js +552 -2
- package/dist/service/conversation-hosting-capability-service.js +61 -2
- package/dist/service/conversation-hosting-enablement-service.js +91 -2
- package/dist/service/conversation-hosting-maintenance-service.js +174 -2
- package/dist/service/conversation-segment-service.js +162 -2
- package/dist/service/conversation-template-node-service.js +992 -2
- package/dist/service/external-session-resume-service.js +152 -2
- package/dist/service/fake-managed-run-service.js +60 -2
- package/dist/service/git-execution-service.js +418 -0
- package/dist/service/git-operation-service.js +57 -0
- package/dist/service/hosted-conversation-node-service.js +1982 -2
- package/dist/service/hosted-conversation-store.js +1405 -2
- package/dist/service/managed-runner-session-service.js +35 -2
- package/dist/service/mar-agent-model-transport-test-service.js +88 -2
- package/dist/service/mcp-installation-verifier.js +87 -2
- package/dist/service/mcp-node-operation-service.js +90 -2
- package/dist/service/mission-mcp-server.js +124 -2
- package/dist/service/mission-mcp-stdio.js +40 -2
- package/dist/service/mission-runtime.js +142 -2
- package/dist/service/native-session-projection-service.js +274 -2
- package/dist/service/native-session-watch-service.js +683 -2
- package/dist/service/node-connection-lifecycle-service.js +88 -2
- package/dist/service/node-control-channel.js +85 -2
- package/dist/service/node-control-message-service.js +109 -2
- package/dist/service/node-request-service.js +62 -2
- package/dist/service/node-run-event-bridge.js +88 -2
- package/dist/service/node-terminal-channel.js +202 -2
- package/dist/service/node-upgrade-coordinator.js +137 -2
- package/dist/service/node-workspace-coordinator.js +97 -2
- package/dist/service/recent-run-projection-service.js +99 -2
- package/dist/service/run-attachment-service.js +321 -2
- package/dist/service/run-domain-state.js +4 -2
- package/dist/service/run-event-service.js +246 -2
- package/dist/service/run-ownership-policy.js +22 -2
- package/dist/service/run-workbench-service.js +120 -2
- package/dist/service/runner-channel-message-service.js +20 -2
- package/dist/service/runner-interaction-service.js +243 -2
- package/dist/service/runner-service.js +69 -2
- package/dist/service/runtime-credential-control-service.js +95 -2
- package/dist/service/session-catalog-service.js +404 -2
- package/dist/service/session-command-service.js +125 -2
- package/dist/service/session-context-service.js +128 -2
- package/dist/service/session-domain-state.js +9 -2
- package/dist/service/session-identity-service.js +86 -2
- package/dist/service/session-lifecycle-service.js +248 -2
- package/dist/service/session-message-service.js +270 -2
- package/dist/service/session-presentation-service.js +43 -2
- package/dist/service/session-query-service.js +82 -2
- package/dist/service/session-watch-slot-coordinator.js +21 -2
- package/dist/service/skill-directory-service.js +238 -2
- package/dist/service/skill-install-service.js +434 -2
- package/dist/service/skill-node-operation-service.js +206 -2
- package/dist/service/terminal-relay-state.js +3 -2
- package/dist/service/terminal-service.js +90 -2
- package/dist/service/web-search-error.js +40 -2
- package/dist/service/workbench-event-service.js +21 -2
- package/dist/service/workbench-manifest-service.js +69 -2
- package/dist/service/workspace-change-service.js +290 -2
- package/dist/service/workspace-content-search-service.js +457 -2
- package/dist/service/workspace-domain-state.js +41 -2
- package/dist/service/workspace-file-service.js +150 -2
- package/dist/service/workspace-git-exclude-service.js +119 -2
- package/dist/service/workspace-queue-workbench-service.js +176 -2
- package/dist/service/workspace-service.js +92 -2
- package/dist/service/workspace-session-service.js +154 -2
- package/dist/service/workspace-upload-service.js +262 -2
- package/dist/service/workspace-watch-controller.js +68 -2
- package/dist/service/workspace-watch-service.js +170 -2
- package/dist/service/workspace-watch-snapshot-service.js +218 -2
- package/dist/service/workspace-workbench-service.js +239 -2
- package/dist/service.js +91 -2
- package/dist/storage.js +32 -2
- package/dist/supervisor.js +136 -2
- package/dist/terminal.js +496 -2
- package/dist/util/git-credential-client.js +32 -0
- package/dist/util/node-operation-data.js +9 -2
- package/dist/util/node-operation-parsers.js +382 -2
- package/dist/util/personal-instructions.js +34 -2
- package/dist/util/runner-error.js +70 -2
- package/dist/util/runner-native-session-parsers.js +510 -2
- package/dist/util/safe-error.js +8 -2
- package/dist/util/secret-environment.js +25 -2
- package/dist/workspace.js +1793 -2
- package/package.json +11 -8
- package/vendor/ripgrep/darwin-arm64/rg +0 -0
- package/vendor/ripgrep/darwin-x64/rg +0 -0
- package/vendor/ripgrep/manifest.json +12 -0
- package/README.md +0 -42
- package/dist/event-buffer.js +0 -2
- package/dist/migrations/v001.js +0 -107
- package/dist/migrations/v002.js +0 -2
- package/dist/migrations/v003.js +0 -2
- package/dist/service/mcp-package-installer.js +0 -2
|
@@ -1,2 +1,152 @@
|
|
|
1
|
-
|
|
2
|
-
const _0xff519d=_0x50f4;function _0x5176(){const _0x5c92dd=['y2XLyxjgywLSDxjL','CMvZB2X2zq','ChjVzhvJDa','zwzMB3j0','ChjVAMvJDgLVBKzVCG','zw5ZDxjLq29UzMLNDxjHDgLVBG','CMvZDw1L','z2v0','y29Kzq','mtu5mdiZnZL2BwLQy3q','rvHurvjoquW','DhjHBNnPDgLVBNm','C2v0rMfPBhvYzq','C2v0','rvHurvjoquXFqunusvzf','CNvUBMvY','zMfPBhvYzurLDgfPBa','z2v0qwDLBNrtzxnZAw9U','nJaWmJqYngncAvLdAa','ywn0AxzPDhK','BwvZC2fNzuzHAwX1CMvdB2rL','n2PhAg1lqW','y3jLyxrLqwDLBNrtzxnZAw9U','CMvTzw1IzxjoyxrPDMu','nte3mJu5nurhDvrizW','BwvZC2fNzvjLC3vTzuzHAwX1CMvZ','CMvZB2X2zuzVCK1LC3nHz2u','C2vZC2LVBG','CMvZDw1LrxH0zxjUywW','DgHLBG','zMfPBhvYzvrPBwvYCW','CMvZDw1Lzej5rgLYzwn0swq','CMvZDw1LrMfPBhvYzurLDgfPBhm','mJy1nZy2mwfwr2T4wG','neTduMHnyq','uLvotKvsx1voqvzbsuXbqKXf','u0vtu0LptL9srvnvtuvFuKvkrunuruq','tufsx01btKfhruq','nda5ndqXyunUwgHU','CMvZDw1LvgfZA3m','Bw9KzwW','zgvSAxzLCNK','ywnJzxnZ','zxH0zxjUywXby3rPDML0Eq','ywrK','C3rHDgu','B3b0Aw9UCW','rvHurvjoquXFu0vtu0LptL9bq1rjvKu','u0vtu0LptL9ot1rFrK9vtKq','D29YA3nWywnLswq','ywnJzxb0rM9YtwvZC2fNzq','CNvUDgLTzq','uKvkrunuruq','BMf0AxzLq29UDhjVBa','uKvtvu1bqKXf','mtCYmdK4nffcwwLMwG','y29UzMLNDxjHDgLVBG','zgvSzxrL','CMvZDw1LrMfPBhvYzxm','C2v0tMf0AxzLq29UDhjVBa','BwfUywDLzefSAwfZrM9Y','AgfZ','zMfPBhvYzunVzgu','C3rHz2u','zxH0zxjUywXtzxnZAw9Uswq','CNvUBMvYCW','CgvYzM9YBvjLC3vTzq','DhjHBNnPDgLVBMLUzW','yxzHAwXHyMXL','mZqZmdG3nKH6sunhCW','Dw5Yzwy','zMfPBhvYzq','D29YA3nWywnLugf0Aa','CNvUBMvYvgL0Bgu','y3vZDg9TvgL0Bgu','y3DK'];_0x5176=function(){return _0x5c92dd;};return _0x5176();}(function(_0x7d1319,_0x4b05de){const _0x48716b=_0x50f4,_0x36e5de=_0x7d1319();while(!![]){try{const _0x53f089=parseInt(_0x48716b(0x1b1))/(0x14b7+-0x24cc+-0x8e*-0x1d)*(-parseInt(_0x48716b(0x1ad))/(-0x2*0x16f+0x59d*-0x5+0x1ef1))+-parseInt(_0x48716b(0x1ac))/(-0xe8e*-0x1+-0xf16+-0x1*-0x8b)+parseInt(_0x48716b(0x184))/(0x1d*-0x77+-0xa*-0x329+-0x3*0x609)+-parseInt(_0x48716b(0x1a3))/(-0x1*-0x71+-0x1*0x28e+-0x1*-0x222)+parseInt(_0x48716b(0x19d))/(0x227*-0x7+-0x1*0xbe9+0x1b00)+parseInt(_0x48716b(0x1a0))/(-0xb5a*-0x3+0x1103+-0x1*0x330a)*(-parseInt(_0x48716b(0x1c2))/(0x83f*0x2+-0xaf*-0x13+0x15*-0x167))+parseInt(_0x48716b(0x194))/(-0x25ed+-0x9ae+0x2fa4);if(_0x53f089===_0x4b05de)break;else _0x36e5de['push'](_0x36e5de['shift']());}catch(_0x5ae765){_0x36e5de['push'](_0x36e5de['shift']());}}}(_0x5176,-0x2*0x83064+0x8b169+0xd*0x160da));function _0x50f4(_0x2f3e4d,_0x3fbddc){_0x2f3e4d=_0x2f3e4d-(0x617*0x5+0x1e6d*-0x1+0x17d);const _0x34d2fc=_0x5176();let _0x3b27fa=_0x34d2fc[_0x2f3e4d];if(_0x50f4['pQyKaw']===undefined){var _0x22a94a=function(_0x26681d){const _0x5333ef='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x3dd715='',_0xb4d914='';for(let _0x471e42=0xacc+0x713+-0x393*0x5,_0x3287b6,_0xfb3c7d,_0x5be23b=-0x139*0x10+-0x83*-0x37+0x1*-0x895;_0xfb3c7d=_0x26681d['charAt'](_0x5be23b++);~_0xfb3c7d&&(_0x3287b6=_0x471e42%(-0x13da+0x537+0xea7)?_0x3287b6*(0x6a*-0x14+-0x1672+0x1efa)+_0xfb3c7d:_0xfb3c7d,_0x471e42++%(-0x20b0+0x1f5+0x1ebf))?_0x3dd715+=String['fromCharCode'](-0xb26*0x3+0x1e16+0xdf*0x5&_0x3287b6>>(-(0x2*-0x1205+-0x7*0xd0+0x29bc)*_0x471e42&0xc9a+-0x3*-0x8a9+0x1*-0x268f)):-0xbed*0x2+-0x1*0x19cd+0x29d*0x13){_0xfb3c7d=_0x5333ef['indexOf'](_0xfb3c7d);}for(let _0x34b529=-0x8a9+-0xe87+0x10*0x173,_0xe900f7=_0x3dd715['length'];_0x34b529<_0xe900f7;_0x34b529++){_0xb4d914+='%'+('00'+_0x3dd715['charCodeAt'](_0x34b529)['toString'](-0xb*0x1f3+-0x298*0xf+0x3c69))['slice'](-(-0x43*-0x3b+-0x3d5*-0x1+-0x1344));}return decodeURIComponent(_0xb4d914);};_0x50f4['UJamzR']=_0x22a94a,_0x50f4['egDBsm']={},_0x50f4['pQyKaw']=!![];}const _0x36dd66=_0x34d2fc[0x2026+0x96c+0x11*-0x272];_0x50f4['IcFexQ']!==_0x36dd66&&(_0x50f4['egDBsm']={},_0x50f4['IcFexQ']=_0x36dd66);const _0x3b23a4=_0x50f4['egDBsm'][_0x2f3e4d];return _0x3b23a4===undefined?(_0x3b27fa=_0x50f4['UJamzR'](_0x3b27fa),_0x50f4['egDBsm'][_0x2f3e4d]=_0x3b27fa):_0x3b27fa=_0x3b23a4,_0x3b27fa;}const RESUME_FAILURE_TTL_MS=(-0x767+0xf84+0x7ff*-0x1)*(-0x10fb3+0x1*0x15e97+0x9b7c);export class ExternalSessionResumeService{[_0xff519d(0x1b9)];[_0xff519d(0x196)]=new Set();[_0xff519d(0x1b2)]=new Map();constructor(_0x360d47){const _0x31dde1=_0xff519d;this[_0x31dde1(0x1b9)]=_0x360d47;}[_0xff519d(0x1ce)](_0x7ed60a){const _0x362fd3=_0xff519d;return this[_0x362fd3(0x196)][_0x362fd3(0x1c8)](_0x7ed60a);}async[_0xff519d(0x191)](_0x2d861b){const _0x4d3d40=_0xff519d,_0x1c1d4c=this[_0x4d3d40(0x1b2)][_0x4d3d40(0x192)](_0x2d861b['id']);if(_0x1c1d4c!==undefined)return _0x1c1d4c;const _0x2594af=this[_0x4d3d40(0x1cd)](_0x2d861b);this[_0x4d3d40(0x1b2)][_0x4d3d40(0x198)](_0x2d861b['id'],_0x2594af);try{return await _0x2594af;}finally{if(this[_0x4d3d40(0x1b2)][_0x4d3d40(0x192)](_0x2d861b['id'])===_0x2594af)this[_0x4d3d40(0x1b2)][_0x4d3d40(0x1c4)](_0x2d861b['id']);}}async[_0xff519d(0x1cd)](_0x144bb5){const _0x34c0c8=_0xff519d;this[_0x34c0c8(0x18b)](_0x144bb5['id']);const _0x438c0d=this[_0x34c0c8(0x1b9)][_0x34c0c8(0x1b8)][_0x34c0c8(0x1aa)][_0x34c0c8(0x192)](_0x144bb5['id']),_0x4c0d6a=_0x438c0d===undefined?undefined:this[_0x34c0c8(0x1b9)][_0x34c0c8(0x1be)][_0x34c0c8(0x19c)](_0x438c0d);if(_0x4c0d6a!==undefined)return this[_0x34c0c8(0x1b9)][_0x34c0c8(0x190)](_0x4c0d6a);const _0x1b94cc=this[_0x34c0c8(0x1b9)][_0x34c0c8(0x1cc)][_0x34c0c8(0x18d)](_0x144bb5[_0x34c0c8(0x19a)]);if(_0x1b94cc===undefined)return this[_0x34c0c8(0x197)](_0x144bb5['id'],_0x34c0c8(0x1ae)),undefined;this[_0x34c0c8(0x196)][_0x34c0c8(0x1b7)](_0x144bb5['id']);let _0x1de049;try{_0x1de049=await _0x1b94cc[_0x34c0c8(0x1a7)](_0x144bb5,{'available':this[_0x34c0c8(0x1b9)][_0x34c0c8(0x183)](_0x144bb5[_0x34c0c8(0x19a)]),'projectionFor':this[_0x34c0c8(0x1b9)][_0x34c0c8(0x18f)],'managedAliasFor':this[_0x34c0c8(0x1b9)][_0x34c0c8(0x1c7)],'currentResumed':_0x7cb731=>{const _0x3b83df=_0x34c0c8,_0x321327=this[_0x3b83df(0x1b9)][_0x3b83df(0x1b8)][_0x3b83df(0x1aa)][_0x3b83df(0x192)](_0x7cb731);return _0x321327===undefined?undefined:this[_0x3b83df(0x1b9)][_0x3b83df(0x1be)][_0x3b83df(0x19c)](_0x321327);},'promote':_0x2cd972=>{const _0x5eaa03=_0x34c0c8;return this[_0x5eaa03(0x1b9)][_0x5eaa03(0x1be)][_0x5eaa03(0x1c6)](_0x2cd972,_0x5eaa03(0x1b0)),this[_0x5eaa03(0x1b9)][_0x5eaa03(0x1be)][_0x5eaa03(0x19c)](_0x2cd972);},'ensureConfiguration':this[_0x34c0c8(0x1b9)][_0x34c0c8(0x190)],'create':_0x2c0680=>{const _0x50f2e9=_0x34c0c8,_0xc85b81=this[_0x50f2e9(0x1b9)][_0x50f2e9(0x1c3)](_0x2c0680);return this[_0x50f2e9(0x1b9)][_0x50f2e9(0x1be)][_0x50f2e9(0x1a1)]({'workspaceId':_0x2c0680[_0x50f2e9(0x1bc)],'runner':_0x2c0680[_0x50f2e9(0x19a)],..._0x2c0680[_0x50f2e9(0x1cb)]===null?{}:{'externalSessionId':_0x2c0680[_0x50f2e9(0x1cb)]},'nativeControl':_0x50f2e9(0x1b0),'cwd':_0x2c0680[_0x50f2e9(0x18a)],'model':_0xc85b81[_0x50f2e9(0x1b3)],'effort':_0xc85b81[_0x50f2e9(0x18e)],'access':_0xc85b81[_0x50f2e9(0x1b5)],..._0x2c0680[_0x50f2e9(0x188)]===null?{}:{'title':_0x2c0680[_0x50f2e9(0x188)]},..._0x2c0680[_0x50f2e9(0x189)]===null?{}:{'customTitle':_0x2c0680[_0x50f2e9(0x189)]}});},'workspacePath':this[_0x34c0c8(0x1b9)][_0x34c0c8(0x187)],'rememberManagedNative':this[_0x34c0c8(0x1b9)][_0x34c0c8(0x1a2)]});}finally{this[_0x34c0c8(0x196)][_0x34c0c8(0x1c4)](_0x144bb5['id']);}if(_0x1de049[_0x34c0c8(0x19e)]!==undefined)this[_0x34c0c8(0x1b9)][_0x34c0c8(0x1b8)][_0x34c0c8(0x1b6)][_0x34c0c8(0x198)](_0x144bb5['id'],_0x1de049[_0x34c0c8(0x19e)]);if(_0x1de049[_0x34c0c8(0x1a6)]!==undefined)return this[_0x34c0c8(0x1b9)][_0x34c0c8(0x1b8)][_0x34c0c8(0x1aa)][_0x34c0c8(0x198)](_0x144bb5['id'],_0x1de049[_0x34c0c8(0x1a6)]['id']),_0x1de049[_0x34c0c8(0x1a6)];return this[_0x34c0c8(0x197)](_0x144bb5['id'],_0x1de049[_0x34c0c8(0x1c9)]??_0x34c0c8(0x1af),_0x1de049[_0x34c0c8(0x19b)]),this[_0x34c0c8(0x1b9)][_0x34c0c8(0x1b8)][_0x34c0c8(0x1a4)][_0x34c0c8(0x198)](_0x144bb5['id'],_0x1de049[_0x34c0c8(0x19f)]??_0x1de049[_0x34c0c8(0x1c9)]??_0x34c0c8(0x1af)),undefined;}[_0xff519d(0x186)](_0x6ddef4){const _0x3d0d85=_0xff519d,_0x54687e=this[_0x3d0d85(0x1b9)][_0x3d0d85(0x1b8)][_0x3d0d85(0x1c5)][_0x3d0d85(0x192)](_0x6ddef4)??_0x3d0d85(0x1af),_0x343b87=this[_0x3d0d85(0x1b9)][_0x3d0d85(0x1b8)][_0x3d0d85(0x1ab)][_0x3d0d85(0x192)](_0x6ddef4);return _0x343b87===undefined?{'code':_0x54687e}:{'code':_0x54687e,'detail':_0x343b87};}async[_0xff519d(0x1a5)](_0xcdd6e1){const _0x47baf6=_0xff519d,_0x268eb7=this[_0x47baf6(0x1b9)][_0x47baf6(0x1b8)][_0x47baf6(0x1aa)][_0x47baf6(0x192)](_0xcdd6e1),_0x59e4f6=this[_0x47baf6(0x1b9)][_0x47baf6(0x1be)][_0x47baf6(0x19c)](_0x268eb7??_0xcdd6e1)??await this[_0x47baf6(0x1b9)][_0x47baf6(0x18c)](_0xcdd6e1);if(_0x59e4f6===undefined)throw new Error(_0x47baf6(0x1bb));if(_0x59e4f6[_0x47baf6(0x1c0)]!==_0x47baf6(0x195))return _0x59e4f6;const _0x4cefd6=await this[_0x47baf6(0x191)](_0x59e4f6);if(_0x4cefd6!==undefined)return _0x4cefd6;if(this[_0x47baf6(0x1b9)][_0x47baf6(0x1b8)][_0x47baf6(0x1b6)][_0x47baf6(0x192)](_0x59e4f6['id'])===_0x47baf6(0x199))throw new Error(_0x47baf6(0x1ba));throw new Error(this[_0x47baf6(0x1b9)][_0x47baf6(0x1b8)][_0x47baf6(0x1a4)][_0x47baf6(0x192)](_0x59e4f6['id'])??this[_0x47baf6(0x186)](_0x59e4f6['id'])[_0x47baf6(0x193)]);}async[_0xff519d(0x1bd)](_0x3b3b4c){const _0x4a07c7=_0xff519d,_0x28beb2=this[_0x4a07c7(0x1b9)][_0x4a07c7(0x1b8)][_0x4a07c7(0x1aa)][_0x4a07c7(0x192)](_0x3b3b4c),_0x375480=this[_0x4a07c7(0x1b9)][_0x4a07c7(0x1be)][_0x4a07c7(0x19c)](_0x28beb2??_0x3b3b4c)??await this[_0x4a07c7(0x1b9)][_0x4a07c7(0x18c)](_0x3b3b4c);if(_0x375480===undefined)throw new Error(_0x4a07c7(0x1bb));if(_0x375480[_0x4a07c7(0x1c0)]!==_0x4a07c7(0x195))return{'session':_0x375480,'ready':Promise[_0x4a07c7(0x18c)](_0x375480),'deferred':![]};const _0x3aeecc=this[_0x4a07c7(0x1b9)][_0x4a07c7(0x18f)](_0x375480)??this[_0x4a07c7(0x1b9)][_0x4a07c7(0x1ca)](_0x375480),_0x3d5c5c=this[_0x4a07c7(0x191)](_0x375480)[_0x4a07c7(0x1a8)](_0x28672f=>{const _0x341a8b=_0x4a07c7;if(_0x28672f!==undefined)return _0x28672f;if(this[_0x341a8b(0x1b9)][_0x341a8b(0x1b8)][_0x341a8b(0x1b6)][_0x341a8b(0x192)](_0x375480['id'])===_0x341a8b(0x199))throw new Error(_0x341a8b(0x1ba));throw new Error(this[_0x341a8b(0x1b9)][_0x341a8b(0x1b8)][_0x341a8b(0x1a4)][_0x341a8b(0x192)](_0x375480['id'])??this[_0x341a8b(0x186)](_0x375480['id'])[_0x341a8b(0x193)]);});return{'session':_0x3aeecc,'ready':_0x3d5c5c,'deferred':!![]};}[_0xff519d(0x1b4)](_0x7dadb6){const _0x4328e0=_0xff519d;return _0x7dadb6[_0x4328e0(0x1c0)]===_0x4328e0(0x195)&&_0x7dadb6[_0x4328e0(0x1cb)]!==null?_0x4328e0(0x1c1):_0x4328e0(0x1bf);}[_0xff519d(0x18b)](_0x3f1132){const _0x1d6b35=_0xff519d,_0x4f665b=this[_0x1d6b35(0x1b9)][_0x1d6b35(0x1b8)][_0x1d6b35(0x1a9)][_0x1d6b35(0x192)](_0x3f1132);if(_0x4f665b!==undefined)clearTimeout(_0x4f665b);this[_0x1d6b35(0x1b9)][_0x1d6b35(0x1b8)][_0x1d6b35(0x1a9)][_0x1d6b35(0x1c4)](_0x3f1132),this[_0x1d6b35(0x1b9)][_0x1d6b35(0x1b8)][_0x1d6b35(0x1c5)][_0x1d6b35(0x1c4)](_0x3f1132),this[_0x1d6b35(0x1b9)][_0x1d6b35(0x1b8)][_0x1d6b35(0x1ab)][_0x1d6b35(0x1c4)](_0x3f1132),this[_0x1d6b35(0x1b9)][_0x1d6b35(0x1b8)][_0x1d6b35(0x1a4)][_0x1d6b35(0x1c4)](_0x3f1132);}[_0xff519d(0x197)](_0x1c201f,_0x352a6d,_0x219ebd){const _0x43f017=_0xff519d;this[_0x43f017(0x18b)](_0x1c201f),this[_0x43f017(0x1b9)][_0x43f017(0x1b8)][_0x43f017(0x1c5)][_0x43f017(0x198)](_0x1c201f,_0x352a6d);if(_0x219ebd===undefined)this[_0x43f017(0x1b9)][_0x43f017(0x1b8)][_0x43f017(0x1ab)][_0x43f017(0x1c4)](_0x1c201f);else this[_0x43f017(0x1b9)][_0x43f017(0x1b8)][_0x43f017(0x1ab)][_0x43f017(0x198)](_0x1c201f,_0x219ebd);const _0x570d12=setTimeout(()=>this[_0x43f017(0x18b)](_0x1c201f),RESUME_FAILURE_TTL_MS);_0x570d12[_0x43f017(0x185)](),this[_0x43f017(0x1b9)][_0x43f017(0x1b8)][_0x43f017(0x1a9)][_0x43f017(0x198)](_0x1c201f,_0x570d12);}}
|
|
1
|
+
const RESUME_FAILURE_TTL_MS = 30 * 60_000;
|
|
2
|
+
export class ExternalSessionResumeService {
|
|
3
|
+
options;
|
|
4
|
+
transitions = new Set();
|
|
5
|
+
resumeTasks = new Map();
|
|
6
|
+
constructor(options) {
|
|
7
|
+
this.options = options;
|
|
8
|
+
}
|
|
9
|
+
transitioning(sessionId) {
|
|
10
|
+
return this.transitions.has(sessionId);
|
|
11
|
+
}
|
|
12
|
+
async resume(external) {
|
|
13
|
+
const existing = this.resumeTasks.get(external.id);
|
|
14
|
+
if (existing !== undefined)
|
|
15
|
+
return existing;
|
|
16
|
+
const task = this.performResume(external);
|
|
17
|
+
this.resumeTasks.set(external.id, task);
|
|
18
|
+
try {
|
|
19
|
+
return await task;
|
|
20
|
+
}
|
|
21
|
+
finally {
|
|
22
|
+
if (this.resumeTasks.get(external.id) === task)
|
|
23
|
+
this.resumeTasks.delete(external.id);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
async performResume(external) {
|
|
27
|
+
this.clearFailure(external.id);
|
|
28
|
+
const priorId = this.options.state.resumedByDirectId.get(external.id);
|
|
29
|
+
const prior = priorId === undefined ? undefined : this.options.runtime.getAgentSession(priorId);
|
|
30
|
+
if (prior !== undefined)
|
|
31
|
+
return this.options.ensureConfiguration(prior);
|
|
32
|
+
const runner = this.options.runners.product(external.runner);
|
|
33
|
+
if (runner === undefined) {
|
|
34
|
+
this.setFailure(external.id, 'RUNNER_UNAVAILABLE');
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
this.transitions.add(external.id);
|
|
38
|
+
let result;
|
|
39
|
+
try {
|
|
40
|
+
result = await runner.resumeExternal(external, {
|
|
41
|
+
available: this.options.available(external.runner),
|
|
42
|
+
projectionFor: this.options.projectionFor,
|
|
43
|
+
managedAliasFor: this.options.managedAliasFor,
|
|
44
|
+
currentResumed: (sessionId) => {
|
|
45
|
+
const resumedId = this.options.state.resumedByDirectId.get(sessionId);
|
|
46
|
+
return resumedId === undefined
|
|
47
|
+
? undefined
|
|
48
|
+
: this.options.runtime.getAgentSession(resumedId);
|
|
49
|
+
},
|
|
50
|
+
promote: (sessionId) => {
|
|
51
|
+
this.options.runtime.setNativeControl(sessionId, 'MAR_MANAGED');
|
|
52
|
+
return this.options.runtime.getAgentSession(sessionId);
|
|
53
|
+
},
|
|
54
|
+
ensureConfiguration: this.options.ensureConfiguration,
|
|
55
|
+
create: (session) => {
|
|
56
|
+
const configuration = this.options.configuration(session);
|
|
57
|
+
return this.options.runtime.createAgentSession({
|
|
58
|
+
workspaceId: session.workspaceId,
|
|
59
|
+
runner: session.runner,
|
|
60
|
+
...(session.externalSessionId === null
|
|
61
|
+
? {}
|
|
62
|
+
: { externalSessionId: session.externalSessionId }),
|
|
63
|
+
nativeControl: 'MAR_MANAGED',
|
|
64
|
+
cwd: session.cwd,
|
|
65
|
+
model: configuration.model,
|
|
66
|
+
effort: configuration.effort,
|
|
67
|
+
access: configuration.access,
|
|
68
|
+
...(session.runnerTitle === null ? {} : { title: session.runnerTitle }),
|
|
69
|
+
...(session.customTitle === null ? {} : { customTitle: session.customTitle })
|
|
70
|
+
});
|
|
71
|
+
},
|
|
72
|
+
workspacePath: this.options.workspacePath,
|
|
73
|
+
rememberManagedNative: this.options.rememberNative
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
finally {
|
|
77
|
+
this.transitions.delete(external.id);
|
|
78
|
+
}
|
|
79
|
+
if (result.activity !== undefined)
|
|
80
|
+
this.options.state.externalActivity.set(external.id, result.activity);
|
|
81
|
+
if (result.session !== undefined) {
|
|
82
|
+
this.options.state.resumedByDirectId.set(external.id, result.session.id);
|
|
83
|
+
return result.session;
|
|
84
|
+
}
|
|
85
|
+
this.setFailure(external.id, result.failureCode ?? 'SESSION_RESUME_REJECTED', result.failureDetail);
|
|
86
|
+
this.options.state.messageResumeFailures.set(external.id, result.messageFailureCode ?? result.failureCode ?? 'SESSION_RESUME_REJECTED');
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
failure(sessionId) {
|
|
90
|
+
const code = this.options.state.resumeFailures.get(sessionId) ?? 'SESSION_RESUME_REJECTED';
|
|
91
|
+
const detail = this.options.state.resumeFailureDetails.get(sessionId);
|
|
92
|
+
return detail === undefined ? { code } : { code, detail };
|
|
93
|
+
}
|
|
94
|
+
async resolveForMessage(sessionId) {
|
|
95
|
+
const resumedId = this.options.state.resumedByDirectId.get(sessionId);
|
|
96
|
+
const current = this.options.runtime.getAgentSession(resumedId ?? sessionId) ??
|
|
97
|
+
(await this.options.resolve(sessionId));
|
|
98
|
+
if (current === undefined)
|
|
99
|
+
throw new Error('SESSION_NOT_FOUND');
|
|
100
|
+
if (current.nativeControl !== 'EXTERNAL')
|
|
101
|
+
return current;
|
|
102
|
+
const resumed = await this.resume(current);
|
|
103
|
+
if (resumed !== undefined)
|
|
104
|
+
return resumed;
|
|
105
|
+
if (this.options.state.externalActivity.get(current.id) === 'EXTERNAL_ACTIVE')
|
|
106
|
+
throw new Error('EXTERNAL_SESSION_ACTIVE');
|
|
107
|
+
throw new Error(this.options.state.messageResumeFailures.get(current.id) ?? this.failure(current.id).code);
|
|
108
|
+
}
|
|
109
|
+
async acceptForMessage(sessionId) {
|
|
110
|
+
const resumedId = this.options.state.resumedByDirectId.get(sessionId);
|
|
111
|
+
const current = this.options.runtime.getAgentSession(resumedId ?? sessionId) ??
|
|
112
|
+
(await this.options.resolve(sessionId));
|
|
113
|
+
if (current === undefined)
|
|
114
|
+
throw new Error('SESSION_NOT_FOUND');
|
|
115
|
+
if (current.nativeControl !== 'EXTERNAL')
|
|
116
|
+
return { session: current, ready: Promise.resolve(current), deferred: false };
|
|
117
|
+
const staged = this.options.projectionFor(current) ?? this.options.stage(current);
|
|
118
|
+
const ready = this.resume(current).then((session) => {
|
|
119
|
+
if (session !== undefined)
|
|
120
|
+
return session;
|
|
121
|
+
if (this.options.state.externalActivity.get(current.id) === 'EXTERNAL_ACTIVE')
|
|
122
|
+
throw new Error('EXTERNAL_SESSION_ACTIVE');
|
|
123
|
+
throw new Error(this.options.state.messageResumeFailures.get(current.id) ?? this.failure(current.id).code);
|
|
124
|
+
});
|
|
125
|
+
return { session: staged, ready, deferred: true };
|
|
126
|
+
}
|
|
127
|
+
delivery(session) {
|
|
128
|
+
return session.nativeControl === 'EXTERNAL' && session.externalSessionId !== null
|
|
129
|
+
? 'RESUMABLE'
|
|
130
|
+
: 'REJECTED';
|
|
131
|
+
}
|
|
132
|
+
clearFailure(sessionId) {
|
|
133
|
+
const timer = this.options.state.failureTimers.get(sessionId);
|
|
134
|
+
if (timer !== undefined)
|
|
135
|
+
clearTimeout(timer);
|
|
136
|
+
this.options.state.failureTimers.delete(sessionId);
|
|
137
|
+
this.options.state.resumeFailures.delete(sessionId);
|
|
138
|
+
this.options.state.resumeFailureDetails.delete(sessionId);
|
|
139
|
+
this.options.state.messageResumeFailures.delete(sessionId);
|
|
140
|
+
}
|
|
141
|
+
setFailure(sessionId, code, detail) {
|
|
142
|
+
this.clearFailure(sessionId);
|
|
143
|
+
this.options.state.resumeFailures.set(sessionId, code);
|
|
144
|
+
if (detail === undefined)
|
|
145
|
+
this.options.state.resumeFailureDetails.delete(sessionId);
|
|
146
|
+
else
|
|
147
|
+
this.options.state.resumeFailureDetails.set(sessionId, detail);
|
|
148
|
+
const timer = setTimeout(() => this.clearFailure(sessionId), RESUME_FAILURE_TTL_MS);
|
|
149
|
+
timer.unref();
|
|
150
|
+
this.options.state.failureTimers.set(sessionId, timer);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -1,2 +1,60 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { isTerminalRunStatus } from '../runner/codex/conversation-parser.js';
|
|
2
|
+
/** Test-only managed Run adapter; production Runner paths never depend on it. */
|
|
3
|
+
export class FakeManagedRunService {
|
|
4
|
+
options;
|
|
5
|
+
constructor(options) {
|
|
6
|
+
this.options = options;
|
|
7
|
+
}
|
|
8
|
+
start(envelope) {
|
|
9
|
+
const payload = envelope.payload;
|
|
10
|
+
if (typeof payload.runId !== 'string' || this.options.state.active.has(payload.runId))
|
|
11
|
+
return;
|
|
12
|
+
const runId = payload.runId;
|
|
13
|
+
const cwd = typeof payload.cwd === 'string' ? payload.cwd : undefined;
|
|
14
|
+
if (typeof payload.sessionId === 'string' &&
|
|
15
|
+
typeof payload.runner === 'string' &&
|
|
16
|
+
cwd !== undefined) {
|
|
17
|
+
const runner = this.options.runners.product(payload.runner);
|
|
18
|
+
if (runner === undefined)
|
|
19
|
+
return;
|
|
20
|
+
this.options.runtime.adoptRun({
|
|
21
|
+
runId,
|
|
22
|
+
sessionId: payload.sessionId,
|
|
23
|
+
...(typeof payload.workspaceId === 'string' ? { workspaceId: payload.workspaceId } : {}),
|
|
24
|
+
runner: runner.name,
|
|
25
|
+
cwd
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
this.options.state.active.add(runId);
|
|
29
|
+
void this.options.runner
|
|
30
|
+
.start(runId, this.options.script(payload.input, runId), {
|
|
31
|
+
event: (eventType, eventPayload, status) => {
|
|
32
|
+
this.options.emitRun(runId, eventType, eventPayload, status);
|
|
33
|
+
if (status !== undefined && isTerminalRunStatus(status)) {
|
|
34
|
+
this.options.state.active.delete(runId);
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
approval: (approvalId, approvalPayload, expiresAt) => {
|
|
38
|
+
try {
|
|
39
|
+
this.options.runtime.createApproval({
|
|
40
|
+
runId,
|
|
41
|
+
approvalId,
|
|
42
|
+
payload: approvalPayload,
|
|
43
|
+
expiresAt
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
this.options.publishApproval({
|
|
50
|
+
runId,
|
|
51
|
+
approvalId,
|
|
52
|
+
payload: approvalPayload,
|
|
53
|
+
expiresAt
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
disconnect: this.options.disconnect
|
|
57
|
+
})
|
|
58
|
+
.finally(() => this.options.state.active.delete(runId));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
import { execFile } from 'node:child_process';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { chmod, lstat, mkdir, mkdtemp, readdir, rm, writeFile } from 'node:fs/promises';
|
|
5
|
+
import { createServer } from 'node:http';
|
|
6
|
+
import { homedir, tmpdir } from 'node:os';
|
|
7
|
+
import { delimiter, join } from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { promisify } from 'node:util';
|
|
10
|
+
import { personalGitRuntimeSchema, selectGitCredential } from '@myagentroam/protocol';
|
|
11
|
+
const execute = promisify(execFile);
|
|
12
|
+
/** Owns only process-scoped Git credentials, never repository or global configuration. */
|
|
13
|
+
export class GitExecutionService {
|
|
14
|
+
options;
|
|
15
|
+
contexts = new Map();
|
|
16
|
+
tokens = new Map();
|
|
17
|
+
removals = new Set();
|
|
18
|
+
server;
|
|
19
|
+
starting;
|
|
20
|
+
root;
|
|
21
|
+
wrappers = new Map();
|
|
22
|
+
closed = false;
|
|
23
|
+
constructor(options = {}) {
|
|
24
|
+
this.options = options;
|
|
25
|
+
}
|
|
26
|
+
async acquire(userId, configuration, baseEnvironment = {}) {
|
|
27
|
+
if (this.closed)
|
|
28
|
+
throw new Error('GIT_EXECUTION_CLOSED');
|
|
29
|
+
if (!Number.isSafeInteger(userId) || userId < 1)
|
|
30
|
+
throw new Error('USER_AUTHORIZATION_REQUIRED');
|
|
31
|
+
const parsed = personalGitRuntimeSchema.safeParse(configuration);
|
|
32
|
+
if (!parsed.success)
|
|
33
|
+
throw new Error('GIT_CONFIGURATION_INVALID');
|
|
34
|
+
const key = createHash('sha256')
|
|
35
|
+
.update(JSON.stringify([
|
|
36
|
+
userId,
|
|
37
|
+
parsed.data,
|
|
38
|
+
Object.entries(baseEnvironment).sort(([a], [b]) => a.localeCompare(b))
|
|
39
|
+
]))
|
|
40
|
+
.digest('hex');
|
|
41
|
+
const endpoint = await this.start();
|
|
42
|
+
if (this.closed)
|
|
43
|
+
throw new Error('GIT_EXECUTION_CLOSED');
|
|
44
|
+
let context = this.contexts.get(key);
|
|
45
|
+
if (context === undefined) {
|
|
46
|
+
const token = randomBytes(32).toString('hex');
|
|
47
|
+
const original = { ...process.env, ...baseEnvironment };
|
|
48
|
+
const binary = await gitExecutable(original);
|
|
49
|
+
const environment = {
|
|
50
|
+
...baseEnvironment,
|
|
51
|
+
MAR_GIT_ENDPOINT: endpoint,
|
|
52
|
+
MAR_GIT_TOKEN: token
|
|
53
|
+
};
|
|
54
|
+
const count = Number(original.GIT_CONFIG_COUNT ?? 0);
|
|
55
|
+
if (!Number.isSafeInteger(count) || count < 0 || count > 128)
|
|
56
|
+
throw new Error('GIT_ENVIRONMENT_INVALID');
|
|
57
|
+
const entries = parsed.data.credentials.some((entry) => entry.protocol === 'HTTPS')
|
|
58
|
+
? [
|
|
59
|
+
['credential.helper', ''],
|
|
60
|
+
['credential.helper', `!${helperCommand('git-execution-helper')} credential`],
|
|
61
|
+
['credential.useHttpPath', 'true']
|
|
62
|
+
]
|
|
63
|
+
: [];
|
|
64
|
+
if (entries.length > 0)
|
|
65
|
+
environment.GIT_CONFIG_COUNT = String(count + entries.length);
|
|
66
|
+
for (const [index, [name, value]] of entries.entries()) {
|
|
67
|
+
environment[`GIT_CONFIG_KEY_${count + index}`] = name;
|
|
68
|
+
environment[`GIT_CONFIG_VALUE_${count + index}`] = value;
|
|
69
|
+
}
|
|
70
|
+
if (parsed.data.credentials.some((entry) => entry.protocol === 'SSH')) {
|
|
71
|
+
environment.GIT_SSH_COMMAND = `${helperCommand('git-execution-helper')} ssh`;
|
|
72
|
+
environment.GIT_SSH_VARIANT = 'ssh';
|
|
73
|
+
}
|
|
74
|
+
const pathKey = process.platform === 'win32' && original.Path !== undefined ? 'Path' : 'PATH';
|
|
75
|
+
environment[pathKey] =
|
|
76
|
+
`${await this.gitWrapper(binary)}${delimiter}${original[pathKey] ?? ''}`;
|
|
77
|
+
const restore = Object.fromEntries(Object.keys(environment)
|
|
78
|
+
.filter((name) => environment[name] !== baseEnvironment[name] || name.startsWith('MAR_GIT_'))
|
|
79
|
+
.map((name) => [name, name.startsWith('MAR_GIT_') ? null : (original[name] ?? null)]));
|
|
80
|
+
context = {
|
|
81
|
+
key,
|
|
82
|
+
token,
|
|
83
|
+
configuration: parsed.data,
|
|
84
|
+
environment: Object.freeze(environment),
|
|
85
|
+
restore,
|
|
86
|
+
keys: new Map(),
|
|
87
|
+
references: 0
|
|
88
|
+
};
|
|
89
|
+
this.contexts.set(key, context);
|
|
90
|
+
this.tokens.set(token, context);
|
|
91
|
+
}
|
|
92
|
+
return { environment: context.environment, release: this.reference(context) };
|
|
93
|
+
}
|
|
94
|
+
retain(environment) {
|
|
95
|
+
if (environment.MAR_GIT_TOKEN === undefined)
|
|
96
|
+
return () => undefined;
|
|
97
|
+
const context = this.tokens.get(environment.MAR_GIT_TOKEN);
|
|
98
|
+
if (context === undefined || this.closed)
|
|
99
|
+
throw new Error('GIT_CREDENTIAL_CONTEXT_EXPIRED');
|
|
100
|
+
return this.reference(context);
|
|
101
|
+
}
|
|
102
|
+
reference(context) {
|
|
103
|
+
clearTimeout(context.timer);
|
|
104
|
+
delete context.timer;
|
|
105
|
+
context.references++;
|
|
106
|
+
let released = false;
|
|
107
|
+
return () => {
|
|
108
|
+
if (released)
|
|
109
|
+
return;
|
|
110
|
+
released = true;
|
|
111
|
+
if (--context.references !== 0 || this.closed)
|
|
112
|
+
return;
|
|
113
|
+
// This outlives the existing 60-second warm Runner window.
|
|
114
|
+
context.timer = setTimeout(() => this.retire(context), this.options.idleTimeoutMs ?? 70_000);
|
|
115
|
+
context.timer.unref();
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
retire(context) {
|
|
119
|
+
this.contexts.delete(context.key);
|
|
120
|
+
this.tokens.delete(context.token);
|
|
121
|
+
clearTimeout(context.timer);
|
|
122
|
+
const removal = Promise.allSettled(context.keys.values())
|
|
123
|
+
.then(async () => {
|
|
124
|
+
if (context.directory !== undefined)
|
|
125
|
+
await rm(await context.directory, { recursive: true, force: true });
|
|
126
|
+
})
|
|
127
|
+
.catch(() => undefined);
|
|
128
|
+
this.removals.add(removal);
|
|
129
|
+
void removal.finally(() => this.removals.delete(removal));
|
|
130
|
+
}
|
|
131
|
+
async close() {
|
|
132
|
+
this.closed = true;
|
|
133
|
+
await this.starting?.catch(() => undefined);
|
|
134
|
+
if (this.server !== undefined) {
|
|
135
|
+
this.server.closeAllConnections();
|
|
136
|
+
await new Promise((resolve) => this.server.close(() => resolve()));
|
|
137
|
+
}
|
|
138
|
+
for (const context of this.contexts.values())
|
|
139
|
+
this.retire(context);
|
|
140
|
+
await Promise.all(this.removals);
|
|
141
|
+
if (this.root !== undefined)
|
|
142
|
+
await rm(await this.root, { recursive: true, force: true });
|
|
143
|
+
}
|
|
144
|
+
start() {
|
|
145
|
+
this.starting ??= new Promise((resolve, reject) => {
|
|
146
|
+
const server = createServer((request, response) => {
|
|
147
|
+
void this.respond(request, response).catch(() => {
|
|
148
|
+
if (!response.headersSent)
|
|
149
|
+
response.writeHead(500, { 'content-type': 'application/json' });
|
|
150
|
+
response.end('{"error":"GIT_CREDENTIAL_UNAVAILABLE"}');
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
this.server = server;
|
|
154
|
+
server.requestTimeout = 10_000;
|
|
155
|
+
server.headersTimeout = 10_000;
|
|
156
|
+
server.on('error', reject);
|
|
157
|
+
server.listen(0, '127.0.0.1', () => {
|
|
158
|
+
const address = server.address();
|
|
159
|
+
if (address === null || typeof address === 'string')
|
|
160
|
+
return reject(new Error('GIT_CREDENTIAL_UNAVAILABLE'));
|
|
161
|
+
server.unref();
|
|
162
|
+
resolve(`http://127.0.0.1:${address.port}`);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
return this.starting;
|
|
166
|
+
}
|
|
167
|
+
async respond(request, response) {
|
|
168
|
+
response.setHeader('content-type', 'application/json');
|
|
169
|
+
response.setHeader('cache-control', 'no-store');
|
|
170
|
+
const token = request.headers.authorization?.replace(/^Bearer /u, '');
|
|
171
|
+
const context = token === undefined ? undefined : this.tokens.get(token);
|
|
172
|
+
if (request.method !== 'POST' ||
|
|
173
|
+
request.headers.origin !== undefined ||
|
|
174
|
+
context === undefined ||
|
|
175
|
+
this.closed) {
|
|
176
|
+
response.writeHead(403).end('{"error":"GIT_CREDENTIAL_CONTEXT_EXPIRED"}');
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const release = this.reference(context);
|
|
180
|
+
try {
|
|
181
|
+
let body = '';
|
|
182
|
+
for await (const chunk of request) {
|
|
183
|
+
body += String(chunk);
|
|
184
|
+
if (Buffer.byteLength(body) > 64 * 1024) {
|
|
185
|
+
response.writeHead(413).end('{"error":"GIT_CREDENTIAL_REQUEST_INVALID"}');
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const input = JSON.parse(body);
|
|
190
|
+
if (input === null || typeof input !== 'object' || Array.isArray(input))
|
|
191
|
+
throw new Error('GIT_CREDENTIAL_REQUEST_INVALID');
|
|
192
|
+
if (request.url === '/restore') {
|
|
193
|
+
response.end(JSON.stringify({ restore: context.restore }));
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
if (request.url === '/passphrase') {
|
|
197
|
+
const credential = context.configuration.credentials.find((candidate) => candidate.id === input.id && candidate.protocol === 'SSH');
|
|
198
|
+
response.end(JSON.stringify({ passphrase: credential?.passphrase ?? '' }));
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (request.url === '/identity') {
|
|
202
|
+
const match = typeof input.remote === 'string'
|
|
203
|
+
? matchRemoteCredential(context.configuration.credentials, input.remote)
|
|
204
|
+
: undefined;
|
|
205
|
+
if (match?.commitName) {
|
|
206
|
+
response.end(JSON.stringify({
|
|
207
|
+
matched: true,
|
|
208
|
+
name: match.commitName,
|
|
209
|
+
email: match.commitEmail
|
|
210
|
+
}));
|
|
211
|
+
}
|
|
212
|
+
else
|
|
213
|
+
response.end('{"matched":false}');
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (request.url !== '/credential' && request.url !== '/ssh') {
|
|
217
|
+
response.writeHead(404).end('{}');
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const protocol = request.url === '/ssh' ? 'SSH' : 'HTTPS';
|
|
221
|
+
let match;
|
|
222
|
+
try {
|
|
223
|
+
if (protocol === 'HTTPS' && input.protocol !== 'https')
|
|
224
|
+
throw new Error('UNMATCHED');
|
|
225
|
+
if (typeof input.host !== 'string' || typeof input.path !== 'string')
|
|
226
|
+
throw new Error('UNMATCHED');
|
|
227
|
+
const address = new URL(`${protocol === 'SSH' ? 'ssh' : 'https'}://${input.host}`);
|
|
228
|
+
match = selectGitCredential(context.configuration.credentials, {
|
|
229
|
+
protocol,
|
|
230
|
+
host: address.hostname,
|
|
231
|
+
port: Number(input.port ?? (address.port || (protocol === 'SSH' ? 22 : 443))),
|
|
232
|
+
path: protocol === 'SSH' ? input.path : decodeURIComponent(input.path)
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
match = undefined;
|
|
237
|
+
}
|
|
238
|
+
if (match === undefined) {
|
|
239
|
+
response.end('{"matched":false}');
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (protocol === 'HTTPS') {
|
|
243
|
+
response.end(JSON.stringify({ matched: true, username: match.username, password: match.secret }));
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
let key = context.keys.get(match.id);
|
|
247
|
+
if (key === undefined) {
|
|
248
|
+
const credential = match;
|
|
249
|
+
key = (async () => {
|
|
250
|
+
context.directory ??= this.privateRoot().then(async (root) => {
|
|
251
|
+
const directory = await mkdtemp(join(root, 'context-'));
|
|
252
|
+
try {
|
|
253
|
+
await writeFile(join(directory, 'ssh_config'), '', { mode: 0o600, flag: 'wx' });
|
|
254
|
+
return directory;
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
await rm(directory, { recursive: true, force: true });
|
|
258
|
+
throw error;
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
const file = join(await context.directory, `${randomBytes(16).toString('hex')}.key`);
|
|
262
|
+
await writeFile(file, `${credential.secret.trim()}\n`, { mode: 0o600, flag: 'wx' });
|
|
263
|
+
return file;
|
|
264
|
+
})();
|
|
265
|
+
context.keys.set(match.id, key);
|
|
266
|
+
}
|
|
267
|
+
response.end(JSON.stringify({
|
|
268
|
+
matched: true,
|
|
269
|
+
id: match.id,
|
|
270
|
+
username: match.username,
|
|
271
|
+
keyFile: await key,
|
|
272
|
+
configFile: join(await context.directory, 'ssh_config'),
|
|
273
|
+
knownHostsFile: this.options.knownHostsFile ?? join(homedir(), '.ssh', 'known_hosts'),
|
|
274
|
+
askpassImport: helperImport('git-askpass')
|
|
275
|
+
}));
|
|
276
|
+
}
|
|
277
|
+
finally {
|
|
278
|
+
release();
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
privateRoot() {
|
|
282
|
+
this.root ??= (async () => {
|
|
283
|
+
const parent = this.options.directory ?? tmpdir();
|
|
284
|
+
if (this.options.directory !== undefined) {
|
|
285
|
+
await mkdir(parent, { recursive: true, mode: 0o700 });
|
|
286
|
+
if ((await lstat(parent)).isSymbolicLink())
|
|
287
|
+
throw new Error('GIT_CREDENTIAL_DIRECTORY_INVALID');
|
|
288
|
+
for (const item of await readdir(parent, { withFileTypes: true })) {
|
|
289
|
+
const pid = /^mar-git-(\d+)-/u.exec(item.name)?.[1];
|
|
290
|
+
if (pid === undefined || !item.isDirectory())
|
|
291
|
+
continue;
|
|
292
|
+
try {
|
|
293
|
+
process.kill(Number(pid), 0);
|
|
294
|
+
}
|
|
295
|
+
catch (error) {
|
|
296
|
+
if (error.code === 'ESRCH')
|
|
297
|
+
await rm(join(parent, item.name), { recursive: true, force: true });
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
const root = await mkdtemp(join(parent, `mar-git-${process.pid}-`));
|
|
302
|
+
try {
|
|
303
|
+
if (process.platform === 'win32') {
|
|
304
|
+
const { stdout } = await execute('whoami', [], { windowsHide: true });
|
|
305
|
+
await execute('icacls', [root, '/inheritance:r', '/grant:r', `${stdout.trim()}:(OI)(CI)F`], { windowsHide: true });
|
|
306
|
+
}
|
|
307
|
+
else
|
|
308
|
+
await chmod(root, 0o700);
|
|
309
|
+
return root;
|
|
310
|
+
}
|
|
311
|
+
catch {
|
|
312
|
+
await rm(root, { recursive: true, force: true });
|
|
313
|
+
throw new Error('GIT_CREDENTIAL_DIRECTORY_INVALID');
|
|
314
|
+
}
|
|
315
|
+
})();
|
|
316
|
+
return this.root;
|
|
317
|
+
}
|
|
318
|
+
gitWrapper(binary) {
|
|
319
|
+
let wrapper = this.wrappers.get(binary);
|
|
320
|
+
if (wrapper !== undefined)
|
|
321
|
+
return wrapper;
|
|
322
|
+
wrapper = (async () => {
|
|
323
|
+
const directory = join(await this.privateRoot(), `bin-${createHash('sha256').update(binary).digest('hex').slice(0, 16)}`);
|
|
324
|
+
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
325
|
+
if (process.platform === 'win32') {
|
|
326
|
+
await writeFile(join(directory, 'git.cmd'), `@echo off\r\nset "MAR_GIT_BINARY=${windowsEnvironmentValue(binary)}"\r\n${windowsCommand([...helperArguments('git-execution-helper'), 'invoke'])} %*\r\n`, { mode: 0o700, flag: 'wx' });
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
const executable = join(directory, 'git');
|
|
330
|
+
await writeFile(executable, `#!/bin/sh\nMAR_GIT_BINARY=${shellArgument(binary)}\nexport MAR_GIT_BINARY\nexec ${helperCommand('git-execution-helper')} invoke "$@"\n`, { mode: 0o700, flag: 'wx' });
|
|
331
|
+
await chmod(executable, 0o700);
|
|
332
|
+
}
|
|
333
|
+
return directory;
|
|
334
|
+
})();
|
|
335
|
+
this.wrappers.set(binary, wrapper);
|
|
336
|
+
return wrapper;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
function matchRemoteCredential(credentials, remote) {
|
|
340
|
+
try {
|
|
341
|
+
if (remote.startsWith('https://')) {
|
|
342
|
+
const address = new URL(remote);
|
|
343
|
+
return selectGitCredential(credentials, {
|
|
344
|
+
protocol: 'HTTPS',
|
|
345
|
+
host: address.hostname,
|
|
346
|
+
port: Number(address.port || 443),
|
|
347
|
+
path: decodeURIComponent(address.pathname)
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
if (remote.startsWith('ssh://')) {
|
|
351
|
+
const address = new URL(remote);
|
|
352
|
+
return selectGitCredential(credentials, {
|
|
353
|
+
protocol: 'SSH',
|
|
354
|
+
host: address.hostname,
|
|
355
|
+
port: Number(address.port || 22),
|
|
356
|
+
path: decodeURIComponent(address.pathname)
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
const scp = /^(?:[^@/\s]+@)?([^:/\s]+):(.+)$/u.exec(remote);
|
|
360
|
+
if (scp === null)
|
|
361
|
+
return undefined;
|
|
362
|
+
return selectGitCredential(credentials, {
|
|
363
|
+
protocol: 'SSH',
|
|
364
|
+
host: scp[1],
|
|
365
|
+
port: 22,
|
|
366
|
+
path: scp[2]
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
catch {
|
|
370
|
+
return undefined;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
async function gitExecutable(environment) {
|
|
374
|
+
const command = process.platform === 'win32' ? 'where.exe' : '/bin/sh';
|
|
375
|
+
const args = process.platform === 'win32' ? ['git.exe'] : ['-lc', 'command -v git'];
|
|
376
|
+
const { stdout } = await execute(command, args, {
|
|
377
|
+
env: environment,
|
|
378
|
+
windowsHide: true,
|
|
379
|
+
maxBuffer: 64 * 1024
|
|
380
|
+
});
|
|
381
|
+
const executable = stdout
|
|
382
|
+
.split(/\r?\n/u)
|
|
383
|
+
.find((value) => value.trim() !== '')
|
|
384
|
+
?.trim();
|
|
385
|
+
if (executable === undefined)
|
|
386
|
+
throw new Error('GIT_TRANSPORT_UNAVAILABLE');
|
|
387
|
+
return executable;
|
|
388
|
+
}
|
|
389
|
+
function helperLocation(name) {
|
|
390
|
+
const built = new URL(`../${name}.js`, import.meta.url);
|
|
391
|
+
return existsSync(built) ? built : new URL(`../${name}.ts`, import.meta.url);
|
|
392
|
+
}
|
|
393
|
+
function helperArguments(name) {
|
|
394
|
+
const url = helperLocation(name);
|
|
395
|
+
return [
|
|
396
|
+
process.execPath,
|
|
397
|
+
...(url.pathname.endsWith('.ts') ? ['--import', import.meta.resolve('tsx/esm')] : []),
|
|
398
|
+
fileURLToPath(url)
|
|
399
|
+
];
|
|
400
|
+
}
|
|
401
|
+
function helperImport(name) {
|
|
402
|
+
const url = helperLocation(name);
|
|
403
|
+
return `${url.pathname.endsWith('.ts') ? `--import ${JSON.stringify(import.meta.resolve('tsx/esm'))} ` : ''}--import ${JSON.stringify(url.href)}`;
|
|
404
|
+
}
|
|
405
|
+
function helperCommand(name) {
|
|
406
|
+
return helperArguments(name).map(shellArgument).join(' ');
|
|
407
|
+
}
|
|
408
|
+
function shellArgument(value) {
|
|
409
|
+
return `'${value.replaceAll("'", "'\\''").replaceAll('\\', '/')}'`;
|
|
410
|
+
}
|
|
411
|
+
function windowsCommand(args) {
|
|
412
|
+
return args.map((value) => `"${value.replaceAll('"', '""')}"`).join(' ');
|
|
413
|
+
}
|
|
414
|
+
function windowsEnvironmentValue(value) {
|
|
415
|
+
if (/[\r\n\0"]/u.test(value))
|
|
416
|
+
throw new Error('GIT_TRANSPORT_UNAVAILABLE');
|
|
417
|
+
return value.replaceAll('%', '%%');
|
|
418
|
+
}
|