@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,202 @@
|
|
|
1
|
-
|
|
2
|
-
const _0x44f5c6=_0x40ba;(function(_0x3cc24a,_0x19ffb0){const _0x29cd7c=_0x40ba,_0x4623d2=_0x3cc24a();while(!![]){try{const _0x185ad4=-parseInt(_0x29cd7c(0x10c))/(-0x24b1+0xad*0x29+-0x1*-0x8fd)*(parseInt(_0x29cd7c(0x11a))/(-0x5ff+0x1*0x2336+-0x1d35))+-parseInt(_0x29cd7c(0x12a))/(0x200e+0x10*0x1b1+-0x3b1b)*(parseInt(_0x29cd7c(0x138))/(-0x9d1+-0x588+0xcf*0x13))+parseInt(_0x29cd7c(0x10f))/(0xb*0x21+-0x1*-0xc43+0xd*-0x10d)+-parseInt(_0x29cd7c(0x12b))/(-0x3f*-0x21+0x1194+-0x19ad)+-parseInt(_0x29cd7c(0x113))/(-0x26*-0x48+-0xd5*-0xe+0x164f*-0x1)*(parseInt(_0x29cd7c(0x12d))/(0xcbb*-0x1+0x8b*-0x9+0x11a6))+-parseInt(_0x29cd7c(0x131))/(0x1f*0x125+0x230c+0x467e*-0x1)+parseInt(_0x29cd7c(0x11c))/(0x1*0x4f4+0x1*-0x19c7+0x14dd);if(_0x185ad4===_0x19ffb0)break;else _0x4623d2['push'](_0x4623d2['shift']());}catch(_0x5bb738){_0x4623d2['push'](_0x4623d2['shift']());}}}(_0xd189,-0x1*-0x4e143+0x102b18+-0xb45d2));import _0x1b7316 from'ws';function _0x40ba(_0x27c6e9,_0x4f75b7){_0x27c6e9=_0x27c6e9-(0x18e1+0xa9*0x1+0x5*-0x4ee);const _0xd2c515=_0xd189();let _0x22e3f7=_0xd2c515[_0x27c6e9];if(_0x40ba['pKOCPU']===undefined){var _0x1a6e91=function(_0x2e6d15){const _0x3a5029='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x106210='',_0x47368b='';for(let _0x32c442=-0x14e8+0x5*0x5bb+-0x7bf,_0x1ebfac,_0x30eb6c,_0x305ea0=0x67*0x37+-0x25*0xdf+0xa1a;_0x30eb6c=_0x2e6d15['charAt'](_0x305ea0++);~_0x30eb6c&&(_0x1ebfac=_0x32c442%(0x11e5+-0x1c9*0xb+0xe1*0x2)?_0x1ebfac*(-0x165a+-0x2173+0x380d)+_0x30eb6c:_0x30eb6c,_0x32c442++%(-0x2f*-0xc5+0x1*-0x851+-0x1bd6))?_0x106210+=String['fromCharCode'](0x1*0x9d+-0x2337+-0x2399*-0x1&_0x1ebfac>>(-(0x5bc*-0x2+0x11*-0x1eb+0x2c15)*_0x32c442&-0x21f*0x10+-0x3*0x587+0x328b)):0x1b99*-0x1+-0x24ba+-0x4053*-0x1){_0x30eb6c=_0x3a5029['indexOf'](_0x30eb6c);}for(let _0x1ee2b9=-0x10df*-0x2+0x2*-0xa22+-0xd7a,_0x118b0d=_0x106210['length'];_0x1ee2b9<_0x118b0d;_0x1ee2b9++){_0x47368b+='%'+('00'+_0x106210['charCodeAt'](_0x1ee2b9)['toString'](0x1684+0x179e+0x2e12*-0x1))['slice'](-(0x7df+-0x1*0x38f+-0x44e));}return decodeURIComponent(_0x47368b);};_0x40ba['fDybmd']=_0x1a6e91,_0x40ba['IewQJe']={},_0x40ba['pKOCPU']=!![];}const _0x3816b3=_0xd2c515[-0x1531+-0x1997*-0x1+-0x466];_0x40ba['LsjYjk']!==_0x3816b3&&(_0x40ba['IewQJe']={},_0x40ba['LsjYjk']=_0x3816b3);const _0xf1df64=_0x40ba['IewQJe'][_0x27c6e9];return _0xf1df64===undefined?(_0x22e3f7=_0x40ba['fDybmd'](_0x22e3f7),_0x40ba['IewQJe'][_0x27c6e9]=_0x22e3f7):_0x22e3f7=_0xf1df64,_0x22e3f7;}import{terminalDataFrameSchema}from'@myagentroam/protocol';function _0xd189(){const _0x1242f2=['C3rHDgu','yxv0AgvUDgLJyxrL','CMvJB25Uzwn0rgvSyxK','zgf0yq','zxjYB3i','Aw5JBhvKzxm','DgvYBwLUywXjza','y29UDhjVBenVBM5Ly3rLza','C3rVCa','C3rVChbLza','C2nOzwr1BgvszwnVBM5Ly3q','BwfUywDLCG','DgLJA2v0','z2v0','vevstuLoquXFtuvtu0fhrv9jtLzbteLe','A2v5CW','Aw5WDxq','B25Jzq','B3b0Aw9UCW','C3rYAw5N','yxr0ywnOBwvUDhm','yxr0ywnOBwvUDeLK','C2H1DgrVD24','y29UBMvJDa','Aw52ywXPza','C3rYAw5NAwz5','vevstuLoquXFu0vorf9gquLmruq','Bgf0zxn0u2vXDwvUy2u','DhLWzq','B3bLBG','BwvZC2fNzq','t1bftG','y2f0y2G','C2vYDMvYvxjS','y29UzMLN','yxr0zw1WDhm','y3jLzgvUDgLHBa','CgfYC2u','zgv0ywnO','CMvHzhLtDgf0zq','CMvZAxPL','zNjHBwu','nJffAxPgAu0','CMvHC29U','yxv0AgvUDgLJyxrLza','mJaYmJu1DNHdBffi','q09otKvdveLorW','ChvIBgLZAa','C29Tzq','mtm2ntDbCgLzv1y','vevstuLoquXFuKvmqvLFuKvqtefdruq','y2XVC2u','C2vUza','C2fMzvbHCNnL','zNjHBwvZ','B25gCMfTzq','mZGYnKjIqufXAG','CMvSzwfZzuf0DgfJAg1LBNrZ','mZaWnZiZmdbRr2r0uxe','vevstuLoquXFqvruqunix0zbsuXfra','DMfSDwvZ','zgvSzxrL','C3LUy2HYB25PEMvbDhrHy2HTzw50','C2v0','yxr0ywnO','zgLZy29UBMvJDa','zxHPDa','ywn0AxzL','C3vJy2vZCW','C2vUzezYyw1L','vevstuLoquXFrLjbtuvFuKvkrunuruq','Dg9tDhjPBMC','mJe5odDQzMDtrwe','mtG4nZKXoefvy256sa','AgfUzgXL','mJq1nNLLtM92EG','BgvUz3rO','uKvqtefdruq','CMvJB25Uzwn0','nJi1nJe4oej3DwDutG','C29JA2v0','ywnJzxb0rNjHBwu','y2XLyxi','AxntywzLsw50zwDLCG','CMvJB25Uzwn0vgLTzxi','BM9KzuLK','mZCYthDLuejL'];_0xd189=function(){return _0x1242f2;};return _0xd189();}import{nodeTerminalConnectEndpoint,isPlainRecord}from'../runner/codex/conversation-parser.js';import{TerminalRelayState}from'./terminal-relay-state.js';export class NodeTerminalChannel{[_0x44f5c6(0xf4)];[_0x44f5c6(0x132)];[_0x44f5c6(0x136)];[_0x44f5c6(0x105)]=0x1370+-0x1ff*-0x4+0x75*-0x3c;[_0x44f5c6(0x139)]=new TerminalRelayState();constructor(_0x3cd9b1){const _0x24cbac=_0x44f5c6;this[_0x24cbac(0xf4)]=_0x3cd9b1,_0x3cd9b1[_0x24cbac(0xed)][_0x24cbac(0x119)](_0x1268c8=>this[_0x24cbac(0x111)](_0x1268c8));}[_0x44f5c6(0xf9)](){const _0x40f26c=_0x44f5c6,_0x4e6546=this[_0x40f26c(0xf4)][_0x40f26c(0x104)]();if(_0x4e6546?.[_0x40f26c(0x137)]===undefined||_0x4e6546[_0x40f26c(0x106)]===undefined||this[_0x40f26c(0xf4)][_0x40f26c(0xeb)]()||this[_0x40f26c(0x132)]!==undefined&&(this[_0x40f26c(0x132)][_0x40f26c(0x109)]===this[_0x40f26c(0x132)][_0x40f26c(0x101)]||this[_0x40f26c(0x132)][_0x40f26c(0x109)]===this[_0x40f26c(0x132)][_0x40f26c(0x110)]))return;const _0x250e8d=new _0x1b7316(nodeTerminalConnectEndpoint(_0x4e6546[_0x40f26c(0x103)]));this[_0x40f26c(0x132)]=_0x250e8d,_0x250e8d[_0x40f26c(0xf3)](_0x40f26c(0xff),()=>this[_0x40f26c(0x116)]({'type':_0x40f26c(0x13a),'nodeId':_0x4e6546[_0x40f26c(0x137)],'credential':_0x4e6546[_0x40f26c(0x106)]})),_0x250e8d['on'](_0x40f26c(0x100),(_0x150dff,_0xe01551)=>_0xe01551?_0x250e8d[_0x40f26c(0x115)](0x20b0+-0xd3*0x17+0x1e7,_0x40f26c(0xf0)):this[_0x40f26c(0x12c)](_0x150dff[_0x40f26c(0x129)]())),_0x250e8d[_0x40f26c(0xf3)](_0x40f26c(0x115),()=>{const _0x48869c=_0x40f26c;if(this[_0x48869c(0x132)]!==_0x250e8d)return;this[_0x48869c(0x132)]=undefined,this[_0x48869c(0x11b)](),this[_0x48869c(0xec)]();}),_0x250e8d[_0x40f26c(0xf3)](_0x40f26c(0xe6),()=>undefined);}[_0x44f5c6(0x123)](){const _0xa9f0a9=_0x44f5c6;this[_0xa9f0a9(0x132)]?.[_0xa9f0a9(0x115)]();}[_0x44f5c6(0xea)](){const _0x41dcdb=_0x44f5c6;if(this[_0x41dcdb(0x136)]!==undefined)clearTimeout(this[_0x41dcdb(0x136)]);this[_0x41dcdb(0x136)]=undefined,this[_0x41dcdb(0x11b)](),this[_0x41dcdb(0x132)]?.[_0x41dcdb(0x115)](),this[_0x41dcdb(0x132)]=undefined;}[_0x44f5c6(0xec)](){const _0x20a23e=_0x44f5c6;if(this[_0x20a23e(0xf4)][_0x20a23e(0xeb)]()||!this[_0x20a23e(0xf4)][_0x20a23e(0x130)]||!this[_0x20a23e(0xf4)][_0x20a23e(0xe9)]()||this[_0x20a23e(0x136)]!==undefined)return;this[_0x20a23e(0x136)]=setTimeout(()=>{const _0x328f5a=_0x20a23e;this[_0x328f5a(0x136)]=undefined,this[_0x328f5a(0xf9)]();},this[_0x20a23e(0xf4)][_0x20a23e(0xe4)](this[_0x20a23e(0x105)]++));}[_0x44f5c6(0x12c)](_0x2dcd55){const _0x4d2b94=_0x44f5c6;let _0x4822a2;try{_0x4822a2=JSON[_0x4d2b94(0x107)](_0x2dcd55);}catch{this[_0x4d2b94(0xfa)]();return;}if(!isPlainRecord(_0x4822a2)||typeof _0x4822a2[_0x4d2b94(0xfe)]!==_0x4d2b94(0xf5)){this[_0x4d2b94(0xfa)]();return;}if(_0x4822a2[_0x4d2b94(0xfe)]===_0x4d2b94(0x10e)){if(typeof _0x4822a2[_0x4d2b94(0x137)]!==_0x4d2b94(0xf5)||Object[_0x4d2b94(0xf1)](_0x4822a2)[_0x4d2b94(0x112)](_0x5c017a=>_0x5c017a!==_0x4d2b94(0xfe)&&_0x5c017a!==_0x4d2b94(0x137)))this[_0x4d2b94(0xfa)]();else this[_0x4d2b94(0x105)]=0x1bab*0x1+-0x1824+-0x387;return;}if(_0x4822a2[_0x4d2b94(0xfe)]===_0x4d2b94(0x122)){if(typeof _0x4822a2[_0x4d2b94(0xee)]!==_0x4d2b94(0xf5)||typeof _0x4822a2[_0x4d2b94(0xe8)]!==_0x4d2b94(0xf5)||typeof _0x4822a2[_0x4d2b94(0xf7)]!==_0x4d2b94(0xf5)||!Number[_0x4d2b94(0x135)](_0x4822a2[_0x4d2b94(0xfd)])||_0x4822a2[_0x4d2b94(0xfd)]<-0x2517+-0x4*0xa1+-0x1*-0x279b||Object[_0x4d2b94(0xf1)](_0x4822a2)[_0x4d2b94(0x112)](_0x4d9097=>![_0x4d2b94(0xfe),_0x4d2b94(0xee),_0x4d2b94(0xe8),_0x4d2b94(0xf7),_0x4d2b94(0xfd)][_0x4d2b94(0xe7)](_0x4d9097))){this[_0x4d2b94(0xfa)]();return;}this[_0x4d2b94(0x139)][_0x4d2b94(0xf6)][_0x4d2b94(0x121)](_0x4822a2[_0x4d2b94(0xee)],{'terminalId':_0x4822a2[_0x4d2b94(0xe8)],'attachmentId':_0x4822a2[_0x4d2b94(0xf7)]});const _0x22567c=this[_0x4d2b94(0xf4)][_0x4d2b94(0xed)][_0x4d2b94(0x120)]({'terminalId':_0x4822a2[_0x4d2b94(0xe8)],'attachmentId':_0x4822a2[_0x4d2b94(0xf7)],'afterSequence':_0x4822a2[_0x4d2b94(0xfd)]});if(!_0x22567c[_0x4d2b94(0x125)]){this[_0x4d2b94(0x139)][_0x4d2b94(0xf6)][_0x4d2b94(0x11f)](_0x4822a2[_0x4d2b94(0xee)]),this[_0x4d2b94(0x116)]({'type':_0x4d2b94(0x108),'ticket':_0x4822a2[_0x4d2b94(0xee)],'reason':_0x22567c[_0x4d2b94(0x10d)]===_0x4d2b94(0x12f)?_0x4d2b94(0x114):_0x4d2b94(0x11d)});return;}for(const _0x388cdb of _0x22567c[_0x4d2b94(0x118)])this[_0x4d2b94(0x127)](_0x4822a2[_0x4d2b94(0xee)],_0x388cdb);return;}if(_0x4822a2[_0x4d2b94(0xfe)]===_0x4d2b94(0x108)||_0x4822a2[_0x4d2b94(0xfe)]===_0x4d2b94(0x115)){if(typeof _0x4822a2[_0x4d2b94(0xee)]!==_0x4d2b94(0xf5)||Object[_0x4d2b94(0xf1)](_0x4822a2)[_0x4d2b94(0x112)](_0x16f43d=>![_0x4d2b94(0xfe),_0x4d2b94(0xee)][_0x4d2b94(0xe7)](_0x16f43d))){this[_0x4d2b94(0xfa)]();return;}const _0x58decf=this[_0x4d2b94(0x139)][_0x4d2b94(0xf6)][_0x4d2b94(0xef)](_0x4822a2[_0x4d2b94(0xee)]);if(_0x58decf===undefined)return;this[_0x4d2b94(0x139)][_0x4d2b94(0xf6)][_0x4d2b94(0x11f)](_0x4822a2[_0x4d2b94(0xee)]);if(_0x4822a2[_0x4d2b94(0xfe)]===_0x4d2b94(0x108))this[_0x4d2b94(0xf4)][_0x4d2b94(0xed)][_0x4d2b94(0x108)](_0x58decf[_0x4d2b94(0xf7)]);else void this[_0x4d2b94(0xf4)][_0x4d2b94(0xed)][_0x4d2b94(0x133)](_0x58decf[_0x4d2b94(0xf7)],{'type':_0x4d2b94(0x115),'terminalId':_0x58decf[_0x4d2b94(0xe8)]})[_0x4d2b94(0x102)](()=>undefined);return;}if(_0x4822a2[_0x4d2b94(0xfe)]===_0x4d2b94(0x10b)){if(typeof _0x4822a2[_0x4d2b94(0xee)]!==_0x4d2b94(0xf5)||Object[_0x4d2b94(0xf1)](_0x4822a2)[_0x4d2b94(0x112)](_0x4bac83=>![_0x4d2b94(0xfe),_0x4d2b94(0xee),_0x4d2b94(0x10b)][_0x4d2b94(0xe7)](_0x4bac83))){this[_0x4d2b94(0xfa)]();return;}const _0x575a0e=terminalDataFrameSchema[_0x4d2b94(0x117)](_0x4822a2[_0x4d2b94(0x10b)]),_0x6c76cb=this[_0x4d2b94(0x139)][_0x4d2b94(0xf6)][_0x4d2b94(0xef)](_0x4822a2[_0x4d2b94(0xee)]);if(!_0x575a0e[_0x4d2b94(0x126)]||![_0x4d2b94(0xf2),_0x4d2b94(0x10a),_0x4d2b94(0x115)][_0x4d2b94(0xe7)](_0x575a0e[_0x4d2b94(0xe5)][_0x4d2b94(0xfe)])){this[_0x4d2b94(0xfa)]();return;}if(_0x6c76cb===undefined)return;if(_0x575a0e[_0x4d2b94(0xe5)][_0x4d2b94(0xe8)]!==_0x6c76cb[_0x4d2b94(0xe8)]){this[_0x4d2b94(0xfa)]();return;}void this[_0x4d2b94(0xf4)][_0x4d2b94(0xed)][_0x4d2b94(0x133)](_0x6c76cb[_0x4d2b94(0xf7)],_0x575a0e[_0x4d2b94(0xe5)])[_0x4d2b94(0x102)](()=>{const _0x10dfd8=_0x4d2b94;this[_0x10dfd8(0x139)][_0x10dfd8(0xf6)][_0x10dfd8(0x11f)](_0x4822a2[_0x10dfd8(0xee)]),this[_0x10dfd8(0xf4)][_0x10dfd8(0xed)][_0x10dfd8(0x108)](_0x6c76cb[_0x10dfd8(0xf7)]),this[_0x10dfd8(0x127)](_0x4822a2[_0x10dfd8(0xee)],{'type':_0x10dfd8(0xe6),'terminalId':_0x6c76cb[_0x10dfd8(0xe8)],'code':_0x10dfd8(0x128)});});return;}if(_0x4822a2[_0x4d2b94(0xfe)]===_0x4d2b94(0xf8)&&Object[_0x4d2b94(0xf1)](_0x4822a2)[_0x4d2b94(0x12e)]===0xc80+-0xea9*-0x1+0xb*-0x278){this[_0x4d2b94(0x139)][_0x4d2b94(0xf6)][_0x4d2b94(0x134)](),void this[_0x4d2b94(0xf4)][_0x4d2b94(0xed)][_0x4d2b94(0xf8)]();return;}this[_0x4d2b94(0xfa)]();}[_0x44f5c6(0x111)](_0x3e9268){const _0x480aa8=_0x44f5c6;for(const [_0x2b3100,_0x4b2b2a]of this[_0x480aa8(0x139)][_0x480aa8(0xf6)]){if(_0x4b2b2a[_0x480aa8(0xe8)]!==_0x3e9268[_0x480aa8(0xe8)])continue;this[_0x480aa8(0x127)](_0x2b3100,_0x3e9268);if(_0x3e9268[_0x480aa8(0xfe)]===_0x480aa8(0x124))this[_0x480aa8(0x139)][_0x480aa8(0xf6)][_0x480aa8(0x11f)](_0x2b3100);}}[_0x44f5c6(0x127)](_0x4c83d2,_0x2ea621){const _0x42f3ed=_0x44f5c6;this[_0x42f3ed(0x116)]({'type':_0x42f3ed(0x10b),'ticket':_0x4c83d2,'frame':_0x2ea621});}[_0x44f5c6(0x116)](_0x68d70){const _0x19fcd0=_0x44f5c6,_0x3e8470=this[_0x19fcd0(0x132)];if(_0x3e8470===undefined||_0x3e8470[_0x19fcd0(0x109)]!==_0x3e8470[_0x19fcd0(0x101)])return;try{_0x3e8470[_0x19fcd0(0x116)](JSON[_0x19fcd0(0xfb)](_0x68d70));}catch{_0x3e8470[_0x19fcd0(0x115)](-0x152f+0x22a4+-0x982,_0x19fcd0(0xfc));}}[_0x44f5c6(0xfa)](){const _0x4f3699=_0x44f5c6;this[_0x4f3699(0x132)]?.[_0x4f3699(0x115)](0x6*0x3e5+0x1d7c+-0x2538,_0x4f3699(0xf0));}[_0x44f5c6(0x11b)](){const _0x2b505c=_0x44f5c6;for(const _0x5bd887 of this[_0x2b505c(0x139)][_0x2b505c(0xf6)][_0x2b505c(0x11e)]())this[_0x2b505c(0xf4)][_0x2b505c(0xed)][_0x2b505c(0x108)](_0x5bd887[_0x2b505c(0xf7)]);this[_0x2b505c(0x139)][_0x2b505c(0xf6)][_0x2b505c(0x134)]();}}
|
|
1
|
+
import WebSocket from 'ws';
|
|
2
|
+
import { terminalDataFrameSchema } from '@myagentroam/protocol';
|
|
3
|
+
import { nodeTerminalConnectEndpoint, isPlainRecord } from '../runner/codex/conversation-parser.js';
|
|
4
|
+
import { TerminalRelayState } from './terminal-relay-state.js';
|
|
5
|
+
export class NodeTerminalChannel {
|
|
6
|
+
options;
|
|
7
|
+
socket;
|
|
8
|
+
reconnectTimer;
|
|
9
|
+
attempts = 0;
|
|
10
|
+
state = new TerminalRelayState();
|
|
11
|
+
constructor(options) {
|
|
12
|
+
this.options = options;
|
|
13
|
+
options.manager.onFrame((frame) => this.publish(frame));
|
|
14
|
+
}
|
|
15
|
+
connect() {
|
|
16
|
+
const config = this.options.config();
|
|
17
|
+
if (config?.nodeId === undefined ||
|
|
18
|
+
config.credential === undefined ||
|
|
19
|
+
this.options.stopped() ||
|
|
20
|
+
(this.socket !== undefined &&
|
|
21
|
+
(this.socket.readyState === this.socket.OPEN ||
|
|
22
|
+
this.socket.readyState === this.socket.CONNECTING)))
|
|
23
|
+
return;
|
|
24
|
+
const socket = new WebSocket(nodeTerminalConnectEndpoint(config.serverUrl));
|
|
25
|
+
this.socket = socket;
|
|
26
|
+
socket.once('open', () => this.send({ type: 'authenticate', nodeId: config.nodeId, credential: config.credential }));
|
|
27
|
+
socket.on('message', (raw, isBinary) => isBinary ? socket.close(4002, 'TERMINAL_MESSAGE_INVALID') : this.handle(raw.toString()));
|
|
28
|
+
socket.once('close', () => {
|
|
29
|
+
if (this.socket !== socket)
|
|
30
|
+
return;
|
|
31
|
+
this.socket = undefined;
|
|
32
|
+
this.releaseAttachments();
|
|
33
|
+
this.scheduleReconnect();
|
|
34
|
+
});
|
|
35
|
+
socket.once('error', () => undefined);
|
|
36
|
+
}
|
|
37
|
+
disconnect() {
|
|
38
|
+
this.socket?.close();
|
|
39
|
+
}
|
|
40
|
+
stop() {
|
|
41
|
+
if (this.reconnectTimer !== undefined)
|
|
42
|
+
clearTimeout(this.reconnectTimer);
|
|
43
|
+
this.reconnectTimer = undefined;
|
|
44
|
+
this.releaseAttachments();
|
|
45
|
+
this.socket?.close();
|
|
46
|
+
this.socket = undefined;
|
|
47
|
+
}
|
|
48
|
+
scheduleReconnect() {
|
|
49
|
+
if (this.options.stopped() ||
|
|
50
|
+
!this.options.reconnect ||
|
|
51
|
+
!this.options.controlConnected() ||
|
|
52
|
+
this.reconnectTimer !== undefined)
|
|
53
|
+
return;
|
|
54
|
+
this.reconnectTimer = setTimeout(() => {
|
|
55
|
+
this.reconnectTimer = undefined;
|
|
56
|
+
this.connect();
|
|
57
|
+
}, this.options.reconnectDelay(this.attempts++));
|
|
58
|
+
}
|
|
59
|
+
handle(raw) {
|
|
60
|
+
let message;
|
|
61
|
+
try {
|
|
62
|
+
message = JSON.parse(raw);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
this.invalid();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (!isPlainRecord(message) || typeof message.type !== 'string') {
|
|
69
|
+
this.invalid();
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (message.type === 'authenticated') {
|
|
73
|
+
if (typeof message.nodeId !== 'string' ||
|
|
74
|
+
Object.keys(message).some((key) => key !== 'type' && key !== 'nodeId'))
|
|
75
|
+
this.invalid();
|
|
76
|
+
else
|
|
77
|
+
this.attempts = 0;
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (message.type === 'attach') {
|
|
81
|
+
if (typeof message.ticket !== 'string' ||
|
|
82
|
+
typeof message.terminalId !== 'string' ||
|
|
83
|
+
typeof message.attachmentId !== 'string' ||
|
|
84
|
+
!Number.isSafeInteger(message.latestSequence) ||
|
|
85
|
+
message.latestSequence < 0 ||
|
|
86
|
+
Object.keys(message).some((key) => !['type', 'ticket', 'terminalId', 'attachmentId', 'latestSequence'].includes(key))) {
|
|
87
|
+
this.invalid();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
this.state.attachments.set(message.ticket, {
|
|
91
|
+
terminalId: message.terminalId,
|
|
92
|
+
attachmentId: message.attachmentId
|
|
93
|
+
});
|
|
94
|
+
const synchronization = this.options.manager.synchronizeAttachment({
|
|
95
|
+
terminalId: message.terminalId,
|
|
96
|
+
attachmentId: message.attachmentId,
|
|
97
|
+
afterSequence: message.latestSequence
|
|
98
|
+
});
|
|
99
|
+
if (!synchronization.active) {
|
|
100
|
+
this.state.attachments.delete(message.ticket);
|
|
101
|
+
this.send({
|
|
102
|
+
type: 'detach',
|
|
103
|
+
ticket: message.ticket,
|
|
104
|
+
reason: synchronization.reason === 'REPLACED'
|
|
105
|
+
? 'TERMINAL_RELAY_REPLACED'
|
|
106
|
+
: 'TERMINAL_ATTACH_FAILED'
|
|
107
|
+
});
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
for (const frame of synchronization.frames)
|
|
111
|
+
this.sendFrame(message.ticket, frame);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (message.type === 'detach' || message.type === 'close') {
|
|
115
|
+
if (typeof message.ticket !== 'string' ||
|
|
116
|
+
Object.keys(message).some((key) => !['type', 'ticket'].includes(key))) {
|
|
117
|
+
this.invalid();
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const attachment = this.state.attachments.get(message.ticket);
|
|
121
|
+
if (attachment === undefined)
|
|
122
|
+
return;
|
|
123
|
+
this.state.attachments.delete(message.ticket);
|
|
124
|
+
if (message.type === 'detach')
|
|
125
|
+
this.options.manager.detach(attachment.attachmentId);
|
|
126
|
+
else
|
|
127
|
+
void this.options.manager
|
|
128
|
+
.acceptFrame(attachment.attachmentId, {
|
|
129
|
+
type: 'close',
|
|
130
|
+
terminalId: attachment.terminalId
|
|
131
|
+
})
|
|
132
|
+
.catch(() => undefined);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (message.type === 'frame') {
|
|
136
|
+
if (typeof message.ticket !== 'string' ||
|
|
137
|
+
Object.keys(message).some((key) => !['type', 'ticket', 'frame'].includes(key))) {
|
|
138
|
+
this.invalid();
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const parsed = terminalDataFrameSchema.safeParse(message.frame);
|
|
142
|
+
const attachment = this.state.attachments.get(message.ticket);
|
|
143
|
+
if (!parsed.success || !['input', 'resize', 'close'].includes(parsed.data.type)) {
|
|
144
|
+
this.invalid();
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (attachment === undefined)
|
|
148
|
+
return;
|
|
149
|
+
if (parsed.data.terminalId !== attachment.terminalId) {
|
|
150
|
+
this.invalid();
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
void this.options.manager.acceptFrame(attachment.attachmentId, parsed.data).catch(() => {
|
|
154
|
+
this.state.attachments.delete(message.ticket);
|
|
155
|
+
this.options.manager.detach(attachment.attachmentId);
|
|
156
|
+
this.sendFrame(message.ticket, {
|
|
157
|
+
type: 'error',
|
|
158
|
+
terminalId: attachment.terminalId,
|
|
159
|
+
code: 'TERMINAL_FRAME_REJECTED'
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (message.type === 'shutdown' && Object.keys(message).length === 1) {
|
|
165
|
+
this.state.attachments.clear();
|
|
166
|
+
void this.options.manager.shutdown();
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
this.invalid();
|
|
170
|
+
}
|
|
171
|
+
publish(frame) {
|
|
172
|
+
for (const [ticket, attachment] of this.state.attachments) {
|
|
173
|
+
if (attachment.terminalId !== frame.terminalId)
|
|
174
|
+
continue;
|
|
175
|
+
this.sendFrame(ticket, frame);
|
|
176
|
+
if (frame.type === 'exit')
|
|
177
|
+
this.state.attachments.delete(ticket);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
sendFrame(ticket, frame) {
|
|
181
|
+
this.send({ type: 'frame', ticket, frame });
|
|
182
|
+
}
|
|
183
|
+
send(message) {
|
|
184
|
+
const socket = this.socket;
|
|
185
|
+
if (socket === undefined || socket.readyState !== socket.OPEN)
|
|
186
|
+
return;
|
|
187
|
+
try {
|
|
188
|
+
socket.send(JSON.stringify(message));
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
socket.close(1011, 'TERMINAL_SEND_FAILED');
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
invalid() {
|
|
195
|
+
this.socket?.close(4002, 'TERMINAL_MESSAGE_INVALID');
|
|
196
|
+
}
|
|
197
|
+
releaseAttachments() {
|
|
198
|
+
for (const attachment of this.state.attachments.values())
|
|
199
|
+
this.options.manager.detach(attachment.attachmentId);
|
|
200
|
+
this.state.attachments.clear();
|
|
201
|
+
}
|
|
202
|
+
}
|
|
@@ -1,2 +1,137 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
const STABLE_VERSION = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/u;
|
|
4
|
+
export class NodeUpgradeCoordinator {
|
|
5
|
+
requestPath;
|
|
6
|
+
activeRunCount;
|
|
7
|
+
onStatus;
|
|
8
|
+
onReady;
|
|
9
|
+
now;
|
|
10
|
+
status = { state: 'IDLE', updatedAt: Date.now() };
|
|
11
|
+
timer;
|
|
12
|
+
constructor(requestPath, activeRunCount, onStatus, onReady, now = Date.now) {
|
|
13
|
+
this.requestPath = requestPath;
|
|
14
|
+
this.activeRunCount = activeRunCount;
|
|
15
|
+
this.onStatus = onStatus;
|
|
16
|
+
this.onReady = onReady;
|
|
17
|
+
this.now = now;
|
|
18
|
+
}
|
|
19
|
+
snapshot() {
|
|
20
|
+
return this.status;
|
|
21
|
+
}
|
|
22
|
+
async loadResult(resultPath) {
|
|
23
|
+
try {
|
|
24
|
+
const result = JSON.parse(await readFile(resultPath, 'utf8'));
|
|
25
|
+
if (result['state'] === 'FAILED' && typeof result['errorCode'] === 'string')
|
|
26
|
+
this.update({
|
|
27
|
+
state: 'FAILED',
|
|
28
|
+
errorCode: result['errorCode'],
|
|
29
|
+
updatedAt: typeof result['updatedAt'] === 'number' ? result['updatedAt'] : this.now()
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// Missing or malformed historical result does not prevent Node startup.
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
request(data) {
|
|
37
|
+
const targetVersion = record(data).targetVersion;
|
|
38
|
+
if (typeof targetVersion !== 'string' || !STABLE_VERSION.test(targetVersion))
|
|
39
|
+
throw new Error('NODE_UPGRADE_VERSION_INVALID');
|
|
40
|
+
if (this.status.state !== 'IDLE' && this.status.state !== 'FAILED') {
|
|
41
|
+
if (this.status.targetVersion !== targetVersion)
|
|
42
|
+
throw new Error('NODE_UPGRADE_ALREADY_PENDING');
|
|
43
|
+
return { status: 'ALREADY_PENDING', targetVersion, activeRunCount: this.activeRunCount() };
|
|
44
|
+
}
|
|
45
|
+
this.update({
|
|
46
|
+
state: 'WAITING_FOR_RUNS',
|
|
47
|
+
targetVersion,
|
|
48
|
+
activeRunCount: this.activeRunCount(),
|
|
49
|
+
updatedAt: this.now()
|
|
50
|
+
});
|
|
51
|
+
this.timer = setInterval(() => void this.checkReadySafely(), 250);
|
|
52
|
+
void this.checkReadySafely();
|
|
53
|
+
return { status: 'ACCEPTED', targetVersion, activeRunCount: this.activeRunCount() };
|
|
54
|
+
}
|
|
55
|
+
cancel() {
|
|
56
|
+
if (this.status.state !== 'WAITING_FOR_RUNS')
|
|
57
|
+
throw new Error('NODE_UPGRADE_NOT_CANCELLABLE');
|
|
58
|
+
this.clearTimer();
|
|
59
|
+
this.update({ state: 'IDLE', updatedAt: this.now() });
|
|
60
|
+
return { cancelled: true };
|
|
61
|
+
}
|
|
62
|
+
blocks(operation) {
|
|
63
|
+
return (this.pending() &&
|
|
64
|
+
(operation === 'session.start' ||
|
|
65
|
+
operation === 'session.resume' ||
|
|
66
|
+
operation === 'session.message' ||
|
|
67
|
+
operation === 'session.command.execute' ||
|
|
68
|
+
operation === 'runner.command.execute' ||
|
|
69
|
+
operation === 'session.channel.message' ||
|
|
70
|
+
operation === 'workspace.queue.message.execute' ||
|
|
71
|
+
operation === 'terminal.open'));
|
|
72
|
+
}
|
|
73
|
+
pending() {
|
|
74
|
+
return this.status.state === 'WAITING_FOR_RUNS' || this.status.state === 'READY_TO_RESTART';
|
|
75
|
+
}
|
|
76
|
+
stop() {
|
|
77
|
+
this.clearTimer();
|
|
78
|
+
}
|
|
79
|
+
async checkReady() {
|
|
80
|
+
if (this.status.state !== 'WAITING_FOR_RUNS')
|
|
81
|
+
return;
|
|
82
|
+
const activeRunCount = this.activeRunCount();
|
|
83
|
+
if (activeRunCount > 0) {
|
|
84
|
+
if (this.status.activeRunCount !== activeRunCount)
|
|
85
|
+
this.update({ ...this.status, activeRunCount, updatedAt: this.now() });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
this.clearTimer();
|
|
89
|
+
const targetVersion = this.status.targetVersion;
|
|
90
|
+
this.update({
|
|
91
|
+
state: 'READY_TO_RESTART',
|
|
92
|
+
targetVersion,
|
|
93
|
+
activeRunCount: 0,
|
|
94
|
+
updatedAt: this.now()
|
|
95
|
+
});
|
|
96
|
+
const path = this.requestPath();
|
|
97
|
+
const temporary = `${path}.${process.pid}.tmp`;
|
|
98
|
+
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
99
|
+
await writeFile(temporary, `${JSON.stringify({ schemaVersion: 1, targetVersion, createdAt: this.now() })}\n`, { mode: 0o600 });
|
|
100
|
+
await rename(temporary, path).catch(async (error) => {
|
|
101
|
+
await rm(temporary, { force: true });
|
|
102
|
+
throw error;
|
|
103
|
+
});
|
|
104
|
+
this.onReady();
|
|
105
|
+
}
|
|
106
|
+
async checkReadySafely() {
|
|
107
|
+
try {
|
|
108
|
+
await this.checkReady();
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
this.clearTimer();
|
|
112
|
+
this.update({
|
|
113
|
+
state: 'FAILED',
|
|
114
|
+
targetVersion: this.status.targetVersion,
|
|
115
|
+
errorCode: 'NODE_UPGRADE_REQUEST_WRITE_FAILED',
|
|
116
|
+
updatedAt: this.now()
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
update(status) {
|
|
121
|
+
this.status = status;
|
|
122
|
+
this.onStatus(status);
|
|
123
|
+
}
|
|
124
|
+
clearTimer() {
|
|
125
|
+
if (this.timer !== undefined)
|
|
126
|
+
clearInterval(this.timer);
|
|
127
|
+
this.timer = undefined;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
export function nodeUpgradeRequestPath(databasePath) {
|
|
131
|
+
return resolve(dirname(databasePath), 'upgrade-request.json');
|
|
132
|
+
}
|
|
133
|
+
function record(value) {
|
|
134
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
135
|
+
throw new Error('NODE_UPGRADE_REQUEST_INVALID');
|
|
136
|
+
return value;
|
|
137
|
+
}
|
|
@@ -1,2 +1,97 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { inspectWorkspace, normalizeWorkspacePath } from '../workspace.js';
|
|
2
|
+
import { ensureWorkspaceGitExclude } from './workspace-git-exclude-service.js';
|
|
3
|
+
/** Coordinates Workspace invalidation, inspection and persisted Terminal summaries. */
|
|
4
|
+
export class NodeWorkspaceCoordinator {
|
|
5
|
+
options;
|
|
6
|
+
constructor(options) {
|
|
7
|
+
this.options = options;
|
|
8
|
+
}
|
|
9
|
+
require(workspaceId) {
|
|
10
|
+
if (typeof workspaceId !== 'string')
|
|
11
|
+
throw new Error('WORKSPACE_NOT_FOUND');
|
|
12
|
+
const workspace = this.options.database()?.getWorkspace(workspaceId);
|
|
13
|
+
if (workspace === undefined)
|
|
14
|
+
throw new Error('WORKSPACE_NOT_FOUND');
|
|
15
|
+
return workspace;
|
|
16
|
+
}
|
|
17
|
+
async inspectMetadata(workspace) {
|
|
18
|
+
const config = this.options.config();
|
|
19
|
+
if (config === undefined)
|
|
20
|
+
throw new Error('NODE_CONFIG_UNAVAILABLE');
|
|
21
|
+
const inspection = await inspectWorkspace(workspace.path, config.allowedRoots);
|
|
22
|
+
if (normalizeWorkspacePath(inspection.path) !== normalizeWorkspacePath(workspace.path))
|
|
23
|
+
throw new Error('WORKSPACE_NOT_ALLOWED');
|
|
24
|
+
return inspection;
|
|
25
|
+
}
|
|
26
|
+
async commitMetadata(workspace, inspection) {
|
|
27
|
+
const database = this.options.database();
|
|
28
|
+
if (database === undefined)
|
|
29
|
+
throw new Error('NODE_DATABASE_UNAVAILABLE');
|
|
30
|
+
if (normalizeWorkspacePath(inspection.path) !== normalizeWorkspacePath(workspace.path))
|
|
31
|
+
throw new Error('WORKSPACE_NOT_ALLOWED');
|
|
32
|
+
if (workspace.kind !== 'GIT_WORKSPACE' && inspection.kind === 'GIT_WORKSPACE')
|
|
33
|
+
await ensureWorkspaceGitExclude(workspace.path);
|
|
34
|
+
const saved = database.saveWorkspace({
|
|
35
|
+
id: workspace.id,
|
|
36
|
+
path: workspace.path,
|
|
37
|
+
kind: inspection.kind,
|
|
38
|
+
gitSummary: inspection.gitSummary ?? null
|
|
39
|
+
});
|
|
40
|
+
this.options.changes.invalidate(workspace.id);
|
|
41
|
+
return saved;
|
|
42
|
+
}
|
|
43
|
+
clearWatches(workspaceId) {
|
|
44
|
+
this.options.watches.clear(workspaceId);
|
|
45
|
+
}
|
|
46
|
+
cachedSessions(workspaceId) {
|
|
47
|
+
return this.options.sessions.cached(workspaceId);
|
|
48
|
+
}
|
|
49
|
+
invalidate(workspaceId) {
|
|
50
|
+
this.options.changes.invalidate(workspaceId);
|
|
51
|
+
for (const topic of ['files', 'changes']) {
|
|
52
|
+
if (!this.options.watches.topicActive(workspaceId, topic))
|
|
53
|
+
continue;
|
|
54
|
+
this.options.emit(workspaceId, topic);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
invalidateChanges(workspaceId) {
|
|
58
|
+
this.options.changes.invalidate(workspaceId);
|
|
59
|
+
}
|
|
60
|
+
persistTerminal(summary) {
|
|
61
|
+
const database = this.options.database();
|
|
62
|
+
if (database === undefined)
|
|
63
|
+
return;
|
|
64
|
+
const value = {
|
|
65
|
+
id: summary.id,
|
|
66
|
+
workspaceId: summary.workspaceId,
|
|
67
|
+
profileId: summary.profileId,
|
|
68
|
+
state: summary.state,
|
|
69
|
+
exitCode: summary.exitCode,
|
|
70
|
+
rows: summary.rows,
|
|
71
|
+
cols: summary.cols,
|
|
72
|
+
latestSequence: summary.latestSequence,
|
|
73
|
+
createdAt: summary.createdAt,
|
|
74
|
+
updatedAt: summary.updatedAt
|
|
75
|
+
};
|
|
76
|
+
database.saveTerminalSession(value);
|
|
77
|
+
}
|
|
78
|
+
async inspect(envelope) {
|
|
79
|
+
const requestedPath = typeof envelope.payload === 'object' &&
|
|
80
|
+
envelope.payload !== null &&
|
|
81
|
+
'path' in envelope.payload
|
|
82
|
+
? envelope.payload.path
|
|
83
|
+
: undefined;
|
|
84
|
+
const config = this.options.config();
|
|
85
|
+
if (typeof requestedPath !== 'string' || config === undefined) {
|
|
86
|
+
this.options.send('workspace.inspect.result', { error: 'WORKSPACE_INVALID' }, envelope.id);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
const inspection = await inspectWorkspace(requestedPath, config.allowedRoots);
|
|
91
|
+
this.options.send('workspace.inspect.result', inspection, envelope.id);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
this.options.send('workspace.inspect.result', { error: 'WORKSPACE_INVALID' }, envelope.id);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -1,2 +1,99 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
const TERMINAL_TTL_MS = 12 * 60 * 60_000;
|
|
2
|
+
const CLEANUP_INTERVAL_MS = 5 * 60_000;
|
|
3
|
+
const TERMINAL_LIMIT = 100;
|
|
4
|
+
export class RecentRunProjectionService {
|
|
5
|
+
emit;
|
|
6
|
+
now;
|
|
7
|
+
runsBySession = new Map();
|
|
8
|
+
cleanupTimer;
|
|
9
|
+
constructor(emit, now = Date.now) {
|
|
10
|
+
this.emit = emit;
|
|
11
|
+
this.now = now;
|
|
12
|
+
this.cleanupTimer = setInterval(() => this.cleanup(), CLEANUP_INTERVAL_MS);
|
|
13
|
+
this.cleanupTimer.unref();
|
|
14
|
+
}
|
|
15
|
+
record(run, session, userMessage) {
|
|
16
|
+
if (!Number.isSafeInteger(run.initiatedByUserId) || run.initiatedByUserId === undefined)
|
|
17
|
+
return;
|
|
18
|
+
if (session.externalSessionId === null)
|
|
19
|
+
return;
|
|
20
|
+
const key = taskKey(run.initiatedByUserId, run.sessionId);
|
|
21
|
+
const existing = this.runsBySession.get(key);
|
|
22
|
+
const sameRun = existing?.runId === run.id;
|
|
23
|
+
if (sameRun && existing.version > run.version)
|
|
24
|
+
return;
|
|
25
|
+
if (!sameRun && existing !== undefined && newerThan(existing, run))
|
|
26
|
+
return;
|
|
27
|
+
const startedAt = (sameRun ? existing.startedAt : null) ?? run.startedAt;
|
|
28
|
+
const completedAt = terminal(run.status)
|
|
29
|
+
? ((sameRun ? existing.completedAt : null) ?? run.completedAt ?? run.updatedAt)
|
|
30
|
+
: null;
|
|
31
|
+
const latestUserMessage = messagePreview(userMessage) ?? (sameRun ? existing.userMessage : undefined);
|
|
32
|
+
const projection = {
|
|
33
|
+
runId: run.id,
|
|
34
|
+
nodeId: run.nodeId,
|
|
35
|
+
workspaceId: run.workspaceId,
|
|
36
|
+
sessionId: run.sessionId,
|
|
37
|
+
sessionTitle: session.customTitle ?? session.runnerTitle ?? session.id,
|
|
38
|
+
...(latestUserMessage === undefined ? {} : { userMessage: latestUserMessage }),
|
|
39
|
+
runner: run.runner,
|
|
40
|
+
status: run.status,
|
|
41
|
+
createdAt: run.createdAt,
|
|
42
|
+
startedAt,
|
|
43
|
+
completedAt,
|
|
44
|
+
sortAt: startedAt ?? run.createdAt,
|
|
45
|
+
updatedAt: run.updatedAt,
|
|
46
|
+
version: run.version,
|
|
47
|
+
userId: run.initiatedByUserId
|
|
48
|
+
};
|
|
49
|
+
this.runsBySession.set(key, projection);
|
|
50
|
+
this.cleanup();
|
|
51
|
+
this.emit({ kind: 'UPDATE', run: projection });
|
|
52
|
+
}
|
|
53
|
+
snapshot() {
|
|
54
|
+
this.cleanup();
|
|
55
|
+
return [...this.runsBySession.values()].sort(compareRecentRuns);
|
|
56
|
+
}
|
|
57
|
+
publishSnapshot() {
|
|
58
|
+
this.emit({ kind: 'SNAPSHOT', runs: [...this.snapshot()] });
|
|
59
|
+
}
|
|
60
|
+
dispose() {
|
|
61
|
+
clearInterval(this.cleanupTimer);
|
|
62
|
+
}
|
|
63
|
+
cleanup() {
|
|
64
|
+
const cutoff = this.now() - TERMINAL_TTL_MS;
|
|
65
|
+
for (const [key, run] of this.runsBySession)
|
|
66
|
+
if (run.completedAt !== null && run.completedAt <= cutoff)
|
|
67
|
+
this.runsBySession.delete(key);
|
|
68
|
+
const byUser = new Map();
|
|
69
|
+
for (const run of this.runsBySession.values()) {
|
|
70
|
+
if (run.completedAt === null)
|
|
71
|
+
continue;
|
|
72
|
+
const values = byUser.get(run.userId) ?? [];
|
|
73
|
+
values.push(run);
|
|
74
|
+
byUser.set(run.userId, values);
|
|
75
|
+
}
|
|
76
|
+
for (const values of byUser.values())
|
|
77
|
+
for (const run of values.sort(compareRecentRuns).slice(TERMINAL_LIMIT))
|
|
78
|
+
this.runsBySession.delete(taskKey(run.userId, run.sessionId));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function compareRecentRuns(left, right) {
|
|
82
|
+
return (right.sortAt - left.sortAt ||
|
|
83
|
+
right.createdAt - left.createdAt ||
|
|
84
|
+
right.runId.localeCompare(left.runId));
|
|
85
|
+
}
|
|
86
|
+
function newerThan(existing, run) {
|
|
87
|
+
return (existing.createdAt > run.createdAt ||
|
|
88
|
+
(existing.createdAt === run.createdAt && existing.runId.localeCompare(run.id) > 0));
|
|
89
|
+
}
|
|
90
|
+
function taskKey(userId, sessionId) {
|
|
91
|
+
return `${userId}\0${sessionId}`;
|
|
92
|
+
}
|
|
93
|
+
function messagePreview(value) {
|
|
94
|
+
const normalized = value?.trim().replace(/\s+/gu, ' ');
|
|
95
|
+
return normalized === undefined || normalized.length === 0 ? undefined : normalized.slice(0, 500);
|
|
96
|
+
}
|
|
97
|
+
function terminal(status) {
|
|
98
|
+
return ['SUCCEEDED', 'FAILED', 'CANCELLED', 'INTERRUPTED'].includes(status);
|
|
99
|
+
}
|