pmx-canvas 0.1.0 → 0.1.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/CHANGELOG.md +87 -0
- package/dist/canvas/index.js +53 -53
- package/dist/types/client/nodes/ExtAppFrame.d.ts +1 -1
- package/dist/types/server/bundled-skills.d.ts +40 -0
- package/dist/types/shared/ext-app-tool-result.d.ts +12 -0
- package/package.json +1 -1
- package/src/cli/index.ts +19 -1
- package/src/client/nodes/ExtAppFrame.tsx +91 -17
- package/src/mcp/server.ts +64 -1
- package/src/server/bundled-skills.ts +143 -0
- package/src/server/canvas-operations.ts +22 -3
- package/src/server/server.ts +20 -13
- package/src/server/web-artifacts.ts +40 -2
- package/src/shared/ext-app-tool-result.ts +25 -0
|
@@ -163,6 +163,12 @@ async function runProcess(
|
|
|
163
163
|
]).has(key);
|
|
164
164
|
}),
|
|
165
165
|
);
|
|
166
|
+
// Spawn in its own process group (POSIX only — Windows has a different model).
|
|
167
|
+
// This lets us kill every descendant — pnpm, bun, parcel, swc, lmdb, etc. —
|
|
168
|
+
// if the build hangs or times out, instead of leaving orphans that accumulate
|
|
169
|
+
// file descriptors and processes across retries (seen as later
|
|
170
|
+
// `fork: Resource temporarily unavailable` in end-user reports).
|
|
171
|
+
const isPosix = process.platform !== 'win32';
|
|
166
172
|
const child = spawn(command, args, {
|
|
167
173
|
cwd: options.cwd,
|
|
168
174
|
env: {
|
|
@@ -171,10 +177,33 @@ async function runProcess(
|
|
|
171
177
|
CI: '1',
|
|
172
178
|
npm_config_yes: 'true',
|
|
173
179
|
pnpm_config_yes: 'true',
|
|
180
|
+
// Cap pnpm's internal child concurrency so installs don't blow past
|
|
181
|
+
// macOS default ulimit -u (often 256-2048) when resolving the ~30
|
|
182
|
+
// radix-ui dependencies in one `pnpm add` call.
|
|
183
|
+
pnpm_config_child_concurrency: '2',
|
|
184
|
+
NPM_CONFIG_CHILD_CONCURRENCY: '2',
|
|
174
185
|
},
|
|
175
186
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
187
|
+
detached: isPosix,
|
|
176
188
|
});
|
|
177
189
|
|
|
190
|
+
const killTree = (signal: NodeJS.Signals): void => {
|
|
191
|
+
if (isPosix && typeof child.pid === 'number') {
|
|
192
|
+
try {
|
|
193
|
+
// Negative pid = send to the whole process group.
|
|
194
|
+
process.kill(-child.pid, signal);
|
|
195
|
+
return;
|
|
196
|
+
} catch {
|
|
197
|
+
// fall through to direct kill
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
try {
|
|
201
|
+
child.kill(signal);
|
|
202
|
+
} catch {
|
|
203
|
+
// ignore
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
|
|
178
207
|
let stdout = '';
|
|
179
208
|
let stderr = '';
|
|
180
209
|
let timedOut = false;
|
|
@@ -189,12 +218,13 @@ async function runProcess(
|
|
|
189
218
|
await new Promise<void>((resolvePromise, rejectPromise) => {
|
|
190
219
|
const timer = setTimeout(() => {
|
|
191
220
|
timedOut = true;
|
|
192
|
-
|
|
221
|
+
killTree('SIGKILL');
|
|
193
222
|
rejectPromise(new Error(`Command timed out after ${options.timeoutMs}ms: ${command}`));
|
|
194
223
|
}, options.timeoutMs);
|
|
195
224
|
|
|
196
225
|
child.on('error', (error) => {
|
|
197
226
|
clearTimeout(timer);
|
|
227
|
+
killTree('SIGKILL');
|
|
198
228
|
rejectPromise(error);
|
|
199
229
|
});
|
|
200
230
|
|
|
@@ -202,9 +232,17 @@ async function runProcess(
|
|
|
202
232
|
clearTimeout(timer);
|
|
203
233
|
if (timedOut) return;
|
|
204
234
|
if (code !== 0) {
|
|
235
|
+
const trimmedStderr = stderr.trim();
|
|
236
|
+
const stderrTail = trimmedStderr.split('\n').slice(-20).join('\n');
|
|
237
|
+
const trimmedStdout = stdout.trim();
|
|
238
|
+
const stdoutTail = trimmedStdout.split('\n').slice(-20).join('\n');
|
|
205
239
|
rejectPromise(
|
|
206
240
|
new Error(
|
|
207
|
-
[
|
|
241
|
+
[
|
|
242
|
+
`Command failed (${code}): ${command} ${args.join(' ')}`,
|
|
243
|
+
stderrTail && `stderr:\n${stderrTail}`,
|
|
244
|
+
!trimmedStderr && stdoutTail && `stdout:\n${stdoutTail}`,
|
|
245
|
+
]
|
|
208
246
|
.filter(Boolean)
|
|
209
247
|
.join('\n'),
|
|
210
248
|
),
|
|
@@ -74,3 +74,28 @@ export function normalizeExtAppToolResult(
|
|
|
74
74
|
isError,
|
|
75
75
|
};
|
|
76
76
|
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Structural equality between two `CallToolResult` values, used by the host
|
|
80
|
+
* ExtAppFrame to suppress echo-back re-renders when an SSE layout update
|
|
81
|
+
* mints a new object reference for an otherwise-unchanged tool result.
|
|
82
|
+
*
|
|
83
|
+
* JSON-stringify is adequate here: tool results are strictly JSON (no
|
|
84
|
+
* functions, symbols, or cycles), typically small, and on the hot path we
|
|
85
|
+
* only hit this when references already differ. For very large payloads
|
|
86
|
+
* (> ~2MB) an early length check skips the stringify to avoid a user-visible
|
|
87
|
+
* stall — such results are treated as "changed" and forwarded to the widget.
|
|
88
|
+
*/
|
|
89
|
+
export function extAppToolResultsMatch(a: CallToolResult, b: CallToolResult): boolean {
|
|
90
|
+
if (a === b) return true;
|
|
91
|
+
if (a.isError !== b.isError) return false;
|
|
92
|
+
try {
|
|
93
|
+
const sa = JSON.stringify(a);
|
|
94
|
+
const sb = JSON.stringify(b);
|
|
95
|
+
if (sa === undefined || sb === undefined) return false;
|
|
96
|
+
if (Math.abs(sa.length - sb.length) > 2_000_000) return false;
|
|
97
|
+
return sa === sb;
|
|
98
|
+
} catch {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
}
|