@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,154 @@
|
|
|
1
|
-
|
|
2
|
-
const
|
|
1
|
+
const WATCH_INTERVAL_MS = 30_000;
|
|
2
|
+
const CACHE_TTL_MS = 15_000;
|
|
3
|
+
const INITIAL_WAIT_MS = 500;
|
|
4
|
+
export class WorkspaceSessionService {
|
|
5
|
+
state;
|
|
6
|
+
options;
|
|
7
|
+
cacheGenerations = new Map();
|
|
8
|
+
constructor(state, options) {
|
|
9
|
+
this.state = state;
|
|
10
|
+
this.options = options;
|
|
11
|
+
}
|
|
12
|
+
ensureWatch(workspaceId) {
|
|
13
|
+
if (this.state.sessionWatchTimers.has(workspaceId) ||
|
|
14
|
+
this.state.sessionWatchReads.has(workspaceId))
|
|
15
|
+
return;
|
|
16
|
+
void this.runWatchCycle(workspaceId);
|
|
17
|
+
}
|
|
18
|
+
stopWatch(workspaceId) {
|
|
19
|
+
const timer = this.state.sessionWatchTimers.get(workspaceId);
|
|
20
|
+
if (timer !== undefined)
|
|
21
|
+
clearTimeout(timer);
|
|
22
|
+
this.state.sessionWatchTimers.delete(workspaceId);
|
|
23
|
+
this.state.sessionWatchSignatures.delete(workspaceId);
|
|
24
|
+
}
|
|
25
|
+
async cached(workspaceId) {
|
|
26
|
+
const cached = this.state.sessionCache.get(workspaceId);
|
|
27
|
+
if (cached !== undefined && cached.expiresAt > Date.now())
|
|
28
|
+
return cached.sessions;
|
|
29
|
+
let initialDiscoveryTimedOut = false;
|
|
30
|
+
let discovery = this.state.sessionCacheReads.get(workspaceId);
|
|
31
|
+
if (discovery === undefined) {
|
|
32
|
+
const generation = this.cacheGenerations.get(workspaceId) ?? 0;
|
|
33
|
+
const read = this.options
|
|
34
|
+
.discover(workspaceId)
|
|
35
|
+
.then((sessions) => {
|
|
36
|
+
if ((this.cacheGenerations.get(workspaceId) ?? 0) !== generation)
|
|
37
|
+
return sessions;
|
|
38
|
+
const previous = this.state.sessionCache.get(workspaceId)?.sessions;
|
|
39
|
+
this.state.sessionCache.set(workspaceId, {
|
|
40
|
+
sessions,
|
|
41
|
+
expiresAt: Date.now() + CACHE_TTL_MS
|
|
42
|
+
});
|
|
43
|
+
if (initialDiscoveryTimedOut ||
|
|
44
|
+
(previous !== undefined && sessionSignature(previous) !== sessionSignature(sessions)))
|
|
45
|
+
this.options.invalidate(workspaceId);
|
|
46
|
+
return sessions;
|
|
47
|
+
})
|
|
48
|
+
.finally(() => {
|
|
49
|
+
if (this.state.sessionCacheReads.get(workspaceId) === read)
|
|
50
|
+
this.state.sessionCacheReads.delete(workspaceId);
|
|
51
|
+
if (!this.state.sessionCacheReads.has(workspaceId) &&
|
|
52
|
+
!this.state.sessionCache.has(workspaceId) &&
|
|
53
|
+
(this.cacheGenerations.get(workspaceId) ?? 0) !== generation)
|
|
54
|
+
this.cacheGenerations.delete(workspaceId);
|
|
55
|
+
});
|
|
56
|
+
discovery = read;
|
|
57
|
+
this.state.sessionCacheReads.set(workspaceId, discovery);
|
|
58
|
+
}
|
|
59
|
+
if (cached !== undefined)
|
|
60
|
+
return cached.sessions;
|
|
61
|
+
const discovered = await within(discovery, INITIAL_WAIT_MS);
|
|
62
|
+
if (discovered !== undefined)
|
|
63
|
+
return discovered;
|
|
64
|
+
initialDiscoveryTimedOut = true;
|
|
65
|
+
this.options.onPending?.(workspaceId, INITIAL_WAIT_MS);
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
clear() {
|
|
69
|
+
for (const timer of this.state.sessionWatchTimers.values())
|
|
70
|
+
clearTimeout(timer);
|
|
71
|
+
this.state.sessionWatchTimers.clear();
|
|
72
|
+
this.state.sessionWatchSignatures.clear();
|
|
73
|
+
this.state.sessionWatchReads.clear();
|
|
74
|
+
for (const workspaceId of new Set([
|
|
75
|
+
...this.state.sessionCache.keys(),
|
|
76
|
+
...this.state.sessionCacheReads.keys()
|
|
77
|
+
]))
|
|
78
|
+
this.bumpCacheGeneration(workspaceId);
|
|
79
|
+
this.state.sessionCache.clear();
|
|
80
|
+
this.state.sessionCacheReads.clear();
|
|
81
|
+
}
|
|
82
|
+
invalidate(workspaceId) {
|
|
83
|
+
this.invalidateCache(workspaceId);
|
|
84
|
+
this.stopWatch(workspaceId);
|
|
85
|
+
}
|
|
86
|
+
invalidateCache(workspaceId) {
|
|
87
|
+
this.bumpCacheGeneration(workspaceId);
|
|
88
|
+
this.state.sessionCache.delete(workspaceId);
|
|
89
|
+
this.state.sessionCacheReads.delete(workspaceId);
|
|
90
|
+
}
|
|
91
|
+
bumpCacheGeneration(workspaceId) {
|
|
92
|
+
this.cacheGenerations.set(workspaceId, (this.cacheGenerations.get(workspaceId) ?? 0) + 1);
|
|
93
|
+
}
|
|
94
|
+
async refreshWatch(workspaceId) {
|
|
95
|
+
const current = this.state.sessionWatchReads.get(workspaceId);
|
|
96
|
+
if (current !== undefined)
|
|
97
|
+
return current;
|
|
98
|
+
const refresh = this.performRefresh(workspaceId).finally(() => this.state.sessionWatchReads.delete(workspaceId));
|
|
99
|
+
this.state.sessionWatchReads.set(workspaceId, refresh);
|
|
100
|
+
return refresh;
|
|
101
|
+
}
|
|
102
|
+
async runWatchCycle(workspaceId) {
|
|
103
|
+
await this.refreshWatch(workspaceId);
|
|
104
|
+
if (!this.options.active(workspaceId) || this.state.sessionWatchTimers.has(workspaceId))
|
|
105
|
+
return;
|
|
106
|
+
const timer = setTimeout(() => {
|
|
107
|
+
this.state.sessionWatchTimers.delete(workspaceId);
|
|
108
|
+
void this.runWatchCycle(workspaceId);
|
|
109
|
+
}, WATCH_INTERVAL_MS);
|
|
110
|
+
timer.unref();
|
|
111
|
+
this.state.sessionWatchTimers.set(workspaceId, timer);
|
|
112
|
+
}
|
|
113
|
+
async performRefresh(workspaceId) {
|
|
114
|
+
if (!this.options.active(workspaceId))
|
|
115
|
+
return this.stopWatch(workspaceId);
|
|
116
|
+
try {
|
|
117
|
+
const signature = JSON.stringify(await this.options.listPage(workspaceId));
|
|
118
|
+
if (!this.options.active(workspaceId))
|
|
119
|
+
return this.stopWatch(workspaceId);
|
|
120
|
+
const previous = this.state.sessionWatchSignatures.get(workspaceId);
|
|
121
|
+
this.state.sessionWatchSignatures.set(workspaceId, signature);
|
|
122
|
+
if (previous === undefined || previous === signature)
|
|
123
|
+
return;
|
|
124
|
+
this.options.invalidate(workspaceId);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// The bounded interval retries without fabricating session state.
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function sessionSignature(sessions) {
|
|
132
|
+
return JSON.stringify(sessions.map((session) => [
|
|
133
|
+
session.id,
|
|
134
|
+
session.runner,
|
|
135
|
+
session.externalSessionId,
|
|
136
|
+
session.nativeControl,
|
|
137
|
+
session.model,
|
|
138
|
+
session.effort,
|
|
139
|
+
session.access,
|
|
140
|
+
session.runnerTitle,
|
|
141
|
+
session.customTitle,
|
|
142
|
+
session.titleSource,
|
|
143
|
+
session.lastActivityAt,
|
|
144
|
+
session.pinnedAt,
|
|
145
|
+
session.pinOrder,
|
|
146
|
+
session.externalDiscovered
|
|
147
|
+
]));
|
|
148
|
+
}
|
|
149
|
+
async function within(promise, timeoutMs) {
|
|
150
|
+
return Promise.race([
|
|
151
|
+
promise,
|
|
152
|
+
new Promise((resolve) => setTimeout(resolve, timeoutMs))
|
|
153
|
+
]);
|
|
154
|
+
}
|
|
@@ -1,2 +1,262 @@
|
|
|
1
|
-
|
|
2
|
-
function _0x40fa(_0x2ba187,_0x1d35b4){_0x2ba187=_0x2ba187-(-0x21c2+-0x13*0x207+-0xd*-0x5a7);const _0x388e65=_0x754a();let _0x2d9540=_0x388e65[_0x2ba187];if(_0x40fa['okKUwK']===undefined){var _0x23788c=function(_0x298ecc){const _0x1daf01='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x291dcf='',_0x312348='';for(let _0x1160fe=0x113*-0xd+-0x3*0xb3c+0x2fab,_0x3f8104,_0x1aabc7,_0x2dba1a=0x576*0x2+-0x384+0x2*-0x3b4;_0x1aabc7=_0x298ecc['charAt'](_0x2dba1a++);~_0x1aabc7&&(_0x3f8104=_0x1160fe%(0xa77+0x2691+-0x3104)?_0x3f8104*(0x1a*-0x7f+-0x1*-0x2273+-0x154d)+_0x1aabc7:_0x1aabc7,_0x1160fe++%(0x164d+0x223f*0x1+-0x1*0x3888))?_0x291dcf+=String['fromCharCode'](-0x2d*-0xb3+0xcff*0x3+-0x1*0x4575&_0x3f8104>>(-(0x1fe0+-0x1*-0x1f2f+-0x3f0d*0x1)*_0x1160fe&-0x967*0x3+0x35*-0x7f+0x3686)):-0x1f*-0xbf+0x69d*0x1+-0x1dbe){_0x1aabc7=_0x1daf01['indexOf'](_0x1aabc7);}for(let _0x3d7f25=0xbd*0x1b+-0x1cc4+0x8d5,_0x4ab862=_0x291dcf['length'];_0x3d7f25<_0x4ab862;_0x3d7f25++){_0x312348+='%'+('00'+_0x291dcf['charCodeAt'](_0x3d7f25)['toString'](0x214d+-0xaae*0x1+-0x168f))['slice'](-(-0x6*-0x576+0x38*-0x1d+-0x1a6a));}return decodeURIComponent(_0x312348);};_0x40fa['NGOlTw']=_0x23788c,_0x40fa['obqvYD']={},_0x40fa['okKUwK']=!![];}const _0x4c6cfb=_0x388e65[0x129*-0x1f+-0x1020+-0x771*-0x7];_0x40fa['UxIaUH']!==_0x4c6cfb&&(_0x40fa['obqvYD']={},_0x40fa['UxIaUH']=_0x4c6cfb);const _0x4bd8b3=_0x40fa['obqvYD'][_0x2ba187];return _0x4bd8b3===undefined?(_0x2d9540=_0x40fa['NGOlTw'](_0x2d9540),_0x40fa['obqvYD'][_0x2ba187]=_0x2d9540):_0x2d9540=_0x4bd8b3,_0x2d9540;}const _0x49dafc=_0x40fa;(function(_0x3ba695,_0x139f23){const _0x4d84b8=_0x40fa,_0x4991a0=_0x3ba695();while(!![]){try{const _0x2e365f=parseInt(_0x4d84b8(0x180))/(-0x895*0x3+0x34a*-0xa+0x3aa4)+parseInt(_0x4d84b8(0x14a))/(-0xc81*-0x1+-0x1*0x950+0xa3*-0x5)*(-parseInt(_0x4d84b8(0x175))/(-0x61a+-0xe6b*0x1+-0x6*-0x36c))+-parseInt(_0x4d84b8(0x167))/(-0x1*-0x173f+0x67*0x21+0x1*-0x2482)*(parseInt(_0x4d84b8(0x13e))/(0x1*0x25e1+-0x1159*-0x1+-0x3735))+parseInt(_0x4d84b8(0x15a))/(-0x26b8+-0xb*0x2a1+0x43a9)*(parseInt(_0x4d84b8(0x168))/(0x4a*-0xe+0xd26+-0x913))+parseInt(_0x4d84b8(0x16c))/(-0x6*0x6d+0x24af+-0x2219)+parseInt(_0x4d84b8(0x14d))/(-0x1*0x1097+-0x159a+0x263a)*(parseInt(_0x4d84b8(0x13f))/(-0xe94+0x470*0x2+-0x2a*-0x23))+parseInt(_0x4d84b8(0x147))/(0x1*-0x1837+0x9*0x2a7+-0x63*-0x1)*(-parseInt(_0x4d84b8(0x190))/(0x2ba*-0xd+0x1*0x31e+0x2060));if(_0x2e365f===_0x139f23)break;else _0x4991a0['push'](_0x4991a0['shift']());}catch(_0x1900c4){_0x4991a0['push'](_0x4991a0['shift']());}}}(_0x754a,0x3d8a1+-0x1e5b7*-0x2+-0x4face));function _0x754a(){const _0x3a36e1=['DgLTzxi','Dg9tDhjPBMC','yMfZzty0','AxntywzLsw50zwDLCG','DxbSB2fKswq','yM9VBgvHBG','mZzIuwfSAxa','otmZodb5DKP0C2u','ywjZB2X1DgvqyxrO','y2f0y2G','vvbmt0fex1rbuKDfvf9fweLtvfm','mJuZmdq4meL0t0TeBa','yxr0ywnOBwvUDfrHCMDLDa','C3rYAw5N','BMfTzq','ChjLzMXPz2H0','CMvTB3zLq29TCgXLDgvK','CMvTB3zL','AgfZ','BgvUz3rO','ndq3BvjgthDy','C2L6zq','y29UC3vTzq','uKvbrfK','y29TCg9ZzxjbDhrHy2HTzw50','y29TCg9ZzxjbDhrHy2HTzw50CW','vvbmt0fex0nivu5lx0LovKfmsuq','lM1HCI90zw1WlwzPBgvZlW','DgvTCc1MAwXLCW','DMfSDwvZ','Cgf0Aa','ndi0m3rAvuXlBG','C2H1DgrVD24','BwfW','zgf0yujHC2u2na','vvbmt0fex05btuvFsu5wquXjra','C3rHDgu','C2v0','vvbmt0fex09grLnfvf9jtLzbteLe','CMvQzwn0','vvbmt0fex0LovKfmsuq','y29TCgXLDgu','AxnbCNjHEq','AxneAxjLy3rVCNK','Dw5Yzwy','ywrK','vvbmt0fex1rbuKDfvf9esvjfq1rpuLK','nZa2oeTvENrSCW','B3zLCNDYAxrL','q09nue9trvjFqvruqunituvovf9esvjfq1rpuLLFsu5wquXjra','su5wquXjrf9oqu1f','vvbmt0fex1njwKvFsu5wquXjra','ywXSu2v0DgXLza','y2XLyxi','y3jLyxrL','ru5pru5u','CMvZB2X2zq','B2jQzwn0','zgLYzwn0B3j5','CMvJzwL2zwrcExrLCW','vvbmt0fex0Loq09nueXfveu','D29YA3nWywnLswq','BwLTzq','y2fUy2vS','AxntEw1IB2XPy0XPBMS','C29Tzq','otK3otvLr3vHq0u','ntbfvhvTDfK','vvbmt0fex1Dssvrfx0zbsuXfra','B25dB21WBgv0zwq','q09nue9trvjFqvruqunituvovf9vtKfwquLmqujmrq','q09nue9trvjFqvruqunituvovf9jtLzbteLe','B2zMC2v0','D3jPDgu','AxngAwXL','mZqYmwz4ANLVsq','ruvysvnu','vvbmt0fex1rbuKDfvf9cvvnz','ndy2quXtDw52','vvbmt0fex05pvf9gt1vora','ywXS','mJG0mZKXCLPzy3jH','lM1HCG','yNL0zxnxCML0DgvU','DgfYz2v0ugf0Aa','rvHju1rtx0zjteu','C3rHDhvZ','y2XVC2u','rKLmrv9qqvrix0vtq0fqrq','AgfUzgXL','zNjVBq','C3LUyW','DgvTCg9Yyxj5ugf0Aa','DxbSB2fKCW','ndjMEvfoBvC','zMLSzxm','rvHju1rtx0rjuKvdve9swq','zgvSzxrL','z2v0','y29Kzq','CMvXDwLYzq'];_0x754a=function(){return _0x3a36e1;};return _0x754a();}import{randomUUID}from'node:crypto';import{lstat,mkdir,open,readFile,rename,rm}from'node:fs/promises';import{dirname,join,resolve}from'node:path';import{parseComposerAttachmentUpload,validComposerAttachmentName}from'../util/node-operation-parsers.js';import{preflightWorkspaceUpload,workspaceUploadTemporaryName}from'../workspace.js';const CHUNK_BYTES=(0x1ccb+0x19e3+-0x1*0x34ae)*(0x1991+0x1*0x2204+0x3795*-0x1),TTL_MS=(-0x7dc+-0x127b+0x1*0x1a93)*(0xdf41+0x15291+0xa3b9*-0x2),MAX_ATTACHMENT_BYTES=(0x1de*-0x14+0x1a77+0xae6*0x1)*(0x39d*0x5+-0x2*0x8af+0xa9*0x5)*(-0x163a+-0xfce*-0x1+0xa6c);export class WorkspaceUploadService{[_0x49dafc(0x185)];[_0x49dafc(0x141)];constructor(_0x3cb3d4,_0x1c0215){const _0x2d96d2=_0x49dafc;this[_0x2d96d2(0x185)]=_0x3cb3d4,this[_0x2d96d2(0x141)]=_0x1c0215;}async[_0x49dafc(0x170)](_0x4e9bb1,_0x4f6e55){const _0x48bacd=_0x49dafc;if(_0x4f6e55[_0x48bacd(0x136)]!==undefined&&typeof _0x4f6e55[_0x48bacd(0x136)]!==_0x48bacd(0x16e)||!Array[_0x48bacd(0x18b)](_0x4f6e55[_0x48bacd(0x15b)])||_0x4f6e55[_0x48bacd(0x15b)][_0x48bacd(0x174)]===-0x1a4b+0x39*0x45+0xaee||_0x4f6e55[_0x48bacd(0x15b)][_0x48bacd(0x13d)](_0x5b15bd=>!plain(_0x5b15bd)||typeof _0x5b15bd[_0x48bacd(0x16f)]!==_0x48bacd(0x16e)))throw new Error(_0x48bacd(0x189));const _0x152372=new Set();return Promise[_0x48bacd(0x14c)](_0x4f6e55[_0x48bacd(0x15b)][_0x48bacd(0x182)](async _0xa79c9e=>{const _0x921422=_0x48bacd,_0x2ba187=_0xa79c9e[_0x921422(0x16f)];if(_0x152372[_0x921422(0x173)](_0x2ba187))return{'name':_0x2ba187,'status':_0x921422(0x193),'path':null};_0x152372[_0x921422(0x18e)](_0x2ba187);const _0x1d35b4=await preflightWorkspaceUpload(_0x4e9bb1[_0x921422(0x17f)],typeof _0x4f6e55[_0x921422(0x136)]===_0x921422(0x16e)?_0x4f6e55[_0x921422(0x136)]:'.',_0x2ba187);return{'name':_0x2ba187,'status':_0x1d35b4[_0x921422(0x152)],'path':_0x1d35b4[_0x921422(0x17f)]||null};}));}async[_0x49dafc(0x197)](_0x388e65,_0x2d9540){const _0x49c4df=_0x49dafc,_0x23788c=parseComposerAttachmentUpload(_0x2d9540);if(_0x2d9540[_0x49c4df(0x136)]!==undefined&&typeof _0x2d9540[_0x49c4df(0x136)]!==_0x49c4df(0x16e)||typeof _0x2d9540[_0x49c4df(0x16f)]!==_0x49c4df(0x16e)||!Number[_0x49c4df(0x164)](_0x2d9540[_0x49c4df(0x176)])||_0x2d9540[_0x49c4df(0x176)]<-0x1f*-0xbf+0x69d*0x1+-0x1dbe||typeof _0x2d9540[_0x49c4df(0x191)]!==_0x49c4df(0x166)||_0x23788c!==undefined&&_0x2d9540[_0x49c4df(0x136)]!==undefined)throw new Error(_0x49c4df(0x189));if(_0x23788c!==undefined&&(!validComposerAttachmentName(_0x2d9540[_0x49c4df(0x16f)])||_0x2d9540[_0x49c4df(0x176)]>MAX_ATTACHMENT_BYTES||this[_0x49c4df(0x185)][_0x49c4df(0x17a)][_0x49c4df(0x173)](_0x23788c['id'])||[...this[_0x49c4df(0x185)][_0x49c4df(0x159)][_0x49c4df(0x17e)]()][_0x49c4df(0x13d)](_0x1160fe=>_0x1160fe[_0x49c4df(0x179)]?.['id']===_0x23788c['id'])))throw new Error(_0x49c4df(0x143));const _0x4c6cfb=_0x23788c===undefined?await preflightWorkspaceUpload(_0x388e65[_0x49c4df(0x17f)],typeof _0x2d9540[_0x49c4df(0x136)]===_0x49c4df(0x16e)?_0x2d9540[_0x49c4df(0x136)]:'.',_0x2d9540[_0x49c4df(0x16f)]):await this[_0x49c4df(0x16d)](_0x388e65[_0x49c4df(0x17f)],_0x23788c['id'],_0x2d9540[_0x49c4df(0x16f)]);if(_0x4c6cfb[_0x49c4df(0x152)]===_0x49c4df(0x193))throw new Error(_0x49c4df(0x184));if(_0x4c6cfb[_0x49c4df(0x152)]===_0x49c4df(0x15c))throw new Error(_0x49c4df(0x18f));if(_0x4c6cfb[_0x49c4df(0x152)]===_0x49c4df(0x151)&&!_0x2d9540[_0x49c4df(0x191)])throw new Error(_0x49c4df(0x16b));if([...this[_0x49c4df(0x185)][_0x49c4df(0x159)][_0x49c4df(0x17e)]()][_0x49c4df(0x13d)](_0x3f8104=>_0x3f8104[_0x49c4df(0x139)]===_0x388e65['id']&&_0x3f8104[_0x49c4df(0x150)]===_0x4c6cfb[_0x49c4df(0x169)]))throw new Error(_0x49c4df(0x149));const _0x4bd8b3=randomUUID(),_0x298ecc=resolve(dirname(_0x4c6cfb[_0x49c4df(0x169)]),workspaceUploadTemporaryName(_0x4bd8b3)),_0x1daf01=await open(_0x298ecc,'wx'),_0x291dcf=setTimeout(()=>{const _0x153da6=_0x49c4df,_0x1aabc7=this[_0x153da6(0x185)][_0x153da6(0x159)][_0x153da6(0x15e)](_0x4bd8b3);if(_0x1aabc7!==undefined)void this[_0x153da6(0x172)](_0x1aabc7);},TTL_MS),_0x312348={'id':_0x4bd8b3,'workspaceId':_0x388e65['id'],'path':_0x4c6cfb[_0x49c4df(0x17f)],'targetPath':_0x4c6cfb[_0x49c4df(0x169)],'temporaryPath':_0x298ecc,'size':_0x2d9540[_0x49c4df(0x176)],'overwrite':_0x2d9540[_0x49c4df(0x191)],..._0x23788c===undefined?{}:{'composerAttachment':_0x23788c},'handle':_0x1daf01,'receivedBytes':0x0,'timer':_0x291dcf};return this[_0x49c4df(0x185)][_0x49c4df(0x159)][_0x49c4df(0x186)](_0x4bd8b3,_0x312348),this[_0x49c4df(0x152)](_0x4bd8b3);}async[_0x49dafc(0x145)](_0x2dba1a){const _0x2dbb17=_0x49dafc;if(typeof _0x2dba1a[_0x2dbb17(0x165)]!==_0x2dbb17(0x16e)||!Number[_0x2dbb17(0x164)](_0x2dba1a[_0x2dbb17(0x144)])||_0x2dba1a[_0x2dbb17(0x144)]<0xbd*0x1b+-0x1cc4+0x8d5||typeof _0x2dba1a[_0x2dbb17(0x183)]!==_0x2dbb17(0x16e))throw new Error(_0x2dbb17(0x17b));const _0x3d7f25=this[_0x2dbb17(0x160)](_0x2dba1a[_0x2dbb17(0x165)]);if(_0x2dba1a[_0x2dbb17(0x144)]!==_0x3d7f25[_0x2dbb17(0x137)])throw new Error(_0x2dbb17(0x187));const _0x4ab862=Buffer[_0x2dbb17(0x156)](_0x2dba1a[_0x2dbb17(0x183)],_0x2dbb17(0x163));if(_0x4ab862[_0x2dbb17(0x174)]===0x214d+-0xaae*0x1+-0x169f||_0x4ab862[_0x2dbb17(0x174)]>CHUNK_BYTES)throw new Error(_0x2dbb17(0x17b));if(_0x3d7f25[_0x2dbb17(0x137)]+_0x4ab862[_0x2dbb17(0x174)]>_0x3d7f25[_0x2dbb17(0x176)])throw new Error(_0x2dbb17(0x194));const _0xc7638f=await _0x3d7f25[_0x2dbb17(0x155)][_0x2dbb17(0x145)](_0x4ab862,-0x6*-0x576+0x38*-0x1d+-0x1a6c,_0x4ab862[_0x2dbb17(0x174)],_0x3d7f25[_0x2dbb17(0x137)]);if(_0xc7638f[_0x2dbb17(0x14f)]!==_0x4ab862[_0x2dbb17(0x174)])throw new Error(_0x2dbb17(0x140));return _0x3d7f25[_0x2dbb17(0x137)]+=_0x4ab862[_0x2dbb17(0x174)],this[_0x2dbb17(0x152)](_0x3d7f25['id']);}[_0x49dafc(0x152)](_0x138f98){const _0x340bc2=_0x49dafc,_0x1ea2d1=this[_0x340bc2(0x160)](_0x138f98);return{'uploadId':_0x1ea2d1['id'],'path':_0x1ea2d1[_0x340bc2(0x17f)],'receivedBytes':_0x1ea2d1[_0x340bc2(0x137)],'size':_0x1ea2d1[_0x340bc2(0x176)]};}async[_0x49dafc(0x18a)](_0x1313db){const _0x1dde42=_0x49dafc,_0xbf21b3=this[_0x1dde42(0x160)](_0x1313db);if(_0xbf21b3[_0x1dde42(0x137)]!==_0xbf21b3[_0x1dde42(0x176)])throw new Error(_0x1dde42(0x138));await _0xbf21b3[_0x1dde42(0x155)][_0x1dde42(0x157)](),await _0xbf21b3[_0x1dde42(0x155)][_0x1dde42(0x153)]();const _0x4d8e85=await lstat(_0xbf21b3[_0x1dde42(0x150)])[_0x1dde42(0x16a)](_0x3e5d05=>_0x3e5d05[_0x1dde42(0x15f)]===_0x1dde42(0x198)?undefined:Promise[_0x1dde42(0x188)](_0x3e5d05));if(_0x4d8e85?.[_0x1dde42(0x13c)]())throw new Error(_0x1dde42(0x154));if(_0x4d8e85!==undefined&&(!_0x4d8e85[_0x1dde42(0x146)]()||!_0xbf21b3[_0x1dde42(0x191)]))throw new Error(_0x1dde42(0x16b));try{await rename(_0xbf21b3[_0x1dde42(0x158)],_0xbf21b3[_0x1dde42(0x150)]);}catch(_0x2c9c77){if(_0x2c9c77[_0x1dde42(0x15f)]!==_0x1dde42(0x148))throw _0x2c9c77;if(!_0xbf21b3[_0x1dde42(0x191)])throw new Error(_0x1dde42(0x16b));await rm(_0xbf21b3[_0x1dde42(0x150)],{'force':![]}),await rename(_0xbf21b3[_0x1dde42(0x158)],_0xbf21b3[_0x1dde42(0x150)]);}clearTimeout(_0xbf21b3[_0x1dde42(0x161)]),this[_0x1dde42(0x185)][_0x1dde42(0x159)][_0x1dde42(0x15d)](_0xbf21b3['id']),this[_0x1dde42(0x141)](_0xbf21b3[_0x1dde42(0x139)]);if(_0xbf21b3[_0x1dde42(0x179)]!==undefined){const _0x164968=_0xbf21b3[_0x1dde42(0x179)]['id'],_0xa23bf1=setTimeout(()=>{const _0x56d082=_0x1dde42,_0x12000a=this[_0x56d082(0x185)][_0x56d082(0x17a)][_0x56d082(0x15e)](_0x164968);if(_0x12000a!==undefined)void this[_0x56d082(0x171)](_0x12000a);},TTL_MS);_0xa23bf1[_0x1dde42(0x18d)](),this[_0x1dde42(0x185)][_0x1dde42(0x17a)][_0x1dde42(0x186)](_0x164968,{'id':_0xbf21b3[_0x1dde42(0x179)]['id'],'workspaceId':_0xbf21b3[_0x1dde42(0x139)],'path':_0xbf21b3[_0x1dde42(0x17f)],'targetPath':_0xbf21b3[_0x1dde42(0x150)],'name':_0xbf21b3[_0x1dde42(0x179)][_0x1dde42(0x16f)],'mime':_0xbf21b3[_0x1dde42(0x179)][_0x1dde42(0x13a)],'size':_0xbf21b3[_0x1dde42(0x176)],'timer':_0xa23bf1});}return{'uploadId':_0xbf21b3['id'],'path':_0xbf21b3[_0x1dde42(0x17f)],'receivedBytes':_0xbf21b3[_0x1dde42(0x137)],'size':_0xbf21b3[_0x1dde42(0x176)]};}async[_0x49dafc(0x13b)](_0x19ba1e){const _0x2affb3=_0x49dafc;if(typeof _0x19ba1e!==_0x2affb3(0x16e))throw new Error(_0x2affb3(0x14b));const _0x3d98ee=this[_0x2affb3(0x185)][_0x2affb3(0x159)][_0x2affb3(0x15e)](_0x19ba1e);if(_0x3d98ee!==undefined)return this[_0x2affb3(0x172)](_0x3d98ee);const _0x15e8a4=this[_0x2affb3(0x185)][_0x2affb3(0x17a)][_0x2affb3(0x15e)](_0x19ba1e);if(_0x15e8a4===undefined)throw new Error(_0x2affb3(0x14b));await this[_0x2affb3(0x171)](_0x15e8a4);}async[_0x49dafc(0x134)](_0x5e0df1,_0x40b436){const _0x67f9e2=_0x49dafc;return Promise[_0x67f9e2(0x14c)](_0x40b436[_0x67f9e2(0x182)](async _0x4dfad3=>{const _0x1fe7e9=_0x67f9e2,_0x441d31=this[_0x1fe7e9(0x185)][_0x1fe7e9(0x17a)][_0x1fe7e9(0x15e)](_0x4dfad3);if(_0x441d31===undefined||_0x441d31[_0x1fe7e9(0x139)]!==_0x5e0df1)throw new Error(_0x1fe7e9(0x142));const _0x177faa=await lstat(_0x441d31[_0x1fe7e9(0x150)])[_0x1fe7e9(0x16a)](()=>undefined);if(_0x177faa===undefined||_0x177faa[_0x1fe7e9(0x13c)]()||!_0x177faa[_0x1fe7e9(0x146)]()||_0x177faa[_0x1fe7e9(0x176)]!==_0x441d31[_0x1fe7e9(0x176)])throw new Error(_0x1fe7e9(0x142));const _0x497ca7=await readFile(_0x441d31[_0x1fe7e9(0x150)]);return{'attachment':{'name':_0x441d31[_0x1fe7e9(0x16f)],'mime':_0x441d31[_0x1fe7e9(0x13a)],'dataBase64':_0x497ca7[_0x1fe7e9(0x162)](_0x1fe7e9(0x163))},'path':_0x441d31[_0x1fe7e9(0x17f)]};}));}async[_0x49dafc(0x177)](_0x2ed3ba){const _0x4b9055=_0x49dafc;await Promise[_0x4b9055(0x14c)](_0x2ed3ba[_0x4b9055(0x182)](async _0x184c8=>{const _0x154496=_0x4b9055,_0x164f35=this[_0x154496(0x185)][_0x154496(0x17a)][_0x154496(0x15e)](_0x184c8);if(_0x164f35!==undefined)await this[_0x154496(0x171)](_0x164f35);}));}async[_0x49dafc(0x181)](){const _0x1b37ed=_0x49dafc;await Promise[_0x1b37ed(0x195)]([...this[_0x1b37ed(0x185)][_0x1b37ed(0x159)][_0x1b37ed(0x17e)]()][_0x1b37ed(0x182)](_0x5acec4=>this[_0x1b37ed(0x172)](_0x5acec4)));for(const _0x1696e1 of this[_0x1b37ed(0x185)][_0x1b37ed(0x17a)][_0x1b37ed(0x17e)]())await this[_0x1b37ed(0x171)](_0x1696e1);this[_0x1b37ed(0x185)][_0x1b37ed(0x17a)][_0x1b37ed(0x196)]();}[_0x49dafc(0x160)](_0x3c3ba3){const _0x489ed8=_0x49dafc;if(typeof _0x3c3ba3!==_0x489ed8(0x16e))throw new Error(_0x489ed8(0x14b));const _0x18cf1b=this[_0x489ed8(0x185)][_0x489ed8(0x159)][_0x489ed8(0x15e)](_0x3c3ba3);if(_0x18cf1b===undefined)throw new Error(_0x489ed8(0x14b));return _0x18cf1b;}async[_0x49dafc(0x172)](_0x138264){const _0x505dba=_0x49dafc;this[_0x505dba(0x185)][_0x505dba(0x159)][_0x505dba(0x15d)](_0x138264['id']),clearTimeout(_0x138264[_0x505dba(0x161)]),await _0x138264[_0x505dba(0x155)][_0x505dba(0x153)]()[_0x505dba(0x16a)](()=>undefined),await rm(_0x138264[_0x505dba(0x158)],{'force':!![]})[_0x505dba(0x16a)](()=>undefined);if(_0x138264[_0x505dba(0x179)]!==undefined)await rm(dirname(_0x138264[_0x505dba(0x150)]),{'recursive':!![],'force':!![]})[_0x505dba(0x16a)](()=>undefined);}async[_0x49dafc(0x171)](_0x2981f9){const _0x13e91b=_0x49dafc;if(this[_0x13e91b(0x185)][_0x13e91b(0x17a)][_0x13e91b(0x15e)](_0x2981f9['id'])!==_0x2981f9)return;this[_0x13e91b(0x185)][_0x13e91b(0x17a)][_0x13e91b(0x15d)](_0x2981f9['id']),clearTimeout(_0x2981f9[_0x13e91b(0x161)]),await rm(dirname(_0x2981f9[_0x13e91b(0x150)]),{'recursive':!![],'force':!![]})[_0x13e91b(0x16a)](()=>undefined);}async[_0x49dafc(0x16d)](_0x557d3b,_0x3b64f7,_0x2fbce2){const _0x33754d=_0x49dafc,_0x20bce8=resolve(_0x557d3b,_0x33754d(0x14e));await mkdir(_0x20bce8,{'recursive':!![],'mode':0x1c0}),await safeDirectory(_0x20bce8);const _0x4e6f01=join(_0x20bce8,_0x33754d(0x17d));await mkdir(_0x4e6f01,{'recursive':!![],'mode':0x1c0}),await safeDirectory(_0x4e6f01);const _0x2de6aa=join(_0x4e6f01,_0x3b64f7);try{await mkdir(_0x2de6aa,{'mode':0x1c0});}catch(_0x5bda0a){if(_0x5bda0a[_0x33754d(0x15f)]===_0x33754d(0x148))throw new Error(_0x33754d(0x143));throw _0x5bda0a;}return await safeDirectory(_0x2de6aa),{'path':_0x33754d(0x17c)+_0x3b64f7+'/'+_0x2fbce2,'absolutePath':join(_0x2de6aa,_0x2fbce2),'status':_0x33754d(0x178)};}}async function safeDirectory(_0x2e63bc){const _0x3f5d13=_0x49dafc,_0x1fbc26=await lstat(_0x2e63bc);if(!_0x1fbc26[_0x3f5d13(0x18c)]()||_0x1fbc26[_0x3f5d13(0x13c)]())throw new Error(_0x3f5d13(0x192));}function plain(_0x202416){const _0x2e6cb7=_0x49dafc;return typeof _0x202416===_0x2e6cb7(0x135)&&_0x202416!==null&&!Array[_0x2e6cb7(0x18b)](_0x202416);}
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { lstat, mkdir, open, readFile, rename, rm } from 'node:fs/promises';
|
|
3
|
+
import { dirname, join, resolve } from 'node:path';
|
|
4
|
+
import { parseComposerAttachmentUpload, validComposerAttachmentName } from '../util/node-operation-parsers.js';
|
|
5
|
+
import { preflightWorkspaceUpload, workspaceUploadTemporaryName } from '../workspace.js';
|
|
6
|
+
const CHUNK_BYTES = 512 * 1024;
|
|
7
|
+
const TTL_MS = 60 * 60_000;
|
|
8
|
+
const MAX_ATTACHMENT_BYTES = 5 * 1024 * 1024;
|
|
9
|
+
export class WorkspaceUploadService {
|
|
10
|
+
state;
|
|
11
|
+
onCompleted;
|
|
12
|
+
constructor(state, onCompleted) {
|
|
13
|
+
this.state = state;
|
|
14
|
+
this.onCompleted = onCompleted;
|
|
15
|
+
}
|
|
16
|
+
async preflight(workspace, input) {
|
|
17
|
+
if ((input.directory !== undefined && typeof input.directory !== 'string') ||
|
|
18
|
+
!Array.isArray(input.files) ||
|
|
19
|
+
input.files.length === 0 ||
|
|
20
|
+
input.files.some((file) => !plain(file) || typeof file.name !== 'string'))
|
|
21
|
+
throw new Error('UPLOAD_INVALID');
|
|
22
|
+
const seen = new Set();
|
|
23
|
+
return Promise.all(input.files.map(async (file) => {
|
|
24
|
+
const name = file.name;
|
|
25
|
+
if (seen.has(name))
|
|
26
|
+
return { name, status: 'INVALID_NAME', path: null };
|
|
27
|
+
seen.add(name);
|
|
28
|
+
const target = await preflightWorkspaceUpload(workspace.path, typeof input.directory === 'string' ? input.directory : '.', name);
|
|
29
|
+
return { name, status: target.status, path: target.path || null };
|
|
30
|
+
}));
|
|
31
|
+
}
|
|
32
|
+
async create(workspace, input) {
|
|
33
|
+
const attachment = parseComposerAttachmentUpload(input);
|
|
34
|
+
if ((input.directory !== undefined && typeof input.directory !== 'string') ||
|
|
35
|
+
typeof input.name !== 'string' ||
|
|
36
|
+
!Number.isSafeInteger(input.size) ||
|
|
37
|
+
input.size < 0 ||
|
|
38
|
+
typeof input.overwrite !== 'boolean' ||
|
|
39
|
+
(attachment !== undefined && input.directory !== undefined))
|
|
40
|
+
throw new Error('UPLOAD_INVALID');
|
|
41
|
+
if (attachment !== undefined &&
|
|
42
|
+
(!validComposerAttachmentName(input.name) ||
|
|
43
|
+
input.size > MAX_ATTACHMENT_BYTES ||
|
|
44
|
+
this.state.composerAttachments.has(attachment.id) ||
|
|
45
|
+
[...this.state.uploads.values()].some((upload) => upload.composerAttachment?.id === attachment.id)))
|
|
46
|
+
throw new Error('COMPOSER_ATTACHMENT_INVALID');
|
|
47
|
+
const target = attachment === undefined
|
|
48
|
+
? await preflightWorkspaceUpload(workspace.path, typeof input.directory === 'string' ? input.directory : '.', input.name)
|
|
49
|
+
: await this.attachmentTarget(workspace.path, attachment.id, input.name);
|
|
50
|
+
if (target.status === 'INVALID_NAME')
|
|
51
|
+
throw new Error('UPLOAD_NAME_INVALID');
|
|
52
|
+
if (target.status === 'EXISTS_DIRECTORY')
|
|
53
|
+
throw new Error('UPLOAD_TARGET_DIRECTORY');
|
|
54
|
+
if (target.status === 'EXISTS_FILE' && !input.overwrite)
|
|
55
|
+
throw new Error('UPLOAD_TARGET_EXISTS');
|
|
56
|
+
if ([...this.state.uploads.values()].some((upload) => upload.workspaceId === workspace.id && upload.targetPath === target.absolutePath))
|
|
57
|
+
throw new Error('UPLOAD_TARGET_BUSY');
|
|
58
|
+
const id = randomUUID();
|
|
59
|
+
const temporaryPath = resolve(dirname(target.absolutePath), workspaceUploadTemporaryName(id));
|
|
60
|
+
const handle = await open(temporaryPath, 'wx');
|
|
61
|
+
const timer = setTimeout(() => {
|
|
62
|
+
const upload = this.state.uploads.get(id);
|
|
63
|
+
if (upload !== undefined)
|
|
64
|
+
void this.remove(upload);
|
|
65
|
+
}, TTL_MS);
|
|
66
|
+
const upload = {
|
|
67
|
+
id,
|
|
68
|
+
workspaceId: workspace.id,
|
|
69
|
+
path: target.path,
|
|
70
|
+
targetPath: target.absolutePath,
|
|
71
|
+
temporaryPath,
|
|
72
|
+
size: input.size,
|
|
73
|
+
overwrite: input.overwrite,
|
|
74
|
+
...(attachment === undefined ? {} : { composerAttachment: attachment }),
|
|
75
|
+
handle,
|
|
76
|
+
receivedBytes: 0,
|
|
77
|
+
timer
|
|
78
|
+
};
|
|
79
|
+
this.state.uploads.set(id, upload);
|
|
80
|
+
return this.status(id);
|
|
81
|
+
}
|
|
82
|
+
async write(input) {
|
|
83
|
+
if (typeof input.uploadId !== 'string' ||
|
|
84
|
+
!Number.isSafeInteger(input.offset) ||
|
|
85
|
+
input.offset < 0 ||
|
|
86
|
+
typeof input.dataBase64 !== 'string')
|
|
87
|
+
throw new Error('UPLOAD_CHUNK_INVALID');
|
|
88
|
+
const upload = this.require(input.uploadId);
|
|
89
|
+
if (input.offset !== upload.receivedBytes)
|
|
90
|
+
throw new Error('UPLOAD_OFFSET_INVALID');
|
|
91
|
+
const bytes = Buffer.from(input.dataBase64, 'base64');
|
|
92
|
+
if (bytes.length === 0 || bytes.length > CHUNK_BYTES)
|
|
93
|
+
throw new Error('UPLOAD_CHUNK_INVALID');
|
|
94
|
+
if (upload.receivedBytes + bytes.length > upload.size)
|
|
95
|
+
throw new Error('UPLOAD_SIZE_INVALID');
|
|
96
|
+
const result = await upload.handle.write(bytes, 0, bytes.length, upload.receivedBytes);
|
|
97
|
+
if (result.bytesWritten !== bytes.length)
|
|
98
|
+
throw new Error('UPLOAD_WRITE_FAILED');
|
|
99
|
+
upload.receivedBytes += bytes.length;
|
|
100
|
+
return this.status(upload.id);
|
|
101
|
+
}
|
|
102
|
+
status(uploadId) {
|
|
103
|
+
const upload = this.require(uploadId);
|
|
104
|
+
return {
|
|
105
|
+
uploadId: upload.id,
|
|
106
|
+
path: upload.path,
|
|
107
|
+
receivedBytes: upload.receivedBytes,
|
|
108
|
+
size: upload.size
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
async complete(uploadId) {
|
|
112
|
+
const upload = this.require(uploadId);
|
|
113
|
+
if (upload.receivedBytes !== upload.size)
|
|
114
|
+
throw new Error('UPLOAD_INCOMPLETE');
|
|
115
|
+
await upload.handle.sync();
|
|
116
|
+
await upload.handle.close();
|
|
117
|
+
const existing = await lstat(upload.targetPath).catch((error) => error.code === 'ENOENT' ? undefined : Promise.reject(error));
|
|
118
|
+
if (existing?.isSymbolicLink())
|
|
119
|
+
throw new Error('FILE_PATH_ESCAPE');
|
|
120
|
+
if (existing !== undefined && (!existing.isFile() || !upload.overwrite))
|
|
121
|
+
throw new Error('UPLOAD_TARGET_EXISTS');
|
|
122
|
+
try {
|
|
123
|
+
await rename(upload.temporaryPath, upload.targetPath);
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
if (error.code !== 'EEXIST')
|
|
127
|
+
throw error;
|
|
128
|
+
if (!upload.overwrite)
|
|
129
|
+
throw new Error('UPLOAD_TARGET_EXISTS');
|
|
130
|
+
await rm(upload.targetPath, { force: false });
|
|
131
|
+
await rename(upload.temporaryPath, upload.targetPath);
|
|
132
|
+
}
|
|
133
|
+
clearTimeout(upload.timer);
|
|
134
|
+
this.state.uploads.delete(upload.id);
|
|
135
|
+
this.onCompleted(upload.workspaceId);
|
|
136
|
+
if (upload.composerAttachment !== undefined) {
|
|
137
|
+
const attachmentId = upload.composerAttachment.id;
|
|
138
|
+
const timer = setTimeout(() => {
|
|
139
|
+
const attachment = this.state.composerAttachments.get(attachmentId);
|
|
140
|
+
if (attachment !== undefined)
|
|
141
|
+
void this.removeCompleted(attachment);
|
|
142
|
+
}, TTL_MS);
|
|
143
|
+
timer.unref();
|
|
144
|
+
this.state.composerAttachments.set(attachmentId, {
|
|
145
|
+
id: upload.composerAttachment.id,
|
|
146
|
+
workspaceId: upload.workspaceId,
|
|
147
|
+
path: upload.path,
|
|
148
|
+
targetPath: upload.targetPath,
|
|
149
|
+
name: upload.composerAttachment.name,
|
|
150
|
+
mime: upload.composerAttachment.mime,
|
|
151
|
+
size: upload.size,
|
|
152
|
+
timer
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
uploadId: upload.id,
|
|
157
|
+
path: upload.path,
|
|
158
|
+
receivedBytes: upload.receivedBytes,
|
|
159
|
+
size: upload.size
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
async cancel(uploadId) {
|
|
163
|
+
if (typeof uploadId !== 'string')
|
|
164
|
+
throw new Error('UPLOAD_NOT_FOUND');
|
|
165
|
+
const active = this.state.uploads.get(uploadId);
|
|
166
|
+
if (active !== undefined)
|
|
167
|
+
return this.remove(active);
|
|
168
|
+
const attachment = this.state.composerAttachments.get(uploadId);
|
|
169
|
+
if (attachment === undefined)
|
|
170
|
+
throw new Error('UPLOAD_NOT_FOUND');
|
|
171
|
+
await this.removeCompleted(attachment);
|
|
172
|
+
}
|
|
173
|
+
async resolve(workspaceId, ids) {
|
|
174
|
+
return Promise.all(ids.map(async (id) => {
|
|
175
|
+
const stored = this.state.composerAttachments.get(id);
|
|
176
|
+
if (stored === undefined || stored.workspaceId !== workspaceId)
|
|
177
|
+
throw new Error('COMPOSER_ATTACHMENT_UNAVAILABLE');
|
|
178
|
+
const metadata = await lstat(stored.targetPath).catch(() => undefined);
|
|
179
|
+
if (metadata === undefined ||
|
|
180
|
+
metadata.isSymbolicLink() ||
|
|
181
|
+
!metadata.isFile() ||
|
|
182
|
+
metadata.size !== stored.size)
|
|
183
|
+
throw new Error('COMPOSER_ATTACHMENT_UNAVAILABLE');
|
|
184
|
+
const bytes = await readFile(stored.targetPath);
|
|
185
|
+
return {
|
|
186
|
+
attachment: {
|
|
187
|
+
name: stored.name,
|
|
188
|
+
mime: stored.mime,
|
|
189
|
+
dataBase64: bytes.toString('base64')
|
|
190
|
+
},
|
|
191
|
+
path: stored.path
|
|
192
|
+
};
|
|
193
|
+
}));
|
|
194
|
+
}
|
|
195
|
+
async consume(ids) {
|
|
196
|
+
await Promise.all(ids.map(async (id) => {
|
|
197
|
+
const attachment = this.state.composerAttachments.get(id);
|
|
198
|
+
if (attachment !== undefined)
|
|
199
|
+
await this.removeCompleted(attachment);
|
|
200
|
+
}));
|
|
201
|
+
}
|
|
202
|
+
async shutdown() {
|
|
203
|
+
await Promise.allSettled([...this.state.uploads.values()].map((upload) => this.remove(upload)));
|
|
204
|
+
for (const attachment of this.state.composerAttachments.values())
|
|
205
|
+
await this.removeCompleted(attachment);
|
|
206
|
+
this.state.composerAttachments.clear();
|
|
207
|
+
}
|
|
208
|
+
require(id) {
|
|
209
|
+
if (typeof id !== 'string')
|
|
210
|
+
throw new Error('UPLOAD_NOT_FOUND');
|
|
211
|
+
const upload = this.state.uploads.get(id);
|
|
212
|
+
if (upload === undefined)
|
|
213
|
+
throw new Error('UPLOAD_NOT_FOUND');
|
|
214
|
+
return upload;
|
|
215
|
+
}
|
|
216
|
+
async remove(upload) {
|
|
217
|
+
this.state.uploads.delete(upload.id);
|
|
218
|
+
clearTimeout(upload.timer);
|
|
219
|
+
await upload.handle.close().catch(() => undefined);
|
|
220
|
+
await rm(upload.temporaryPath, { force: true }).catch(() => undefined);
|
|
221
|
+
if (upload.composerAttachment !== undefined)
|
|
222
|
+
await rm(dirname(upload.targetPath), { recursive: true, force: true }).catch(() => undefined);
|
|
223
|
+
}
|
|
224
|
+
async removeCompleted(attachment) {
|
|
225
|
+
if (this.state.composerAttachments.get(attachment.id) !== attachment)
|
|
226
|
+
return;
|
|
227
|
+
this.state.composerAttachments.delete(attachment.id);
|
|
228
|
+
clearTimeout(attachment.timer);
|
|
229
|
+
await rm(dirname(attachment.targetPath), { recursive: true, force: true }).catch(() => undefined);
|
|
230
|
+
}
|
|
231
|
+
async attachmentTarget(workspacePath, id, name) {
|
|
232
|
+
const mar = resolve(workspacePath, '.mar');
|
|
233
|
+
await mkdir(mar, { recursive: true, mode: 0o700 });
|
|
234
|
+
await safeDirectory(mar);
|
|
235
|
+
const temp = join(mar, 'temp-files');
|
|
236
|
+
await mkdir(temp, { recursive: true, mode: 0o700 });
|
|
237
|
+
await safeDirectory(temp);
|
|
238
|
+
const directory = join(temp, id);
|
|
239
|
+
try {
|
|
240
|
+
await mkdir(directory, { mode: 0o700 });
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
if (error.code === 'EEXIST')
|
|
244
|
+
throw new Error('COMPOSER_ATTACHMENT_INVALID');
|
|
245
|
+
throw error;
|
|
246
|
+
}
|
|
247
|
+
await safeDirectory(directory);
|
|
248
|
+
return {
|
|
249
|
+
path: `.mar/temp-files/${id}/${name}`,
|
|
250
|
+
absolutePath: join(directory, name),
|
|
251
|
+
status: 'READY'
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
async function safeDirectory(path) {
|
|
256
|
+
const value = await lstat(path);
|
|
257
|
+
if (!value.isDirectory() || value.isSymbolicLink())
|
|
258
|
+
throw new Error('COMPOSER_ATTACHMENT_DIRECTORY_INVALID');
|
|
259
|
+
}
|
|
260
|
+
function plain(value) {
|
|
261
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
262
|
+
}
|
|
@@ -1,2 +1,68 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/** Handles only the Workspace Watch protocol and delegates all topic reads to snapshots. */
|
|
2
|
+
export class WorkspaceWatchController {
|
|
3
|
+
options;
|
|
4
|
+
constructor(options) {
|
|
5
|
+
this.options = options;
|
|
6
|
+
}
|
|
7
|
+
operations() {
|
|
8
|
+
return {
|
|
9
|
+
'workspace.watch': (data) => this.watch(record(data)),
|
|
10
|
+
'workspace.unwatch': (data) => this.unwatch(record(data))
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
async watch(input) {
|
|
14
|
+
this.validate(input);
|
|
15
|
+
const workspace = this.options.requireWorkspace(input.workspaceId);
|
|
16
|
+
const key = this.options.watch.key(input);
|
|
17
|
+
if (input.mode === 'renew') {
|
|
18
|
+
const active = this.options.watch.renew(workspace.id, key, input.watchId);
|
|
19
|
+
if (active === undefined)
|
|
20
|
+
throw new Error('WORKSPACE_WATCH_NOT_FOUND');
|
|
21
|
+
return this.accepted(active);
|
|
22
|
+
}
|
|
23
|
+
const topics = this.options.watch.parseTopics(input);
|
|
24
|
+
const existing = this.options.watch.existing(workspace.id, key);
|
|
25
|
+
if (existing !== undefined &&
|
|
26
|
+
existing.watchId === input.watchId &&
|
|
27
|
+
JSON.stringify(existing.topics) === JSON.stringify(topics)) {
|
|
28
|
+
this.options.watch.renew(workspace.id, key, input.watchId);
|
|
29
|
+
return this.accepted(existing);
|
|
30
|
+
}
|
|
31
|
+
const watch = this.options.watch.create(workspace.id, input.watchId, this.options.watch.sessionHash(input), input.workbenchConnectionId, this.options.nodeGeneration(), topics);
|
|
32
|
+
void this.options.snapshots.initial(workspace, watch);
|
|
33
|
+
return this.accepted(watch);
|
|
34
|
+
}
|
|
35
|
+
unwatch(input) {
|
|
36
|
+
if (typeof input.workspaceId !== 'string' ||
|
|
37
|
+
typeof input.watchId !== 'string' ||
|
|
38
|
+
typeof input.workbenchConnectionId !== 'string' ||
|
|
39
|
+
!/^[A-Za-z0-9_-]{8,128}$/.test(input.watchId))
|
|
40
|
+
throw new Error('WORKSPACE_WATCH_INVALID');
|
|
41
|
+
const key = this.options.watch.key(input);
|
|
42
|
+
const watch = this.options.watch.renew(input.workspaceId, key, input.watchId);
|
|
43
|
+
if (watch !== undefined)
|
|
44
|
+
this.options.watch.remove(input.workspaceId, key);
|
|
45
|
+
return { stopped: true };
|
|
46
|
+
}
|
|
47
|
+
validate(input) {
|
|
48
|
+
if (typeof input.workspaceId !== 'string' ||
|
|
49
|
+
typeof input.watchId !== 'string' ||
|
|
50
|
+
typeof input.workbenchConnectionId !== 'string' ||
|
|
51
|
+
!/^[A-Za-z0-9_-]{8,128}$/.test(input.watchId) ||
|
|
52
|
+
(input.mode !== 'initial' && input.mode !== 'renew'))
|
|
53
|
+
throw new Error('WORKSPACE_WATCH_INVALID');
|
|
54
|
+
}
|
|
55
|
+
accepted(watch) {
|
|
56
|
+
return {
|
|
57
|
+
accepted: true,
|
|
58
|
+
watchId: watch.watchId,
|
|
59
|
+
nodeGeneration: watch.nodeGeneration,
|
|
60
|
+
expiresAt: watch.expiresAt
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function record(value) {
|
|
65
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
66
|
+
throw new Error('WORKSPACE_WATCH_INVALID');
|
|
67
|
+
return value;
|
|
68
|
+
}
|