@dev-mainsequence/codex-plugin 0.3.0 → 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.
Files changed (39) hide show
  1. package/.codex-plugin/plugin.json +6 -5
  2. package/.mcp.json +17 -0
  3. package/README.md +49 -11
  4. package/assets/mainsequence-mark.svg +453 -0
  5. package/build-info.json +3 -3
  6. package/dist/hosts/codexPresentation.js +1 -0
  7. package/dist/hosts/discovery.js +252 -0
  8. package/dist/hosts/messaging.js +63 -0
  9. package/dist/hosts/panel/panel.html +265 -0
  10. package/dist/hosts/workspace.js +107 -0
  11. package/dist/panel-server.js +223 -0
  12. package/dist/vendor/devtools/index.js +45 -1
  13. package/dist/vendor/devtools/package.json +1 -1
  14. package/dist/vendor/devtools/shared/accountScope.js +19 -0
  15. package/dist/vendor/devtools/shared/actions.js +44 -7
  16. package/dist/vendor/devtools/shared/agentAssets.js +38 -0
  17. package/dist/vendor/devtools/shared/agentDelivery.js +477 -0
  18. package/dist/vendor/devtools/shared/agentIcon.js +43 -0
  19. package/dist/vendor/devtools/shared/agents.js +18 -7
  20. package/dist/vendor/devtools/shared/arguments.js +16 -9
  21. package/dist/vendor/devtools/shared/deployments.js +214 -0
  22. package/dist/vendor/devtools/shared/environments.js +38 -0
  23. package/dist/vendor/devtools/shared/mcp.js +237 -46
  24. package/dist/vendor/devtools/shared/mcpTransport.js +79 -23
  25. package/dist/vendor/devtools/shared/organizationProjectWorkspace.js +8 -3
  26. package/dist/vendor/devtools/shared/organizationProjects.js +2 -13
  27. package/dist/vendor/devtools/shared/projectTools.js +437 -0
  28. package/dist/vendor/devtools/shared/repositories.js +2 -2
  29. package/dist/vendor/devtools/shared/session.js +133 -14
  30. package/dist/vendor/devtools/shared/sharedTransport.js +506 -0
  31. package/dist/vendor/devtools/shared/stateDirectory.js +44 -0
  32. package/dist/vendor/devtools/shared/workingDirectory.js +13 -0
  33. package/dist/vendor/devtools/shared/workspaceSettings.js +96 -0
  34. package/docs/agents-tools-panel.md +522 -0
  35. package/docs/codex-verification.md +37 -0
  36. package/docs/conversational-mcp.md +16 -3
  37. package/package.json +9 -5
  38. package/skills/mainsequence-mcp/SKILL.md +19 -4
  39. package/skills/ms/assets/mainsequence-mark.svg +453 -0
@@ -5,6 +5,7 @@ exports.parseArguments = parseArguments;
5
5
  exports.tokenizeCommand = tokenizeCommand;
6
6
  const node_fs_1 = require("node:fs");
7
7
  const models_1 = require("./models.js");
8
+ const workingDirectory_1 = require("./workingDirectory.js");
8
9
  exports.HELP = `MainSequence developer tooling (Node 22+)
9
10
 
10
11
  ms-devtools login|logout|refresh-token|auth-status|repositories [--backend URL]
@@ -13,6 +14,7 @@ ms-devtools mcp-discover [--search TEXT] [--limit N] [--offset N] [--backend URL
13
14
  ms-devtools mcp-list [--backend URL]
14
15
  ms-devtools mcp-add|mcp-remove|mcp-tools --uid RELEASE_UUID [--backend URL]
15
16
  ms-devtools mcp-call --uid RELEASE_UUID --tool NAME [--arguments-file JSON_FILE] [--backend URL]
17
+ ms-devtools mcp-catalog [--refresh] [--backend URL]
16
18
  ms-devtools status|resolve [--path ROOT] [--type python|vite_react] [--backend URL]
17
19
  ms-devtools sync [--path ROOT]
18
20
  ms-devtools sdk-status|update-sdk|update-agents|install-skills|install|env-cleanup [--path ROOT]
@@ -27,13 +29,16 @@ skills-sync requires MainSequence MCP and reports unsupported.
27
29
  open-folder and signed-terminal require a host with those native capabilities.
28
30
  In Codex desktop, type /ms, select the ms slash-menu entry, then append COMMAND ... .
29
31
  The trusted plugin hook executes the selected command before inference.
32
+ mcp-catalog lists the saved connections' tools as hosts register them (ms_<tool>_<hash>) from the
33
+ shared catalog; --refresh re-reads every connection's catalog first. A cold project MCP can take
34
+ over a minute to answer.
30
35
  `;
31
- function parseArguments(argv, cwd = process.cwd()) {
36
+ function parseArguments(argv, cwd) {
32
37
  if (argv.length === 0 || argv[0] === '--help' || argv[0] === '-h')
33
38
  return { json: argv.includes('--json'), help: true };
34
39
  const action = argv[0];
35
40
  const flags = {};
36
- const boolean = new Set(['--json', '--check', '--dry-run', '--install']);
41
+ const boolean = new Set(['--json', '--check', '--dry-run', '--install', '--refresh']);
37
42
  const valued = new Set(['--backend', '--path', '--type', '--uid', '--branch', '--base-dir', '--search', '--limit', '--offset', '--tool', '--arguments-file']);
38
43
  for (let i = 1; i < argv.length; i++) {
39
44
  const key = argv[i];
@@ -61,16 +66,17 @@ function parseArguments(argv, cwd = process.cwd()) {
61
66
  : action === 'mcp-list' ? global
62
67
  : ['mcp-add', 'mcp-remove', 'mcp-tools'].includes(action) ? [...global, '--uid']
63
68
  : action === 'mcp-call' ? [...global, '--uid', '--tool', '--arguments-file']
64
- : ['login', 'logout', 'refresh-token', 'repositories'].includes(action) ? global
65
- : action === 'auth-status' ? [...global, '--check']
66
- : action === 'setup' ? [...global, '--uid', '--branch', '--base-dir', '--install']
67
- : [...global, ...project, ...(['update-sdk', 'install-skills'].includes(action) ? ['--dry-run'] : [])];
69
+ : action === 'mcp-catalog' ? [...global, '--refresh']
70
+ : ['login', 'logout', 'refresh-token', 'repositories'].includes(action) ? global
71
+ : action === 'auth-status' ? [...global, '--check']
72
+ : action === 'setup' ? [...global, '--uid', '--branch', '--base-dir', '--install']
73
+ : [...global, ...project, ...(['update-sdk', 'install-skills'].includes(action) ? ['--dry-run'] : [])];
68
74
  if (Object.keys(flags).some(key => !allowed.includes(key)))
69
75
  throw new models_1.ToolingError('INVALID_REQUEST', 'This option does not apply to the selected action.');
70
76
  const backend = typeof flags['--backend'] === 'string' ? flags['--backend'] : undefined;
71
77
  const common = { backend };
72
78
  let request;
73
- if (['mcp-discover', 'mcp-list', 'mcp-add', 'mcp-remove', 'mcp-tools', 'mcp-call'].includes(action)) {
79
+ if (['mcp-discover', 'mcp-list', 'mcp-add', 'mcp-remove', 'mcp-tools', 'mcp-call', 'mcp-catalog'].includes(action)) {
74
80
  let args = {};
75
81
  if (typeof flags['--arguments-file'] === 'string') {
76
82
  try {
@@ -88,7 +94,8 @@ function parseArguments(argv, cwd = process.cwd()) {
88
94
  ...(flags['--search'] !== undefined ? { search: flags['--search'] } : {}),
89
95
  ...(flags['--limit'] !== undefined ? { limit: Number(flags['--limit']) } : {}),
90
96
  ...(flags['--offset'] !== undefined ? { offset: Number(flags['--offset']) } : {}),
91
- ...(action === 'mcp-call' ? { tool: flags['--tool'], arguments: args } : {}) };
97
+ ...(action === 'mcp-call' ? { tool: flags['--tool'], arguments: args } : {}),
98
+ ...(action === 'mcp-catalog' && flags['--refresh'] ? { refresh: 'force' } : {}) };
92
99
  }
93
100
  else if (['login', 'logout', 'refresh-token', 'repositories'].includes(action))
94
101
  request = { action, ...common };
@@ -97,7 +104,7 @@ function parseArguments(argv, cwd = process.cwd()) {
97
104
  else if (action === 'setup')
98
105
  request = { action, ...common, repositoryUid: flags['--uid'], branch: flags['--branch'], baseDir: flags['--base-dir'], install: Boolean(flags['--install']) };
99
106
  else
100
- request = { action, ...common, root: flags['--path'] || cwd, projectType: flags['--type'], dryRun: Boolean(flags['--dry-run']) };
107
+ request = { action, ...common, root: flags['--path'] || cwd || (0, workingDirectory_1.currentDirectory)(), projectType: flags['--type'], dryRun: Boolean(flags['--dry-run']) };
101
108
  return { request, json: Boolean(flags['--json']) };
102
109
  }
103
110
  /** Tokenize explicit hook commands without evaluating a shell, substitutions, or expansions. */
@@ -0,0 +1,214 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HIDDEN_DEPLOYMENT = exports.DEPLOYMENT_ACTIONS = void 0;
4
+ exports.workflowRun = workflowRun;
5
+ exports.deploymentRun = deploymentRun;
6
+ exports.createDeployments = createDeployments;
7
+ const models_1 = require("./models.js");
8
+ const session_1 = require("./session.js");
9
+ const accountScope_1 = require("./accountScope.js");
10
+ exports.DEPLOYMENT_ACTIONS = new Set(['deployment-history', 'deployment-run', 'deployment-logs']);
11
+ /** Command Center's wording for a run that answers 404: it never says whether the record exists. */
12
+ exports.HIDDEN_DEPLOYMENT = "You can't see this deployment. A deployment is shown to the people who can see its release or Job.";
13
+ const UUID = /^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i;
14
+ const ACTIVE = new Set(['pending', 'queued', 'running']);
15
+ const RUN_PATH = /^\/api\/v1\/(?:deployment-runs\/[a-f0-9-]{36}\/(?:logs\/)?|deployment-history\/[a-f0-9-]{36}\/)$/;
16
+ function uuid(value, message) {
17
+ if (typeof value !== 'string' || !UUID.test(value))
18
+ throw new models_1.ToolingError('INVALID_REQUEST', message);
19
+ return value.toLowerCase();
20
+ }
21
+ function integer(value, minimum, maximum, fallback, message) {
22
+ if (value === undefined)
23
+ return fallback;
24
+ if (!Number.isInteger(value) || value < minimum || value > maximum)
25
+ throw new models_1.ToolingError('INVALID_REQUEST', message);
26
+ return value;
27
+ }
28
+ const text = (value, limit = 4000) => typeof value === 'string'
29
+ ? value.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/g, '').slice(0, limit) : '';
30
+ const optionalUuid = (value) => typeof value === 'string' && UUID.test(value) ? value.toLowerCase() : null;
31
+ function when(value) {
32
+ if (typeof value !== 'string')
33
+ return null;
34
+ const time = Date.parse(value);
35
+ return Number.isFinite(time) ? new Date(time).toISOString() : null;
36
+ }
37
+ function duration(start, end, now) {
38
+ if (!start)
39
+ return null;
40
+ const from = Date.parse(start), to = end ? Date.parse(end) : now;
41
+ return Number.isFinite(from) && Number.isFinite(to) && to >= from ? to - from : null;
42
+ }
43
+ function errorText(value) {
44
+ if (!value)
45
+ return '';
46
+ if (typeof value === 'string')
47
+ return text(value, 2000);
48
+ if (typeof value === 'object') {
49
+ const row = value;
50
+ const detail = [row.detail, row.message, row.reason].find(item => typeof item === 'string' && item.trim());
51
+ const code = typeof row.code === 'string' ? row.code : '';
52
+ if (detail || code)
53
+ return text([code, detail].filter(Boolean).join(': '), 2000);
54
+ try {
55
+ return text(JSON.stringify(value), 1000);
56
+ }
57
+ catch {
58
+ return 'Error details unavailable.';
59
+ }
60
+ }
61
+ return text(String(value), 500);
62
+ }
63
+ const earliest = (values) => values.filter((value) => !!value).sort()[0] ?? null;
64
+ const latest = (values) => values.filter((value) => !!value).sort().at(-1) ?? null;
65
+ const list = (value) => Array.isArray(value) ? value : [];
66
+ const rec = (value) => value && typeof value === 'object' && !Array.isArray(value) ? value : {};
67
+ /** An ADR-074 workflow run: the sealed step graph in plan order and the runs it owns. */
68
+ function workflowRun(row, now) {
69
+ const run = rec(row);
70
+ const uid = optionalUuid(run.uid);
71
+ if (!uid)
72
+ return null;
73
+ const steps = list(run.steps).slice(0, 60).map(item => {
74
+ const step = rec(item);
75
+ const startedAt = when(step.dispatch_started_at) ?? when(step.dispatch_authorized_at), finishedAt = when(step.finished_at);
76
+ const key = text(step.step_key, 200);
77
+ return { id: key, key, operation: text(step.operation, 100), resourceKey: text(step.resource_key, 200), state: text(step.state, 40) || 'unknown',
78
+ reason: text(step.reason, 500), needs: list(step.needs).slice(0, 40).map(need => text(need, 200)), deploymentRunUid: optionalUuid(step.deployment_run_uid),
79
+ startedAt, finishedAt, durationMs: duration(startedAt, finishedAt, now), error: errorText(step.error) };
80
+ });
81
+ const createdAt = when(run.creation_date), finishedAt = when(run.finished_at);
82
+ const startedAt = earliest([createdAt, ...steps.map(step => step.startedAt)]) ?? createdAt;
83
+ const deploymentRuns = list(run.deployment_runs).slice(0, 40).flatMap(item => {
84
+ const owned = rec(item), ownedUid = optionalUuid(owned.uid);
85
+ return ownedUid ? [{ uid: ownedUid, targetLabel: text(owned.target_label, 200), targetType: text(owned.target_type, 60), state: text(owned.state, 40) || 'unknown',
86
+ workflowStepKeys: list(owned.workflow_step_keys).slice(0, 40).map(key => text(key, 200)),
87
+ createdAt: when(owned.created_at), startedAt: when(owned.started_at), finishedAt: when(owned.finished_at) }] : [];
88
+ });
89
+ return { kind: 'workflow_run', uid, state: text(run.state, 40) || 'unknown', reason: text(run.reason, 500), workflowPath: text(run.workflow_path, 300),
90
+ repositoryUid: optionalUuid(run.code_repository_uid), repositoryName: text(run.code_repository_name, 200),
91
+ branchUid: optionalUuid(run.code_repository_branch_uid), branchName: text(run.repository_branch, 200),
92
+ commit: text(run.commit_sha, 40).slice(0, 7), createdAt, startedAt, finishedAt: finishedAt ?? (ACTIVE.has(text(run.state, 40)) ? null : latest(steps.map(step => step.finishedAt))),
93
+ durationMs: duration(startedAt, finishedAt ?? (ACTIVE.has(text(run.state, 40)) ? null : latest(steps.map(step => step.finishedAt))), now),
94
+ steps, deploymentRuns, error: errorText(run.error) };
95
+ }
96
+ /** A deployment run: its pipeline stages in `sequence` order and, when included, the workflow run that started it. */
97
+ function deploymentRun(row, now) {
98
+ const run = rec(row);
99
+ const uid = optionalUuid(run.uid);
100
+ if (!uid)
101
+ return null;
102
+ const target = rec(run.target), repository = rec(target.code_repository), branch = rec(target.code_repository_branch);
103
+ const steps = list(rec(run.pipeline).steps).map(rec).filter(step => Number.isFinite(Number(step.sequence)))
104
+ .sort((a, b) => Number(a.sequence) - Number(b.sequence)).slice(0, 60).map(step => {
105
+ const startedAt = when(step.started_at), finishedAt = when(step.finished_at);
106
+ const stepUid = optionalUuid(step.uid), key = text(step.key, 200);
107
+ return { id: stepUid ?? key, uid: stepUid, sequence: Number(step.sequence), key, name: text(step.name, 200), kind: text(step.kind, 60),
108
+ state: text(step.state, 40) || 'unknown', outcome: text(step.outcome, 100), reason: text(step.reason ?? step.skip_reason, 500),
109
+ startedAt, finishedAt, durationMs: duration(startedAt, finishedAt, now), error: errorText(step.error) };
110
+ });
111
+ const createdAt = when(run.created_at), finishedAt = when(run.finished_at);
112
+ const startedAt = when(run.started_at) ?? earliest(steps.map(step => step.startedAt)) ?? createdAt;
113
+ const workflow = run.workflow_run ? workflowRun(run.workflow_run, now) : null;
114
+ return { kind: 'deployment_run', uid, state: text(run.state, 40) || 'unknown', outcome: text(run.outcome, 100), operation: text(run.operation, 60), source: text(run.source, 60),
115
+ targetLabel: text(target.display_name || target.name, 200) || text(run.target_type, 60), targetType: text(run.target_type, 60),
116
+ repositoryUid: optionalUuid(repository.uid), repositoryName: text(repository.name, 200),
117
+ branchUid: optionalUuid(run.code_repository_branch_uid) ?? optionalUuid(branch.uid), branchName: text(branch.repository_branch, 200),
118
+ commit: text(run.commit_sha, 40).slice(0, 7), createdAt, startedAt, finishedAt, durationMs: duration(startedAt, finishedAt, now),
119
+ currentStepKey: text(rec(run.pipeline).current_step_key, 200), steps,
120
+ workflowStepKeys: list(run.workflow_step_keys).slice(0, 40).map(key => text(key, 200)), workflowRun: workflow,
121
+ logsAvailable: rec(run.logs).state === 'available' || typeof rec(run.logs).url === 'string', error: errorText(run.error) };
122
+ }
123
+ function historyEntry(row, now) {
124
+ const kind = rec(row).kind;
125
+ return kind === 'workflow_run' ? workflowRun(row, now) : kind === 'deployment_run' ? deploymentRun(row, now) : null;
126
+ }
127
+ function logEntry(row) {
128
+ const entry = rec(row);
129
+ return { sequence: Number.isFinite(Number(entry.sequence)) ? Number(entry.sequence) : null, timestamp: when(entry.timestamp), stepUid: optionalUuid(entry.step_uid),
130
+ source: text(entry.source, 100), stream: text(entry.stream, 20), level: text(entry.level, 20), text: text(entry.text, 4000) };
131
+ }
132
+ /** A read-only bridge over the public session service; the saved credentials never leave it. */
133
+ function createDeployments(options) {
134
+ const transport = options.fetch ?? fetch;
135
+ const session = (0, session_1.createSessionService)(options.host, { env: options.env, signal: options.signal, requestTimeoutMs: 30_000, sessionLock: options.sessionLock,
136
+ fetch: async (input, init) => {
137
+ const response = await transport(input, init);
138
+ // A run the reader may not see answers 404. Describe it without asserting existence,
139
+ // adapting only canonical run responses inside the authenticated transport.
140
+ if (response.status === 404 && RUN_PATH.test(new URL(String(input)).pathname)) {
141
+ await response.body?.cancel().catch(() => { });
142
+ return new Response(JSON.stringify({ hidden: true }), { status: 200, headers: { 'Content-Type': 'application/json' } });
143
+ }
144
+ return response;
145
+ } });
146
+ async function dispatch(input) {
147
+ const action = input?.action || 'unknown';
148
+ try {
149
+ const backend = (0, session_1.requireBackend)(input.backend);
150
+ const scoped = await (0, accountScope_1.bindSessionAccount)(session, backend, input.expectedAccount);
151
+ const now = Date.now(), at = new Date(now).toISOString();
152
+ let data, summary;
153
+ if (input.action === 'deployment-history') {
154
+ const environmentUid = uuid(input.environmentUid, 'Choose an organization environment first.');
155
+ const limit = integer(input.limit, 1, 100, 25, 'Request between 1 and 100 deployment history entries.');
156
+ const offset = integer(input.offset, 0, 1_000_000, 0, 'The deployment history offset is invalid.');
157
+ const branchUid = input.codeRepositoryBranchUid === undefined ? '' : uuid(input.codeRepositoryBranchUid, 'Choose a repository branch from the current environment.');
158
+ const page = await scoped.request(backend, 'GET', `/api/v1/deployment-history/?organization_environment_uid=${environmentUid}&limit=${limit}&offset=${offset}${branchUid ? `&code_repository_branch_uid=${branchUid}` : ''}`);
159
+ if (!page || !Array.isArray(page.results) || !Number.isInteger(page.count) || page.count < 0)
160
+ throw new models_1.ToolingError('API_ERROR', 'The deployment history is unavailable.');
161
+ const results = page.results.map((row) => historyEntry(row, now)).filter(Boolean);
162
+ data = { environmentUid, branchUid: branchUid || null, count: page.count, limit, offset, now: at, results };
163
+ summary = `${page.count} deployment history ${page.count === 1 ? 'entry' : 'entries'}.`;
164
+ }
165
+ else if (input.action === 'deployment-run') {
166
+ const runUid = uuid(input.runUid, 'Choose a deployment or workflow run.');
167
+ const kind = input.kind === 'workflow_run' ? 'workflow_run' : 'deployment_run';
168
+ const row = await scoped.request(backend, 'GET', kind === 'workflow_run'
169
+ ? `/api/v1/deployment-history/${runUid}/` : `/api/v1/deployment-runs/${runUid}/?include=workflow_run`);
170
+ if (rec(row).hidden === true) {
171
+ data = { hidden: true, uid: runUid, kind, now: at };
172
+ summary = exports.HIDDEN_DEPLOYMENT;
173
+ }
174
+ else {
175
+ const run = kind === 'workflow_run' ? workflowRun(row, now) : deploymentRun(row, now);
176
+ if (!run || run.uid !== runUid)
177
+ throw new models_1.ToolingError('API_ERROR', 'The deployment run record is unavailable.');
178
+ data = { hidden: false, uid: runUid, kind, run, now: at };
179
+ summary = `${kind === 'workflow_run' ? 'Workflow run' : 'Deployment'} ${run.state}.`;
180
+ }
181
+ }
182
+ else if (input.action === 'deployment-logs') {
183
+ const runUid = uuid(input.runUid, 'Choose a deployment run.');
184
+ const stepUid = input.stepUid === undefined ? '' : uuid(input.stepUid, 'Choose a stage of this deployment run.');
185
+ const limit = integer(input.limit, 1, 200, 200, 'Request between 1 and 200 log entries.');
186
+ if (input.cursor !== undefined && (typeof input.cursor !== 'string' || !/^[A-Za-z0-9_.:=~-]{1,4000}$/.test(input.cursor)))
187
+ throw new models_1.ToolingError('INVALID_REQUEST', 'Use a log cursor returned by the previous page.');
188
+ const cursor = input.cursor || '';
189
+ const page = await scoped.request(backend, 'GET', `/api/v1/deployment-runs/${runUid}/logs/?limit=${limit}${stepUid ? `&step_uid=${stepUid}` : ''}${cursor ? `&cursor=${encodeURIComponent(cursor)}` : ''}`);
190
+ if (rec(page).hidden === true) {
191
+ data = { hidden: true, uid: runUid, now: at };
192
+ summary = exports.HIDDEN_DEPLOYMENT;
193
+ }
194
+ else {
195
+ if (!page || !Array.isArray(page.entries))
196
+ throw new models_1.ToolingError('API_ERROR', 'The deployment logs are unavailable.');
197
+ const entries = page.entries.slice(0, limit).map(logEntry);
198
+ data = { hidden: false, runUid, stepUid: stepUid || null, cursor: cursor || null, nextCursor: typeof page.next_cursor === 'string' && page.next_cursor ? page.next_cursor.slice(0, 4000) : null,
199
+ complete: page.complete === true, entries, now: at };
200
+ summary = `${entries.length} log ${entries.length === 1 ? 'entry' : 'entries'}.`;
201
+ }
202
+ }
203
+ else
204
+ throw new models_1.ToolingError('INVALID_REQUEST', 'Unknown deployment action.');
205
+ await scoped.status(backend, true);
206
+ return JSON.parse(session.sanitize(JSON.stringify({ schemaVersion: 1, action, status: 'success', summary, data })));
207
+ }
208
+ catch (error) {
209
+ const failure = options.signal?.aborted ? new models_1.ToolingError('CANCELLED', 'The read was cancelled.') : error instanceof models_1.ToolingError ? error : new models_1.ToolingError('API_ERROR', 'Deployment information is unavailable.');
210
+ return { schemaVersion: 1, action, status: failure.code === 'CANCELLED' ? 'cancelled' : 'failure', summary: failure.message, error: { code: failure.code, message: failure.message } };
211
+ }
212
+ }
213
+ return { dispatch };
214
+ }
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.listEnvironments = listEnvironments;
4
+ const models_1 = require("./models.js");
5
+ const session_1 = require("./session.js");
6
+ /** One bounded directory reader for all hosts and domain clients. No settings writes. */
7
+ async function listEnvironments(session, inputBackend, signal) {
8
+ const backend = (0, session_1.requireBackend)(inputBackend), items = [], seen = new Set();
9
+ let expectedCount;
10
+ for (let offset = 0;;) {
11
+ signal?.throwIfAborted();
12
+ const page = await session.request(backend, 'GET', `/api/v1/organization-environments/?limit=100&offset=${offset}`);
13
+ signal?.throwIfAborted();
14
+ const rows = Array.isArray(page) ? page : page?.results;
15
+ const count = Array.isArray(page) ? page.length : page?.count;
16
+ if (!Array.isArray(rows) || !Number.isSafeInteger(count) || count < 0 || count > 10_000 || offset + rows.length > count
17
+ || (expectedCount !== undefined && expectedCount !== count) || (offset > 0 && Array.isArray(page))) {
18
+ throw new models_1.ToolingError('API_ERROR', 'The environment pagination is invalid or changed. Refresh the directory.');
19
+ }
20
+ expectedCount = count;
21
+ for (const row of rows) {
22
+ if (typeof row?.uid !== 'string' || !/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i.test(row.uid)
23
+ || typeof row.name !== 'string' || !row.name.trim() || /[\r\n\0]/.test(row.name)) {
24
+ throw new models_1.ToolingError('API_ERROR', 'The backend returned an invalid organization environment.');
25
+ }
26
+ const uid = row.uid.toLowerCase();
27
+ if (seen.has(uid))
28
+ throw new models_1.ToolingError('API_ERROR', 'Environment pagination returned duplicate rows.');
29
+ seen.add(uid);
30
+ items.push({ uid, name: row.name.trim(), isProduction: row.is_production === true });
31
+ }
32
+ offset += rows.length;
33
+ if (offset === count)
34
+ return items;
35
+ if (!rows.length)
36
+ throw new models_1.ToolingError('API_ERROR', 'Environment pagination stopped before completion.');
37
+ }
38
+ }