@dev-mainsequence/codex-plugin 0.4.2 → 0.7.9
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/.codex-plugin/plugin.json +3 -3
- package/.mcp.json +2 -1
- package/README.md +30 -4
- package/assets/mainsequence-mark.svg +453 -0
- package/build-info.json +3 -3
- package/dist/hosts/discovery.js +138 -24
- package/dist/hosts/messaging.js +63 -0
- package/dist/hosts/panel/panel.html +225 -125
- package/dist/hosts/workspace.js +107 -0
- package/dist/panel-server.js +115 -27
- package/dist/vendor/devtools/index.js +41 -1
- package/dist/vendor/devtools/package.json +1 -1
- package/dist/vendor/devtools/shared/actions.js +36 -7
- package/dist/vendor/devtools/shared/agentAssets.js +38 -0
- package/dist/vendor/devtools/shared/agentDelivery.js +477 -0
- package/dist/vendor/devtools/shared/agentIcon.js +43 -0
- package/dist/vendor/devtools/shared/agents.js +13 -6
- package/dist/vendor/devtools/shared/arguments.js +16 -9
- package/dist/vendor/devtools/shared/deployments.js +214 -0
- package/dist/vendor/devtools/shared/mcp.js +124 -25
- package/dist/vendor/devtools/shared/mcpTransport.js +64 -21
- package/dist/vendor/devtools/shared/organizationProjectWorkspace.js +8 -3
- package/dist/vendor/devtools/shared/projectTools.js +437 -0
- package/dist/vendor/devtools/shared/repositories.js +2 -2
- package/dist/vendor/devtools/shared/session.js +46 -15
- package/dist/vendor/devtools/shared/sharedTransport.js +506 -0
- package/dist/vendor/devtools/shared/stateDirectory.js +44 -0
- package/dist/vendor/devtools/shared/workingDirectory.js +13 -0
- package/dist/vendor/devtools/shared/workspaceSettings.js +96 -0
- package/docs/agents-tools-panel.md +471 -12
- package/docs/conversational-mcp.md +15 -2
- package/package.json +3 -3
- package/skills/mainsequence-mcp/SKILL.md +18 -3
- package/skills/ms/assets/mainsequence-mark.svg +453 -0
|
@@ -53,6 +53,7 @@ const session_1 = require("./session.js");
|
|
|
53
53
|
const repositoryIdentity_1 = require("./repositoryIdentity.js");
|
|
54
54
|
const repositoryClient_1 = require("./repositoryClient.js");
|
|
55
55
|
const repositoryModels_1 = require("./repositoryModels.js");
|
|
56
|
+
const stateDirectory_1 = require("./stateDirectory.js");
|
|
56
57
|
async function readProjectEnv(root) {
|
|
57
58
|
let text;
|
|
58
59
|
try {
|
|
@@ -92,8 +93,7 @@ async function resolveBackend(explicit, root, env = process.env) {
|
|
|
92
93
|
/** Resolve non-secret workspace settings without requiring a session or writing configuration. */
|
|
93
94
|
async function readWorkspacePreferences(options = {}) {
|
|
94
95
|
const env = options.env ?? process.env, home = options.homeDir ?? os.homedir();
|
|
95
|
-
const directory =
|
|
96
|
-
: process.platform === 'win32' ? path.join(env.APPDATA || home, 'MainSequenceCLI') : path.join(home, '.config', 'mainsequence');
|
|
96
|
+
const directory = (0, stateDirectory_1.mainSequenceStateDirectory)(env, home);
|
|
97
97
|
const config = await fs.readFile(options.configPath ?? path.join(directory, 'config.json'), 'utf8')
|
|
98
98
|
.then(text => JSON.parse(text)).catch(() => ({}));
|
|
99
99
|
const folder = config?.mainsequence_path;
|
|
@@ -177,6 +177,12 @@ class MachineSessionService {
|
|
|
177
177
|
throw new models_1.ToolingError('NOT_AUTHENTICATED', 'No session is saved for this backend. Run login.');
|
|
178
178
|
return { session, source: 'machine' };
|
|
179
179
|
}
|
|
180
|
+
async selectionIdentity(input) {
|
|
181
|
+
const backend = requireBackend(input), { session, source } = await this.selected(backend);
|
|
182
|
+
const expiresAt = expiry(session.access);
|
|
183
|
+
return { key: crypto.createHash('sha256').update(JSON.stringify([backend, source, session.username, session.access, session.refresh])).digest('hex'),
|
|
184
|
+
...(expiresAt ? { expiresAt: Date.parse(expiresAt) } : {}) };
|
|
185
|
+
}
|
|
180
186
|
async http(backend, method, apiPath, body, access, timeoutMs) {
|
|
181
187
|
this.options.signal?.throwIfAborted();
|
|
182
188
|
if ((apiPath !== '/mcp' && !/^\/(api|auth)\//.test(apiPath)) || apiPath.includes('..'))
|
|
@@ -184,19 +190,25 @@ class MachineSessionService {
|
|
|
184
190
|
const controller = new AbortController();
|
|
185
191
|
const abort = () => controller.abort();
|
|
186
192
|
this.options.signal?.addEventListener('abort', abort, { once: true });
|
|
187
|
-
const
|
|
193
|
+
const budget = this.options.requestTimeoutMs ?? timeoutMs ?? 30_000;
|
|
194
|
+
let timedOut = false;
|
|
195
|
+
const timer = setTimeout(() => { timedOut = true; controller.abort(); }, budget);
|
|
188
196
|
try {
|
|
189
197
|
const response = await (this.options.fetch ?? fetch)(backend + apiPath, {
|
|
190
198
|
method, redirect: 'error', signal: controller.signal,
|
|
199
|
+
// Opt-in shared transports may deduplicate only these stateless platform
|
|
200
|
+
// reads. Never infer read safety from arbitrary project tool annotations.
|
|
201
|
+
...(apiPath === '/mcp' && body && typeof body === 'object' && (['initialize', 'notifications/initialized', 'tools/list'].includes(body.method)
|
|
202
|
+
|| body.method === 'tools/call' && ['agent.list', 'agent.search', 'agent.get', 'resource_release.mcp_endpoints'].includes(body.params?.name)) ? { cache: 'force-cache' } : {}),
|
|
191
203
|
headers: { 'Content-Type': 'application/json', ...(apiPath === '/mcp' ? { Accept: 'application/json, text/event-stream', 'MCP-Protocol-Version': '2025-11-25' } : {}), ...(access ? { Authorization: `Bearer ${access}` } : {}) },
|
|
192
204
|
body: body === undefined ? undefined : JSON.stringify(body)
|
|
193
205
|
});
|
|
194
206
|
if (response.status === 401 || response.status === 403)
|
|
195
207
|
throw new models_1.ToolingError('AUTH_REJECTED', 'The backend rejected the selected credentials.');
|
|
196
208
|
if (!response.ok)
|
|
197
|
-
throw new models_1.ToolingError('API_ERROR', `The backend returned HTTP ${response.status}.`);
|
|
209
|
+
throw new models_1.ToolingError('API_ERROR', `The backend ${backend} returned HTTP ${response.status} for ${method} ${apiPath}.`);
|
|
198
210
|
if (apiPath === '/mcp')
|
|
199
|
-
return await (0, mcpTransport_1.readMcpResponse)(response, body);
|
|
211
|
+
return await (0, mcpTransport_1.readMcpResponse)(response, body, 'platform MCP ' + backend);
|
|
200
212
|
if (response.status === 204)
|
|
201
213
|
return undefined;
|
|
202
214
|
try {
|
|
@@ -211,25 +223,33 @@ class MachineSessionService {
|
|
|
211
223
|
throw new models_1.ToolingError('CANCELLED', 'The action was cancelled.');
|
|
212
224
|
if (error instanceof models_1.ToolingError)
|
|
213
225
|
throw error;
|
|
214
|
-
throw new models_1.ToolingError('BACKEND_UNREACHABLE',
|
|
226
|
+
throw new models_1.ToolingError('BACKEND_UNREACHABLE', `The backend ${backend} could not be reached for ${method} ${apiPath}: `
|
|
227
|
+
+ (timedOut ? `the request timed out after ${(0, mcpTransport_1.describeSeconds)(budget)} s` : (0, mcpTransport_1.failureReason)(error) || 'the connection failed') + '. The saved session has been kept.');
|
|
215
228
|
}
|
|
216
229
|
finally {
|
|
217
230
|
clearTimeout(timer);
|
|
218
231
|
this.options.signal?.removeEventListener('abort', abort);
|
|
219
232
|
}
|
|
220
233
|
}
|
|
221
|
-
renew(backend, expectedAccount) {
|
|
234
|
+
renew(backend, expectedAccount, stale) {
|
|
222
235
|
const renewalKey = JSON.stringify([backend, expectedAccount]);
|
|
223
236
|
const running = this.renewals.get(renewalKey);
|
|
224
237
|
if (running)
|
|
225
238
|
return running;
|
|
226
|
-
const
|
|
239
|
+
const run = async () => {
|
|
227
240
|
// Bypass cached state: another SDK process may have rotated the refresh token.
|
|
228
241
|
const previous = await this.saved(backend);
|
|
229
242
|
if (!previous?.refresh)
|
|
230
243
|
throw new models_1.ToolingError('NOT_AUTHENTICATED', 'No refreshable machine session is saved for this backend.');
|
|
231
244
|
if (expectedAccount !== undefined && previous.username && previous.username !== expectedAccount)
|
|
232
245
|
throw this.accountChanged();
|
|
246
|
+
// Another host process may have renewed while this one waited for the
|
|
247
|
+
// shared lease. Reuse its saved pair; never rotate it a second time.
|
|
248
|
+
if (stale && previous.access && previous.access !== stale.access) {
|
|
249
|
+
if (expectedAccount !== undefined)
|
|
250
|
+
await this.verifyAccount(backend, previous, expectedAccount);
|
|
251
|
+
return previous;
|
|
252
|
+
}
|
|
233
253
|
let data;
|
|
234
254
|
try {
|
|
235
255
|
data = await this.http(backend, 'POST', '/auth/jwt-token/token/refresh/', { refresh: previous.refresh });
|
|
@@ -242,7 +262,10 @@ class MachineSessionService {
|
|
|
242
262
|
const session = this.remember(tokenPair(data, previous));
|
|
243
263
|
if (expectedAccount !== undefined) {
|
|
244
264
|
const current = await this.saved(backend);
|
|
245
|
-
|
|
265
|
+
// A shared transport can coalesce simultaneous rotations. Another client
|
|
266
|
+
// may already have saved this exact returned pair; that is the same login.
|
|
267
|
+
if (!current || (current.access !== previous.access || current.refresh !== previous.refresh)
|
|
268
|
+
&& (current.access !== session.access || current.refresh !== session.refresh))
|
|
246
269
|
throw this.accountChanged();
|
|
247
270
|
}
|
|
248
271
|
// Refresh may rotate the old token. Keep the new pair even if the subsequent
|
|
@@ -251,7 +274,8 @@ class MachineSessionService {
|
|
|
251
274
|
if (expectedAccount !== undefined)
|
|
252
275
|
await this.verifyAccount(backend, session, expectedAccount);
|
|
253
276
|
return session;
|
|
254
|
-
}
|
|
277
|
+
};
|
|
278
|
+
const task = this.options.sessionLock ? this.options.sessionLock(backend, run, this.options.signal) : run();
|
|
255
279
|
this.renewals.set(renewalKey, task);
|
|
256
280
|
void task.finally(() => this.renewals.delete(renewalKey)).catch(() => { });
|
|
257
281
|
return task;
|
|
@@ -330,15 +354,22 @@ class MachineSessionService {
|
|
|
330
354
|
target(value);
|
|
331
355
|
return this.accountRequest(backend, method, apiPath, body, retrySafe, options, expectedAccount);
|
|
332
356
|
},
|
|
333
|
-
status: async (value) => {
|
|
357
|
+
status: async (value, check = true) => {
|
|
334
358
|
target(value);
|
|
335
|
-
|
|
359
|
+
// During one account-bound operation, reuse the identity verified for this
|
|
360
|
+
// exact access token. Still reread shared credentials on every boundary;
|
|
361
|
+
// a changed token must be verified before it can access the remote service.
|
|
362
|
+
if (check)
|
|
363
|
+
await this.accountRequest(backend, 'GET', '/api/v1/users/me/', undefined, true, undefined, expectedAccount);
|
|
336
364
|
const current = await this.selected(backend);
|
|
337
365
|
await this.verifyAccount(backend, current.session, expectedAccount);
|
|
338
|
-
const
|
|
339
|
-
if (
|
|
366
|
+
const final = await this.selected(backend);
|
|
367
|
+
if (final.source !== current.source || final.session.access !== current.session.access || final.session.refresh !== current.session.refresh)
|
|
340
368
|
throw this.accountChanged();
|
|
341
|
-
|
|
369
|
+
const source = current.source === 'environment' ? 'environment' : this.options.persistence?.mode ?? 'machine';
|
|
370
|
+
return { backend, authenticated: true, source, persistence: source,
|
|
371
|
+
store: source === 'environment' ? null : this.options.persistence?.name ?? this.store?.name ?? null,
|
|
372
|
+
username: expectedAccount, expiresAt: expiry(current.session.access), checked: true };
|
|
342
373
|
},
|
|
343
374
|
login: unsupported, logout: unsupported, refresh: unsupported,
|
|
344
375
|
sanitize: text => this.sanitize(text),
|
|
@@ -353,7 +384,7 @@ class MachineSessionService {
|
|
|
353
384
|
let { session, source } = await this.selected(backend);
|
|
354
385
|
if (!session.access || (expiry(session.access) && Date.parse(expiry(session.access)) <= Date.now() + 30_000)) {
|
|
355
386
|
if (source === 'machine')
|
|
356
|
-
session = await this.renew(backend, expectedAccount);
|
|
387
|
+
session = await this.renew(backend, expectedAccount, session);
|
|
357
388
|
else if (!session.access)
|
|
358
389
|
throw new models_1.ToolingError('NOT_AUTHENTICATED', 'The explicit environment has no access token.');
|
|
359
390
|
}
|
|
@@ -388,7 +419,7 @@ class MachineSessionService {
|
|
|
388
419
|
if (!retrySafe)
|
|
389
420
|
throw error;
|
|
390
421
|
const latest = await this.saved(backend);
|
|
391
|
-
const retry = latest?.access && latest.access !== session.access ? latest : await this.renew(backend, expectedAccount);
|
|
422
|
+
const retry = latest?.access && latest.access !== session.access ? latest : await this.renew(backend, expectedAccount, session);
|
|
392
423
|
try {
|
|
393
424
|
return await send(retry);
|
|
394
425
|
}
|