@dev-mainsequence/codex-plugin 0.4.2 → 0.7.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/.codex-plugin/plugin.json +3 -3
  2. package/.mcp.json +2 -1
  3. package/README.md +30 -4
  4. package/assets/mainsequence-mark.svg +453 -0
  5. package/build-info.json +3 -3
  6. package/dist/hosts/discovery.js +138 -24
  7. package/dist/hosts/messaging.js +63 -0
  8. package/dist/hosts/panel/panel.html +250 -125
  9. package/dist/hosts/workspace.js +107 -0
  10. package/dist/panel-server.js +115 -27
  11. package/dist/vendor/devtools/index.js +41 -1
  12. package/dist/vendor/devtools/package.json +1 -1
  13. package/dist/vendor/devtools/shared/actions.js +36 -7
  14. package/dist/vendor/devtools/shared/agentAssets.js +38 -0
  15. package/dist/vendor/devtools/shared/agentDelivery.js +477 -0
  16. package/dist/vendor/devtools/shared/agentIcon.js +43 -0
  17. package/dist/vendor/devtools/shared/agents.js +13 -6
  18. package/dist/vendor/devtools/shared/arguments.js +16 -9
  19. package/dist/vendor/devtools/shared/deployments.js +214 -0
  20. package/dist/vendor/devtools/shared/mcp.js +124 -25
  21. package/dist/vendor/devtools/shared/mcpTransport.js +64 -21
  22. package/dist/vendor/devtools/shared/organizationProjectWorkspace.js +8 -3
  23. package/dist/vendor/devtools/shared/projectTools.js +437 -0
  24. package/dist/vendor/devtools/shared/repositories.js +2 -2
  25. package/dist/vendor/devtools/shared/session.js +46 -15
  26. package/dist/vendor/devtools/shared/sharedTransport.js +506 -0
  27. package/dist/vendor/devtools/shared/stateDirectory.js +44 -0
  28. package/dist/vendor/devtools/shared/workingDirectory.js +13 -0
  29. package/dist/vendor/devtools/shared/workspaceSettings.js +96 -0
  30. package/docs/agents-tools-panel.md +523 -12
  31. package/docs/conversational-mcp.md +15 -2
  32. package/package.json +3 -3
  33. package/skills/mainsequence-mcp/SKILL.md +18 -3
  34. 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 = process.platform === 'darwin' ? path.join(home, 'Library', 'Application Support', 'MainSequenceCLI')
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 timer = setTimeout(abort, this.options.requestTimeoutMs ?? timeoutMs ?? 30_000);
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', 'The backend could not be reached. The saved session has been kept.');
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 task = (async () => {
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
- if (!current || current.access !== previous.access || current.refresh !== previous.refresh)
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
- await this.accountRequest(backend, 'GET', '/api/v1/users/me/', undefined, true, undefined, expectedAccount);
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 status = await this.status(backend);
339
- if (!status.authenticated)
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
- return { ...status, username: expectedAccount, checked: true };
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
  }