@dev-mainsequence/codex-plugin 0.0.0-stage → 0.4.2
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 +22 -0
- package/.mcp.json +16 -0
- package/LICENSE +21 -0
- package/NOTICE +13 -0
- package/README.md +155 -2
- package/assets/mainsequence-icon.png +0 -0
- package/assets/mainsequence-icon.svg +454 -0
- package/build-info.json +10 -0
- package/dist/hosts/codex.js +62 -0
- package/dist/hosts/codexPresentation.js +165 -0
- package/dist/hosts/discovery.js +138 -0
- package/dist/hosts/panel/panel.html +165 -0
- package/dist/package.json +1 -0
- package/dist/panel-server.js +135 -0
- package/dist/runner.js +71 -0
- package/dist/vendor/devtools/LICENSE +21 -0
- package/dist/vendor/devtools/NOTICE +12 -0
- package/dist/vendor/devtools/index.js +72 -0
- package/dist/vendor/devtools/package.json +5 -0
- package/dist/vendor/devtools/shared/accountScope.js +19 -0
- package/dist/vendor/devtools/shared/actions.js +164 -0
- package/dist/vendor/devtools/shared/agentReadiness.js +71 -0
- package/dist/vendor/devtools/shared/agents.js +240 -0
- package/dist/vendor/devtools/shared/arguments.js +142 -0
- package/dist/vendor/devtools/shared/cli.js +144 -0
- package/dist/vendor/devtools/shared/config.js +64 -0
- package/dist/vendor/devtools/shared/devcontainerJsonc.js +153 -0
- package/dist/vendor/devtools/shared/envfile.js +128 -0
- package/dist/vendor/devtools/shared/environments.js +38 -0
- package/dist/vendor/devtools/shared/hostSession.js +324 -0
- package/dist/vendor/devtools/shared/machineSession.js +338 -0
- package/dist/vendor/devtools/shared/mcp.js +421 -0
- package/dist/vendor/devtools/shared/mcpTransport.js +177 -0
- package/dist/vendor/devtools/shared/models.js +12 -0
- package/dist/vendor/devtools/shared/organizationProjectWorkspace.js +300 -0
- package/dist/vendor/devtools/shared/organizationProjects.js +134 -0
- package/dist/vendor/devtools/shared/process.js +61 -0
- package/dist/vendor/devtools/shared/projectOperations.js +279 -0
- package/dist/vendor/devtools/shared/projects.js +390 -0
- package/dist/vendor/devtools/shared/pythonProjectStatus.js +86 -0
- package/dist/vendor/devtools/shared/pythonSdkStatus.js +26 -0
- package/dist/vendor/devtools/shared/repositories.js +215 -0
- package/dist/vendor/devtools/shared/repositoryClient.js +108 -0
- package/dist/vendor/devtools/shared/repositoryIdentity.js +116 -0
- package/dist/vendor/devtools/shared/repositoryModels.js +72 -0
- package/dist/vendor/devtools/shared/session.js +532 -0
- package/docs/adr/0002-conversational-project-mcp-client.md +59 -0
- package/docs/agents-tools-panel.md +63 -0
- package/docs/codex-verification.md +161 -0
- package/docs/conversational-mcp.md +54 -0
- package/hooks/hooks.json +17 -0
- package/package.json +50 -4
- package/skills/mainsequence-mcp/SKILL.md +40 -0
- package/skills/ms/SKILL.md +8 -0
- package/skills/ms/agents/openai.yaml +7 -0
- package/skills/ms/assets/mainsequence-icon.png +0 -0
- package/skills/ms/assets/mainsequence-icon.svg +454 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.PROJECT_OPERATION_ACTIONS = void 0;
|
|
37
|
+
exports.validateDockerImage = validateDockerImage;
|
|
38
|
+
exports.validateDockerPlatform = validateDockerPlatform;
|
|
39
|
+
exports.createProjectOperations = createProjectOperations;
|
|
40
|
+
const fs = __importStar(require("fs/promises"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const crypto = __importStar(require("crypto"));
|
|
43
|
+
const models_1 = require("./models.js");
|
|
44
|
+
const repositories_1 = require("./repositories.js");
|
|
45
|
+
const projects_1 = require("./projects.js");
|
|
46
|
+
const devcontainerJsonc_1 = require("./devcontainerJsonc.js");
|
|
47
|
+
exports.PROJECT_OPERATION_ACTIONS = new Set([
|
|
48
|
+
'local-repositories', 'delete-local-preview', 'delete-local',
|
|
49
|
+
'docker-preview', 'docker-build', 'devcontainer-preview', 'devcontainer-write'
|
|
50
|
+
]);
|
|
51
|
+
const absolute = (value, label) => {
|
|
52
|
+
if (typeof value !== 'string' || !path.isAbsolute(value) || /[\0\r\n]/.test(value))
|
|
53
|
+
throw new models_1.ToolingError('INVALID_REQUEST', `${label} must be an absolute local path.`);
|
|
54
|
+
return path.resolve(value);
|
|
55
|
+
};
|
|
56
|
+
const digest = (value) => crypto.createHash('sha256').update(JSON.stringify(value)).digest('hex');
|
|
57
|
+
const isMissing = (error) => error?.code === 'ENOENT';
|
|
58
|
+
function validateDockerImage(value) {
|
|
59
|
+
// Build tags accept a repository and optional tag, never shell text or immutable digests.
|
|
60
|
+
if (typeof value !== 'string' || value.length > 255 || !/^(?:[a-z0-9]+(?:[.-][a-z0-9]+)*(?::[0-9]{1,5})?\/)?[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*(?:\/[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*)*(?::[A-Za-z0-9_][A-Za-z0-9_.-]{0,127})?$/.test(value)) {
|
|
61
|
+
throw new models_1.ToolingError('INVALID_REQUEST', 'Use a valid Docker repository and optional tag, for example mainsequence-app:latest.');
|
|
62
|
+
}
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
function validateDockerPlatform(value) {
|
|
66
|
+
if (value === undefined)
|
|
67
|
+
return 'linux/amd64';
|
|
68
|
+
if (typeof value !== 'string' || !/^linux\/(?:amd64|arm64(?:\/v8)?|arm\/v[5-8]|386|ppc64le|s390x|riscv64)$/.test(value)) {
|
|
69
|
+
throw new models_1.ToolingError('INVALID_REQUEST', 'Select one supported Linux Docker platform, such as linux/amd64 or linux/arm64.');
|
|
70
|
+
}
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
function createProjectOperations(options) {
|
|
74
|
+
const { host, signal } = options;
|
|
75
|
+
async function realDirectory(input, label = 'Project root') {
|
|
76
|
+
const requested = absolute(input, label);
|
|
77
|
+
try {
|
|
78
|
+
const root = await fs.realpath(requested);
|
|
79
|
+
if (!(await fs.stat(root)).isDirectory())
|
|
80
|
+
throw new Error('not a directory');
|
|
81
|
+
return root;
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
throw new models_1.ToolingError('PROJECT_NOT_FOUND', `${label} does not name an accessible directory.`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async function repository(root) {
|
|
88
|
+
const realRoot = await realDirectory(root);
|
|
89
|
+
const top = await fs.realpath(await (0, repositories_1.git)(host, realRoot, ['rev-parse', '--show-toplevel'], signal));
|
|
90
|
+
if (top !== realRoot)
|
|
91
|
+
throw new models_1.ToolingError('INVALID_GIT_CONTEXT', 'Select the repository root rather than a nested directory.');
|
|
92
|
+
const common = await (0, repositories_1.git)(host, realRoot, ['rev-parse', '--git-common-dir'], signal);
|
|
93
|
+
return { root: realRoot, commonDir: await fs.realpath(path.resolve(realRoot, common)) };
|
|
94
|
+
}
|
|
95
|
+
function organization(value) {
|
|
96
|
+
if (typeof value !== 'string' || !value.trim() || /[\0\r\n]/.test(value))
|
|
97
|
+
throw new models_1.ToolingError('INVALID_REQUEST', 'Select the repository organization.');
|
|
98
|
+
return (0, repositories_1.safeSlug)(value);
|
|
99
|
+
}
|
|
100
|
+
async function localContext(request) {
|
|
101
|
+
const base = await realDirectory(request.baseDir, 'Repositories base folder');
|
|
102
|
+
const org = organization(request.organization);
|
|
103
|
+
const parent = path.join(base, org, 'projects');
|
|
104
|
+
const requested = absolute(request.root, 'Local repository');
|
|
105
|
+
const item = await fs.lstat(requested).catch(() => { throw new models_1.ToolingError('PROJECT_NOT_FOUND', 'The local repository was not found.'); });
|
|
106
|
+
if (!item.isDirectory() || item.isSymbolicLink())
|
|
107
|
+
throw new models_1.ToolingError('INVALID_REQUEST', 'Only a real local repository directory can be deleted.');
|
|
108
|
+
const real = await fs.realpath(requested);
|
|
109
|
+
if (path.dirname(real) !== parent || await fs.realpath(parent) !== parent)
|
|
110
|
+
throw new models_1.ToolingError('INVALID_REQUEST', 'The repository must be a direct child of the selected organization projects folder, with no redirected parent.');
|
|
111
|
+
const marker = await fs.lstat(path.join(real, '.git')).catch(() => undefined);
|
|
112
|
+
if (!marker?.isDirectory() || marker.isSymbolicLink())
|
|
113
|
+
throw new models_1.ToolingError('UNSUPPORTED_ACTION', 'Only standalone local Git repositories can be deleted here. Linked worktrees and submodules must be removed through their owning Git workspace.');
|
|
114
|
+
const context = await repository(real);
|
|
115
|
+
if (context.commonDir !== path.join(real, '.git'))
|
|
116
|
+
throw new models_1.ToolingError('INVALID_GIT_CONTEXT', 'The repository Git data is outside the selected local copy.');
|
|
117
|
+
const status = await (0, repositories_1.git)(host, real, ['status', '--porcelain=v1', '--untracked-files=all'], signal);
|
|
118
|
+
const head = await (0, repositories_1.git)(host, real, ['rev-parse', '--verify', 'HEAD'], signal);
|
|
119
|
+
return { ...context, confirmation: digest({ root: real, base, org, dev: item.dev, ino: item.ino, birthtime: item.birthtimeMs, head, status }),
|
|
120
|
+
warning: 'Permanently deletes this local repository, including uncommitted and untracked files. The repository on the platform is retained.', hasLocalChanges: Boolean(status) };
|
|
121
|
+
}
|
|
122
|
+
async function dockerPlan(request) {
|
|
123
|
+
const context = await repository(request.root);
|
|
124
|
+
const file = path.join(context.root, 'Dockerfile');
|
|
125
|
+
const fileInfo = await fs.lstat(file).catch(() => undefined);
|
|
126
|
+
if (!fileInfo?.isFile() || fileInfo.isSymbolicLink())
|
|
127
|
+
throw new models_1.ToolingError('MISSING_PREREQUISITE', 'A regular Dockerfile is required in the repository root.');
|
|
128
|
+
const image = validateDockerImage(request.image ?? `${(0, repositories_1.safeSlug)(path.basename(context.root))}-img:latest`);
|
|
129
|
+
const platform = validateDockerPlatform(request.platform);
|
|
130
|
+
return { ...context, image, platform, executable: 'docker', args: ['buildx', 'build', '--platform', platform, '--load', '-t', image, '-f', 'Dockerfile', '.'] };
|
|
131
|
+
}
|
|
132
|
+
async function devcontainerPlan(request) {
|
|
133
|
+
const context = await repository(request.root);
|
|
134
|
+
const image = validateDockerImage(request.image);
|
|
135
|
+
const directory = path.join(context.root, '.devcontainer');
|
|
136
|
+
const directoryInfo = await fs.lstat(directory).catch(error => { if (isMissing(error))
|
|
137
|
+
return undefined; throw error; });
|
|
138
|
+
if (directoryInfo && (!directoryInfo.isDirectory() || directoryInfo.isSymbolicLink()))
|
|
139
|
+
throw new models_1.ToolingError('INVALID_REQUEST', 'The .devcontainer path must be a real directory inside the repository.');
|
|
140
|
+
const target = path.join(directory, 'devcontainer.json');
|
|
141
|
+
const info = await fs.lstat(target).catch(error => { if (isMissing(error))
|
|
142
|
+
return undefined; throw error; });
|
|
143
|
+
if (info && (!info.isFile() || info.isSymbolicLink() || info.size > 2_000_000))
|
|
144
|
+
throw new models_1.ToolingError('INVALID_REQUEST', 'The devcontainer configuration must be a regular JSONC file under 2 MB.');
|
|
145
|
+
const before = info ? await fs.readFile(target, 'utf8') : '';
|
|
146
|
+
const after = (0, devcontainerJsonc_1.updateDevcontainerJsonc)(before, image);
|
|
147
|
+
return { ...context, path: target, before, after, expectedDigest: digest({ root: context.root, image, exists: Boolean(info), before }), changed: before !== after };
|
|
148
|
+
}
|
|
149
|
+
async function dispatch(request) {
|
|
150
|
+
const action = request?.action || 'unknown';
|
|
151
|
+
let mutationStarted = false;
|
|
152
|
+
try {
|
|
153
|
+
if (!exports.PROJECT_OPERATION_ACTIONS.has(action))
|
|
154
|
+
throw new models_1.ToolingError('INVALID_REQUEST', 'Unknown project operation.');
|
|
155
|
+
signal?.throwIfAborted();
|
|
156
|
+
let data;
|
|
157
|
+
if (request.action === 'local-repositories') {
|
|
158
|
+
const requestedBase = absolute(request.baseDir, 'Repositories base folder');
|
|
159
|
+
const baseExists = await fs.stat(requestedBase).then(() => true).catch(error => { if (isMissing(error))
|
|
160
|
+
return false; throw error; });
|
|
161
|
+
const base = baseExists ? await realDirectory(requestedBase, 'Repositories base folder') : requestedBase;
|
|
162
|
+
const org = organization(request.organization), organizationRoot = path.join(base, org, 'projects');
|
|
163
|
+
if (request.repositories !== undefined && (!Array.isArray(request.repositories) || request.repositories.some(item => !item || typeof item.uid !== 'string' || !/^[A-Za-z0-9._-]+$/.test(item.uid)))) {
|
|
164
|
+
throw new models_1.ToolingError('INVALID_REQUEST', 'Local discovery repository identities must be a list of valid UIDs.');
|
|
165
|
+
}
|
|
166
|
+
const parentInfo = await fs.lstat(organizationRoot).catch(error => { if (isMissing(error))
|
|
167
|
+
return undefined; throw error; });
|
|
168
|
+
if (parentInfo && (!parentInfo.isDirectory() || parentInfo.isSymbolicLink() || await fs.realpath(organizationRoot) !== organizationRoot))
|
|
169
|
+
throw new models_1.ToolingError('INVALID_REQUEST', 'The organization projects folder is redirected outside its canonical location.');
|
|
170
|
+
const entries = await fs.readdir(organizationRoot, { withFileTypes: true }).catch(error => { if (isMissing(error))
|
|
171
|
+
return []; throw error; });
|
|
172
|
+
const items = [];
|
|
173
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
174
|
+
if (!entry.isDirectory() || entry.isSymbolicLink() || entry.name.startsWith('.'))
|
|
175
|
+
continue;
|
|
176
|
+
const root = path.join(organizationRoot, entry.name);
|
|
177
|
+
const match = request.repositories?.filter(item => entry.name.endsWith(`-${item.uid}`)).sort((a, b) => b.uid.length - a.uid.length)[0];
|
|
178
|
+
items.push({ name: match?.name ?? entry.name, path: root, ...(match ? { repositoryUid: match.uid } : {}), isGitRepository: await fs.lstat(path.join(root, '.git')).then(() => true).catch(() => false) });
|
|
179
|
+
}
|
|
180
|
+
data = { baseDir: base, organization: org, organizationRoot, items };
|
|
181
|
+
}
|
|
182
|
+
else if (request.action === 'delete-local-preview' || request.action === 'delete-local') {
|
|
183
|
+
const preview = await localContext(request);
|
|
184
|
+
if (request.action === 'delete-local-preview')
|
|
185
|
+
data = preview;
|
|
186
|
+
else {
|
|
187
|
+
if (typeof request.confirmation !== 'string' || request.confirmation !== preview.confirmation)
|
|
188
|
+
throw new models_1.ToolingError('INVALID_REQUEST', 'The local repository changed or deletion was not previewed. Review a fresh deletion preview.');
|
|
189
|
+
let deleted = false;
|
|
190
|
+
try {
|
|
191
|
+
await (0, projects_1.withProjectLock)(preview.commonDir, async () => {
|
|
192
|
+
const current = await localContext(request);
|
|
193
|
+
if (current.confirmation !== request.confirmation)
|
|
194
|
+
throw new models_1.ToolingError('INVALID_REQUEST', 'The local repository changed after preview. Review it again before deleting.');
|
|
195
|
+
signal?.throwIfAborted();
|
|
196
|
+
mutationStarted = true;
|
|
197
|
+
await fs.rm(current.root, { recursive: true, force: false });
|
|
198
|
+
deleted = true;
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
// This action intentionally removes .git and its shared lock together.
|
|
203
|
+
if (!deleted || !isMissing(error) || await fs.lstat(preview.root).then(() => true).catch(() => false))
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
206
|
+
data = { root: preview.root, deleted: true, remoteRepositoryRetained: true };
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
else if (request.action === 'docker-preview' || request.action === 'docker-build') {
|
|
210
|
+
const plan = await dockerPlan(request);
|
|
211
|
+
if (request.action === 'docker-build') {
|
|
212
|
+
await (0, projects_1.withProjectLock)(plan.commonDir, async () => {
|
|
213
|
+
const current = await dockerPlan(request);
|
|
214
|
+
if (current.root !== plan.root || current.commonDir !== plan.commonDir)
|
|
215
|
+
throw new models_1.ToolingError('INVALID_GIT_CONTEXT', 'The selected repository changed before Docker execution.');
|
|
216
|
+
signal?.throwIfAborted();
|
|
217
|
+
mutationStarted = true;
|
|
218
|
+
host.progress?.(`Building ${plan.image} for ${plan.platform}.`);
|
|
219
|
+
const result = await host.runProcess({ executable: plan.executable, args: plan.args, cwd: plan.root, env: options.env, signal, timeoutMs: 1_800_000 });
|
|
220
|
+
if (result.cancelled || signal?.aborted)
|
|
221
|
+
throw new models_1.ToolingError('CANCELLED', 'Docker build was cancelled. Partial build cache may remain.');
|
|
222
|
+
if (result.timedOut)
|
|
223
|
+
throw new models_1.ToolingError('PROCESS_TIMEOUT', 'Docker build timed out. Partial build cache may remain.');
|
|
224
|
+
if (result.unavailable)
|
|
225
|
+
throw new models_1.ToolingError('MISSING_PREREQUISITE', 'Docker could not be started. Install and start Docker before building.');
|
|
226
|
+
if (result.exitCode !== 0)
|
|
227
|
+
throw new models_1.ToolingError('PROCESS_FAILED', `Docker build failed (exit ${result.exitCode ?? 'unknown'}). No container was started.`);
|
|
228
|
+
host.progress?.('Docker image build completed.');
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
const { commonDir: _commonDir, ...publicPlan } = plan;
|
|
232
|
+
data = { ...publicPlan, built: request.action === 'docker-build' };
|
|
233
|
+
}
|
|
234
|
+
else if (request.action === 'devcontainer-preview' || request.action === 'devcontainer-write') {
|
|
235
|
+
const plan = await devcontainerPlan(request);
|
|
236
|
+
if (request.action === 'devcontainer-preview') {
|
|
237
|
+
const { commonDir: _commonDir, ...publicPlan } = plan;
|
|
238
|
+
data = publicPlan;
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
if (request.expectedDigest !== plan.expectedDigest)
|
|
242
|
+
throw new models_1.ToolingError('INVALID_REQUEST', 'The devcontainer configuration changed or was not previewed. Review a fresh preview before saving.');
|
|
243
|
+
await (0, projects_1.withProjectLock)(plan.commonDir, async () => {
|
|
244
|
+
const current = await devcontainerPlan(request);
|
|
245
|
+
if (current.expectedDigest !== request.expectedDigest)
|
|
246
|
+
throw new models_1.ToolingError('INVALID_REQUEST', 'The devcontainer configuration changed after preview. No file was changed.');
|
|
247
|
+
if (!current.changed)
|
|
248
|
+
return;
|
|
249
|
+
signal?.throwIfAborted();
|
|
250
|
+
mutationStarted = true;
|
|
251
|
+
await fs.mkdir(path.dirname(current.path), { recursive: true });
|
|
252
|
+
const temporary = `${current.path}.${crypto.randomUUID()}.tmp`;
|
|
253
|
+
try {
|
|
254
|
+
await fs.writeFile(temporary, current.after, { encoding: 'utf8', flag: 'wx', mode: 0o600 });
|
|
255
|
+
signal?.throwIfAborted();
|
|
256
|
+
await fs.rename(temporary, current.path);
|
|
257
|
+
}
|
|
258
|
+
finally {
|
|
259
|
+
await fs.unlink(temporary).catch(error => { if (!isMissing(error))
|
|
260
|
+
throw error; });
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
data = { path: plan.path, changed: plan.changed };
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return sanitized({ schemaVersion: 1, action, status: 'success', summary: `${action} completed.`, data });
|
|
267
|
+
}
|
|
268
|
+
catch (error) {
|
|
269
|
+
const failure = signal?.aborted ? new models_1.ToolingError('CANCELLED', 'The project operation was cancelled.') : error instanceof models_1.ToolingError ? error : new models_1.ToolingError('INTERNAL_ERROR', 'The project operation could not complete. Check the selected paths and local tool permissions.');
|
|
270
|
+
return sanitized({ schemaVersion: 1, action, status: failure.code === 'CANCELLED' ? 'cancelled' : 'failure', summary: failure.message, error: { code: failure.code, message: failure.message },
|
|
271
|
+
...(mutationStarted ? { effects: { state: 'partial-or-uncertain', message: 'Local files or Docker state may have changed. Inspect the project before retrying.' } } : {}) });
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
function sanitized(result) {
|
|
275
|
+
const walk = (value) => typeof value === 'string' ? options.sanitize?.(value) ?? value : Array.isArray(value) ? value.map(walk) : value && typeof value === 'object' ? Object.fromEntries(Object.entries(value).map(([key, item]) => [key, walk(item)])) : value;
|
|
276
|
+
return walk(result);
|
|
277
|
+
}
|
|
278
|
+
return { dispatch };
|
|
279
|
+
}
|