@perstack/runtime 0.0.68 → 0.0.70
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/dist/bin/cli.js +1 -1
- package/dist/{chunk-7QLRRSNX.js → chunk-3RWT2GPO.js} +294 -194
- package/dist/chunk-3RWT2GPO.js.map +1 -0
- package/dist/src/index.js +2 -2
- package/package.json +18 -17
- package/dist/chunk-7QLRRSNX.js.map +0 -1
package/dist/bin/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { package_default, run } from '../chunk-
|
|
2
|
+
import { package_default, run } from '../chunk-3RWT2GPO.js';
|
|
3
3
|
import { parseWithFriendlyError, runCommandInputSchema, perstackConfigSchema } from '@perstack/core';
|
|
4
4
|
import { Command } from 'commander';
|
|
5
5
|
import dotenv from 'dotenv';
|
|
@@ -11,10 +11,10 @@ import { ProxyAgent, fetch } from 'undici';
|
|
|
11
11
|
import { setup, assign, createActor } from 'xstate';
|
|
12
12
|
import { generateText, tool, jsonSchema } from 'ai';
|
|
13
13
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
14
|
-
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
15
|
-
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
16
14
|
import { McpError } from '@modelcontextprotocol/sdk/types.js';
|
|
17
15
|
import { createId } from '@paralleldrive/cuid2';
|
|
16
|
+
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
17
|
+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
18
18
|
import { readFile } from 'fs/promises';
|
|
19
19
|
import { dedent } from 'ts-dedent';
|
|
20
20
|
import { ApiV1Client } from '@perstack/api-client/v1';
|
|
@@ -22,7 +22,7 @@ import { ApiV1Client } from '@perstack/api-client/v1';
|
|
|
22
22
|
// package.json
|
|
23
23
|
var package_default = {
|
|
24
24
|
name: "@perstack/runtime",
|
|
25
|
-
version: "0.0.
|
|
25
|
+
version: "0.0.70",
|
|
26
26
|
description: "Perstack Runtime",
|
|
27
27
|
author: "Wintermute Technologies, Inc.",
|
|
28
28
|
license: "Apache-2.0",
|
|
@@ -54,32 +54,33 @@ var package_default = {
|
|
|
54
54
|
typecheck: "tsc --noEmit"
|
|
55
55
|
},
|
|
56
56
|
dependencies: {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
"
|
|
60
|
-
"@ai-sdk/
|
|
61
|
-
"@ai-sdk/
|
|
62
|
-
"@ai-sdk/
|
|
63
|
-
"@ai-sdk/
|
|
64
|
-
"@
|
|
65
|
-
"@ai-sdk/google-vertex": "^3.0.24",
|
|
66
|
-
"@ai-sdk/openai": "^2.0.75",
|
|
67
|
-
"@modelcontextprotocol/sdk": "^1.24.0",
|
|
57
|
+
"@ai-sdk/amazon-bedrock": "^3.0.71",
|
|
58
|
+
"@ai-sdk/anthropic": "^2.0.56",
|
|
59
|
+
"@ai-sdk/azure": "^2.0.90",
|
|
60
|
+
"@ai-sdk/deepseek": "^1.0.32",
|
|
61
|
+
"@ai-sdk/google": "^2.0.49",
|
|
62
|
+
"@ai-sdk/google-vertex": "^3.0.94",
|
|
63
|
+
"@ai-sdk/openai": "^2.0.88",
|
|
64
|
+
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
68
65
|
"@paralleldrive/cuid2": "^3.0.4",
|
|
69
66
|
"@perstack/api-client": "workspace:*",
|
|
70
67
|
"@perstack/core": "workspace:*",
|
|
71
|
-
ai: "^5.0.
|
|
68
|
+
ai: "^5.0.115",
|
|
69
|
+
commander: "^14.0.2",
|
|
70
|
+
dotenv: "^17.2.3",
|
|
72
71
|
"ollama-ai-provider-v2": "^1.5.5",
|
|
72
|
+
"smol-toml": "^1.5.2",
|
|
73
73
|
"ts-dedent": "^2.2.0",
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
undici: "^7.16.0",
|
|
75
|
+
xstate: "^5.25.0"
|
|
76
76
|
},
|
|
77
77
|
devDependencies: {
|
|
78
78
|
"@tsconfig/node22": "^22.0.5",
|
|
79
|
-
"@types/node": "^
|
|
79
|
+
"@types/node": "^25.0.3",
|
|
80
|
+
memfs: "^4.51.1",
|
|
80
81
|
tsup: "^8.5.1",
|
|
81
82
|
typescript: "^5.9.3",
|
|
82
|
-
vitest: "^4.0.
|
|
83
|
+
vitest: "^4.0.16"
|
|
83
84
|
},
|
|
84
85
|
engines: {
|
|
85
86
|
node: ">=22.0.0"
|
|
@@ -260,11 +261,11 @@ var BaseSkillManager = class {
|
|
|
260
261
|
this._initializing = void 0;
|
|
261
262
|
}
|
|
262
263
|
async getToolDefinitions() {
|
|
263
|
-
if (!this.isInitialized() &&
|
|
264
|
-
|
|
264
|
+
if (!this.isInitialized() && this._initializing) {
|
|
265
|
+
await this._initializing;
|
|
265
266
|
}
|
|
266
|
-
if (!this.isInitialized()
|
|
267
|
-
|
|
267
|
+
if (!this.isInitialized()) {
|
|
268
|
+
throw new Error(`Skill ${this.name} is not initialized`);
|
|
268
269
|
}
|
|
269
270
|
return this._filterTools(this._toolDefinitions);
|
|
270
271
|
}
|
|
@@ -273,6 +274,22 @@ var BaseSkillManager = class {
|
|
|
273
274
|
}
|
|
274
275
|
};
|
|
275
276
|
|
|
277
|
+
// src/skill-manager/command-args.ts
|
|
278
|
+
function getCommandArgs(skill) {
|
|
279
|
+
const { name, command, packageName, args } = skill;
|
|
280
|
+
if (!packageName && (!args || args.length === 0)) {
|
|
281
|
+
throw new Error(`Skill ${name} has no packageName or args. Please provide one of them.`);
|
|
282
|
+
}
|
|
283
|
+
if (packageName && args && args.length > 0) {
|
|
284
|
+
throw new Error(`Skill ${name} has both packageName and args. Please provide only one of them.`);
|
|
285
|
+
}
|
|
286
|
+
let newArgs = args && args.length > 0 ? args : [packageName];
|
|
287
|
+
if (command === "npx" && !newArgs.includes("-y")) {
|
|
288
|
+
newArgs = ["-y", ...newArgs];
|
|
289
|
+
}
|
|
290
|
+
return { command, args: newArgs };
|
|
291
|
+
}
|
|
292
|
+
|
|
276
293
|
// src/skill-manager/delegate.ts
|
|
277
294
|
var DelegateSkillManager = class extends BaseSkillManager {
|
|
278
295
|
name;
|
|
@@ -334,6 +351,114 @@ var InteractiveSkillManager = class extends BaseSkillManager {
|
|
|
334
351
|
return [];
|
|
335
352
|
}
|
|
336
353
|
};
|
|
354
|
+
|
|
355
|
+
// src/skill-manager/ip-validator.ts
|
|
356
|
+
function isPrivateOrLocalIP(hostname) {
|
|
357
|
+
if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "0.0.0.0") {
|
|
358
|
+
return true;
|
|
359
|
+
}
|
|
360
|
+
const ipv4Match = hostname.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/);
|
|
361
|
+
if (ipv4Match) {
|
|
362
|
+
const [, a, b] = ipv4Match.map(Number);
|
|
363
|
+
if (a === 10) return true;
|
|
364
|
+
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
365
|
+
if (a === 192 && b === 168) return true;
|
|
366
|
+
if (a === 169 && b === 254) return true;
|
|
367
|
+
if (a === 127) return true;
|
|
368
|
+
}
|
|
369
|
+
if (hostname.includes(":")) {
|
|
370
|
+
if (hostname.startsWith("fe80:")) return true;
|
|
371
|
+
if (hostname.startsWith("fc") || hostname.startsWith("fd")) return true;
|
|
372
|
+
}
|
|
373
|
+
if (hostname.startsWith("::ffff:")) {
|
|
374
|
+
const ipv4Part = hostname.slice(7);
|
|
375
|
+
if (isPrivateOrLocalIP(ipv4Part)) {
|
|
376
|
+
return true;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
381
|
+
function handleToolError(error, toolName, McpErrorClass) {
|
|
382
|
+
if (error instanceof McpErrorClass) {
|
|
383
|
+
return [
|
|
384
|
+
{
|
|
385
|
+
type: "textPart",
|
|
386
|
+
text: `Error calling tool ${toolName}: ${error.message}`,
|
|
387
|
+
id: createId()
|
|
388
|
+
}
|
|
389
|
+
];
|
|
390
|
+
}
|
|
391
|
+
throw error;
|
|
392
|
+
}
|
|
393
|
+
function convertToolResult(result, toolName, input) {
|
|
394
|
+
if (!result.content || result.content.length === 0) {
|
|
395
|
+
return [
|
|
396
|
+
{
|
|
397
|
+
type: "textPart",
|
|
398
|
+
text: `Tool ${toolName} returned nothing with arguments: ${JSON.stringify(input)}`,
|
|
399
|
+
id: createId()
|
|
400
|
+
}
|
|
401
|
+
];
|
|
402
|
+
}
|
|
403
|
+
return result.content.filter((part) => part.type !== "audio" && part.type !== "resource_link").map((part) => convertPart(part));
|
|
404
|
+
}
|
|
405
|
+
function convertPart(part) {
|
|
406
|
+
switch (part.type) {
|
|
407
|
+
case "text":
|
|
408
|
+
if (!part.text || part.text === "") {
|
|
409
|
+
return { type: "textPart", text: "Error: No content", id: createId() };
|
|
410
|
+
}
|
|
411
|
+
return { type: "textPart", text: part.text, id: createId() };
|
|
412
|
+
case "image":
|
|
413
|
+
if (!part.data || !part.mimeType) {
|
|
414
|
+
throw new Error("Image part must have both data and mimeType");
|
|
415
|
+
}
|
|
416
|
+
return {
|
|
417
|
+
type: "imageInlinePart",
|
|
418
|
+
encodedData: part.data,
|
|
419
|
+
mimeType: part.mimeType,
|
|
420
|
+
id: createId()
|
|
421
|
+
};
|
|
422
|
+
case "resource":
|
|
423
|
+
if (!part.resource) {
|
|
424
|
+
throw new Error("Resource part must have resource content");
|
|
425
|
+
}
|
|
426
|
+
return convertResource(part.resource);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
function convertResource(resource) {
|
|
430
|
+
if (!resource.mimeType) {
|
|
431
|
+
throw new Error(`Resource ${JSON.stringify(resource)} has no mimeType`);
|
|
432
|
+
}
|
|
433
|
+
if (resource.text && typeof resource.text === "string") {
|
|
434
|
+
return { type: "textPart", text: resource.text, id: createId() };
|
|
435
|
+
}
|
|
436
|
+
if (resource.blob && typeof resource.blob === "string") {
|
|
437
|
+
return {
|
|
438
|
+
type: "fileInlinePart",
|
|
439
|
+
encodedData: resource.blob,
|
|
440
|
+
mimeType: resource.mimeType,
|
|
441
|
+
id: createId()
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
throw new Error(`Unsupported resource type: ${JSON.stringify(resource)}`);
|
|
445
|
+
}
|
|
446
|
+
var DefaultTransportFactory = class {
|
|
447
|
+
createStdio(options) {
|
|
448
|
+
return new StdioClientTransport({
|
|
449
|
+
command: options.command,
|
|
450
|
+
args: options.args,
|
|
451
|
+
env: options.env,
|
|
452
|
+
stderr: options.stderr
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
createSse(options) {
|
|
456
|
+
return new SSEClientTransport(options.url);
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
var defaultTransportFactory = new DefaultTransportFactory();
|
|
460
|
+
|
|
461
|
+
// src/skill-manager/mcp.ts
|
|
337
462
|
var McpSkillManager = class extends BaseSkillManager {
|
|
338
463
|
name;
|
|
339
464
|
type = "mcp";
|
|
@@ -341,11 +466,13 @@ var McpSkillManager = class extends BaseSkillManager {
|
|
|
341
466
|
skill;
|
|
342
467
|
_mcpClient;
|
|
343
468
|
_env;
|
|
344
|
-
|
|
469
|
+
_transportFactory;
|
|
470
|
+
constructor(skill, env, jobId, runId, eventListener, options) {
|
|
345
471
|
super(jobId, runId, eventListener);
|
|
346
472
|
this.name = skill.name;
|
|
347
473
|
this.skill = skill;
|
|
348
474
|
this._env = env;
|
|
475
|
+
this._transportFactory = options?.transportFactory ?? defaultTransportFactory;
|
|
349
476
|
this.lazyInit = skill.type === "mcpStdioSkill" && skill.lazyInit && skill.name !== "@perstack/base";
|
|
350
477
|
}
|
|
351
478
|
async _doInit() {
|
|
@@ -396,7 +523,7 @@ var McpSkillManager = class extends BaseSkillManager {
|
|
|
396
523
|
}
|
|
397
524
|
const env = getFilteredEnv(requiredEnv);
|
|
398
525
|
const startTime = Date.now();
|
|
399
|
-
const { command, args } =
|
|
526
|
+
const { command, args } = getCommandArgs(skill);
|
|
400
527
|
if (this._eventListener) {
|
|
401
528
|
const event = createRuntimeEvent("skillStarting", this._jobId, this._runId, {
|
|
402
529
|
skillName: skill.name,
|
|
@@ -405,7 +532,7 @@ var McpSkillManager = class extends BaseSkillManager {
|
|
|
405
532
|
});
|
|
406
533
|
this._eventListener(event);
|
|
407
534
|
}
|
|
408
|
-
const transport =
|
|
535
|
+
const transport = this._transportFactory.createStdio({ command, args, env, stderr: "pipe" });
|
|
409
536
|
const spawnDurationMs = Date.now() - startTime;
|
|
410
537
|
if (transport.stderr) {
|
|
411
538
|
transport.stderr.on("data", (chunk) => {
|
|
@@ -437,56 +564,14 @@ var McpSkillManager = class extends BaseSkillManager {
|
|
|
437
564
|
if (url.protocol !== "https:") {
|
|
438
565
|
throw new Error(`Skill ${skill.name} SSE endpoint must use HTTPS: ${skill.endpoint}`);
|
|
439
566
|
}
|
|
440
|
-
if (
|
|
567
|
+
if (isPrivateOrLocalIP(url.hostname)) {
|
|
441
568
|
throw new Error(
|
|
442
569
|
`Skill ${skill.name} SSE endpoint cannot use private/local IP: ${skill.endpoint}`
|
|
443
570
|
);
|
|
444
571
|
}
|
|
445
|
-
const transport =
|
|
572
|
+
const transport = this._transportFactory.createSse({ url });
|
|
446
573
|
await this._mcpClient.connect(transport);
|
|
447
574
|
}
|
|
448
|
-
_isPrivateOrLocalIP(hostname) {
|
|
449
|
-
if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "0.0.0.0") {
|
|
450
|
-
return true;
|
|
451
|
-
}
|
|
452
|
-
const ipv4Match = hostname.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/);
|
|
453
|
-
if (ipv4Match) {
|
|
454
|
-
const [, a, b] = ipv4Match.map(Number);
|
|
455
|
-
if (a === 10) return true;
|
|
456
|
-
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
457
|
-
if (a === 192 && b === 168) return true;
|
|
458
|
-
if (a === 169 && b === 254) return true;
|
|
459
|
-
if (a === 127) return true;
|
|
460
|
-
}
|
|
461
|
-
if (hostname.includes(":")) {
|
|
462
|
-
if (hostname.startsWith("fe80:") || hostname.startsWith("fc") || hostname.startsWith("fd")) {
|
|
463
|
-
return true;
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
if (hostname.startsWith("::ffff:")) {
|
|
467
|
-
const ipv4Part = hostname.slice(7);
|
|
468
|
-
if (this._isPrivateOrLocalIP(ipv4Part)) {
|
|
469
|
-
return true;
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
return false;
|
|
473
|
-
}
|
|
474
|
-
_getCommandArgs(skill) {
|
|
475
|
-
const { name, command, packageName, args } = skill;
|
|
476
|
-
if (!packageName && (!args || args.length === 0)) {
|
|
477
|
-
throw new Error(`Skill ${name} has no packageName or args. Please provide one of them.`);
|
|
478
|
-
}
|
|
479
|
-
if (packageName && args && args.length > 0) {
|
|
480
|
-
throw new Error(
|
|
481
|
-
`Skill ${name} has both packageName and args. Please provide only one of them.`
|
|
482
|
-
);
|
|
483
|
-
}
|
|
484
|
-
let newArgs = args && args.length > 0 ? args : [packageName];
|
|
485
|
-
if (command === "npx" && !newArgs.includes("-y")) {
|
|
486
|
-
newArgs = ["-y", ...newArgs];
|
|
487
|
-
}
|
|
488
|
-
return { command, args: newArgs };
|
|
489
|
-
}
|
|
490
575
|
async close() {
|
|
491
576
|
if (this._mcpClient) {
|
|
492
577
|
await this._mcpClient.close();
|
|
@@ -512,77 +597,33 @@ var McpSkillManager = class extends BaseSkillManager {
|
|
|
512
597
|
name: toolName,
|
|
513
598
|
arguments: input
|
|
514
599
|
});
|
|
515
|
-
return
|
|
600
|
+
return convertToolResult(result, toolName, input);
|
|
516
601
|
} catch (error) {
|
|
517
|
-
return
|
|
602
|
+
return handleToolError(error, toolName, McpError);
|
|
518
603
|
}
|
|
519
604
|
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
605
|
+
};
|
|
606
|
+
|
|
607
|
+
// src/skill-manager/skill-manager-factory.ts
|
|
608
|
+
var DefaultSkillManagerFactory = class {
|
|
609
|
+
createMcp(skill, context) {
|
|
610
|
+
return new McpSkillManager(
|
|
611
|
+
skill,
|
|
612
|
+
context.env,
|
|
613
|
+
context.jobId,
|
|
614
|
+
context.runId,
|
|
615
|
+
context.eventListener,
|
|
616
|
+
context.mcpOptions
|
|
617
|
+
);
|
|
531
618
|
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
return [
|
|
535
|
-
{
|
|
536
|
-
type: "textPart",
|
|
537
|
-
text: `Tool ${toolName} returned nothing with arguments: ${JSON.stringify(input)}`,
|
|
538
|
-
id: createId()
|
|
539
|
-
}
|
|
540
|
-
];
|
|
541
|
-
}
|
|
542
|
-
return result.content.filter((part) => part.type !== "audio" && part.type !== "resource_link").map((part) => this._convertPart(part));
|
|
619
|
+
createInteractive(skill, context) {
|
|
620
|
+
return new InteractiveSkillManager(skill, context.jobId, context.runId, context.eventListener);
|
|
543
621
|
}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
case "text":
|
|
547
|
-
if (!part.text || part.text === "") {
|
|
548
|
-
return { type: "textPart", text: "Error: No content", id: createId() };
|
|
549
|
-
}
|
|
550
|
-
return { type: "textPart", text: part.text, id: createId() };
|
|
551
|
-
case "image":
|
|
552
|
-
if (!part.data || !part.mimeType) {
|
|
553
|
-
throw new Error("Image part must have both data and mimeType");
|
|
554
|
-
}
|
|
555
|
-
return {
|
|
556
|
-
type: "imageInlinePart",
|
|
557
|
-
encodedData: part.data,
|
|
558
|
-
mimeType: part.mimeType,
|
|
559
|
-
id: createId()
|
|
560
|
-
};
|
|
561
|
-
case "resource":
|
|
562
|
-
if (!part.resource) {
|
|
563
|
-
throw new Error("Resource part must have resource content");
|
|
564
|
-
}
|
|
565
|
-
return this._convertResource(part.resource);
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
_convertResource(resource) {
|
|
569
|
-
if (!resource.mimeType) {
|
|
570
|
-
throw new Error(`Resource ${JSON.stringify(resource)} has no mimeType`);
|
|
571
|
-
}
|
|
572
|
-
if (resource.text && typeof resource.text === "string") {
|
|
573
|
-
return { type: "textPart", text: resource.text, id: createId() };
|
|
574
|
-
}
|
|
575
|
-
if (resource.blob && typeof resource.blob === "string") {
|
|
576
|
-
return {
|
|
577
|
-
type: "fileInlinePart",
|
|
578
|
-
encodedData: resource.blob,
|
|
579
|
-
mimeType: resource.mimeType,
|
|
580
|
-
id: createId()
|
|
581
|
-
};
|
|
582
|
-
}
|
|
583
|
-
throw new Error(`Unsupported resource type: ${JSON.stringify(resource)}`);
|
|
622
|
+
createDelegate(expert, context) {
|
|
623
|
+
return new DelegateSkillManager(expert, context.jobId, context.runId, context.eventListener);
|
|
584
624
|
}
|
|
585
625
|
};
|
|
626
|
+
var defaultSkillManagerFactory = new DefaultSkillManagerFactory();
|
|
586
627
|
|
|
587
628
|
// src/skill-manager/helpers.ts
|
|
588
629
|
async function initSkillManagersWithCleanup(managers, allManagers) {
|
|
@@ -597,11 +638,20 @@ async function initSkillManagersWithCleanup(managers, allManagers) {
|
|
|
597
638
|
async function getSkillManagers(expert, experts, setting, eventListener, options) {
|
|
598
639
|
const { perstackBaseSkillCommand, env, jobId, runId } = setting;
|
|
599
640
|
const { skills } = expert;
|
|
641
|
+
const factory = options?.factory ?? defaultSkillManagerFactory;
|
|
600
642
|
if (!skills["@perstack/base"]) {
|
|
601
643
|
throw new Error("Base skill is not defined");
|
|
602
644
|
}
|
|
645
|
+
const factoryContext = {
|
|
646
|
+
env,
|
|
647
|
+
jobId,
|
|
648
|
+
runId,
|
|
649
|
+
eventListener
|
|
650
|
+
};
|
|
603
651
|
const allManagers = [];
|
|
604
|
-
const mcpSkills = Object.values(skills).filter(
|
|
652
|
+
const mcpSkills = Object.values(skills).filter(
|
|
653
|
+
(skill) => skill.type === "mcpStdioSkill" || skill.type === "mcpSseSkill"
|
|
654
|
+
).map((skill) => {
|
|
605
655
|
if (perstackBaseSkillCommand && skill.type === "mcpStdioSkill") {
|
|
606
656
|
const matchesBaseByPackage = skill.command === "npx" && skill.packageName === "@perstack/base";
|
|
607
657
|
const matchesBaseByArgs = skill.command === "npx" && Array.isArray(skill.args) && skill.args.includes("@perstack/base");
|
|
@@ -622,7 +672,7 @@ async function getSkillManagers(expert, experts, setting, eventListener, options
|
|
|
622
672
|
return skill;
|
|
623
673
|
});
|
|
624
674
|
const mcpSkillManagers = mcpSkills.map((skill) => {
|
|
625
|
-
const manager =
|
|
675
|
+
const manager = factory.createMcp(skill, factoryContext);
|
|
626
676
|
allManagers.push(manager);
|
|
627
677
|
return manager;
|
|
628
678
|
});
|
|
@@ -632,7 +682,7 @@ async function getSkillManagers(expert, experts, setting, eventListener, options
|
|
|
632
682
|
(skill) => skill.type === "interactiveSkill"
|
|
633
683
|
);
|
|
634
684
|
const interactiveSkillManagers = interactiveSkills.map((interactiveSkill) => {
|
|
635
|
-
const manager =
|
|
685
|
+
const manager = factory.createInteractive(interactiveSkill, factoryContext);
|
|
636
686
|
allManagers.push(manager);
|
|
637
687
|
return manager;
|
|
638
688
|
});
|
|
@@ -646,7 +696,7 @@ async function getSkillManagers(expert, experts, setting, eventListener, options
|
|
|
646
696
|
})));
|
|
647
697
|
throw new Error(`Delegate expert "${delegateExpertName}" not found in experts`);
|
|
648
698
|
}
|
|
649
|
-
const manager =
|
|
699
|
+
const manager = factory.createDelegate(delegate, factoryContext);
|
|
650
700
|
allManagers.push(manager);
|
|
651
701
|
delegateSkillManagers.push(manager);
|
|
652
702
|
}
|
|
@@ -1973,63 +2023,113 @@ var StateMachineLogics = {
|
|
|
1973
2023
|
CallingDelegate: callingDelegateLogic,
|
|
1974
2024
|
FinishingStep: finishingStepLogic
|
|
1975
2025
|
};
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
if (!shouldContinue) {
|
|
2017
|
-
runActor.stop();
|
|
2018
|
-
await closeSkillManagers(runState.context.skillManagers);
|
|
2019
|
-
resolve(runState.context.checkpoint);
|
|
2020
|
-
return;
|
|
2021
|
-
}
|
|
2022
|
-
}
|
|
2023
|
-
runActor.send(event);
|
|
2024
|
-
}
|
|
2025
|
-
} catch (error) {
|
|
2026
|
-
await closeSkillManagers(skillManagers).catch(() => {
|
|
2026
|
+
var DefaultActorFactory = class {
|
|
2027
|
+
create(input) {
|
|
2028
|
+
return createActor(runtimeStateMachine, input);
|
|
2029
|
+
}
|
|
2030
|
+
};
|
|
2031
|
+
var defaultActorFactory = new DefaultActorFactory();
|
|
2032
|
+
|
|
2033
|
+
// src/state-machine/coordinator.ts
|
|
2034
|
+
var StateMachineCoordinator = class {
|
|
2035
|
+
constructor(params, deps = {}) {
|
|
2036
|
+
this.params = params;
|
|
2037
|
+
this.actorFactory = deps.actorFactory ?? defaultActorFactory;
|
|
2038
|
+
this.closeManagers = deps.closeSkillManagers ?? closeSkillManagers;
|
|
2039
|
+
this.logics = deps.logics ?? StateMachineLogics;
|
|
2040
|
+
}
|
|
2041
|
+
actorFactory;
|
|
2042
|
+
closeManagers;
|
|
2043
|
+
logics;
|
|
2044
|
+
actor = null;
|
|
2045
|
+
resolvePromise = null;
|
|
2046
|
+
rejectPromise = null;
|
|
2047
|
+
/**
|
|
2048
|
+
* Execute the state machine and return the final checkpoint.
|
|
2049
|
+
*/
|
|
2050
|
+
async execute() {
|
|
2051
|
+
const { setting, initialCheckpoint, eventListener, skillManagers } = this.params;
|
|
2052
|
+
this.actor = this.actorFactory.create({
|
|
2053
|
+
input: {
|
|
2054
|
+
setting,
|
|
2055
|
+
initialCheckpoint,
|
|
2056
|
+
eventListener,
|
|
2057
|
+
skillManagers
|
|
2058
|
+
}
|
|
2059
|
+
});
|
|
2060
|
+
return new Promise((resolve, reject) => {
|
|
2061
|
+
this.resolvePromise = resolve;
|
|
2062
|
+
this.rejectPromise = reject;
|
|
2063
|
+
this.actor.subscribe((runState) => {
|
|
2064
|
+
this.handleStateChange(runState).catch((error) => {
|
|
2065
|
+
this.handleError(error);
|
|
2027
2066
|
});
|
|
2028
|
-
|
|
2067
|
+
});
|
|
2068
|
+
this.actor.start();
|
|
2069
|
+
});
|
|
2070
|
+
}
|
|
2071
|
+
/**
|
|
2072
|
+
* Handle state changes from the actor.
|
|
2073
|
+
* Exported for testing purposes.
|
|
2074
|
+
*/
|
|
2075
|
+
async handleStateChange(runState) {
|
|
2076
|
+
if (runState.value === "Stopped") {
|
|
2077
|
+
await this.handleStoppedState(runState);
|
|
2078
|
+
} else {
|
|
2079
|
+
await this.handleActiveState(runState);
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
/**
|
|
2083
|
+
* Handle the stopped state - cleanup and resolve.
|
|
2084
|
+
*/
|
|
2085
|
+
async handleStoppedState(runState) {
|
|
2086
|
+
const { checkpoint, skillManagers } = runState.context;
|
|
2087
|
+
if (!checkpoint) {
|
|
2088
|
+
throw new Error("Checkpoint is undefined");
|
|
2089
|
+
}
|
|
2090
|
+
await this.closeManagers(skillManagers);
|
|
2091
|
+
this.resolvePromise?.(checkpoint);
|
|
2092
|
+
}
|
|
2093
|
+
/**
|
|
2094
|
+
* Handle active states - execute logic, store checkpoint, emit events.
|
|
2095
|
+
*/
|
|
2096
|
+
async handleActiveState(runState) {
|
|
2097
|
+
const { eventEmitter, storeCheckpoint, shouldContinueRun } = this.params;
|
|
2098
|
+
const stateValue = runState.value;
|
|
2099
|
+
const event = await this.logics[stateValue](runState.context);
|
|
2100
|
+
if ("checkpoint" in event) {
|
|
2101
|
+
await storeCheckpoint(event.checkpoint);
|
|
2102
|
+
}
|
|
2103
|
+
await eventEmitter.emit(event);
|
|
2104
|
+
if (shouldContinueRun) {
|
|
2105
|
+
const shouldContinue = await shouldContinueRun(
|
|
2106
|
+
runState.context.setting,
|
|
2107
|
+
runState.context.checkpoint,
|
|
2108
|
+
runState.context.step
|
|
2109
|
+
);
|
|
2110
|
+
if (!shouldContinue) {
|
|
2111
|
+
this.actor?.stop();
|
|
2112
|
+
await this.closeManagers(runState.context.skillManagers);
|
|
2113
|
+
this.resolvePromise?.(runState.context.checkpoint);
|
|
2114
|
+
return;
|
|
2029
2115
|
}
|
|
2116
|
+
}
|
|
2117
|
+
this.actor?.send(event);
|
|
2118
|
+
}
|
|
2119
|
+
/**
|
|
2120
|
+
* Handle errors - cleanup and reject.
|
|
2121
|
+
*/
|
|
2122
|
+
async handleError(error) {
|
|
2123
|
+
await this.closeManagers(this.params.skillManagers).catch(() => {
|
|
2030
2124
|
});
|
|
2031
|
-
|
|
2032
|
-
}
|
|
2125
|
+
this.rejectPromise?.(error instanceof Error ? error : new Error(String(error)));
|
|
2126
|
+
}
|
|
2127
|
+
};
|
|
2128
|
+
|
|
2129
|
+
// src/state-machine/executor.ts
|
|
2130
|
+
async function executeStateMachine(params) {
|
|
2131
|
+
const coordinator = new StateMachineCoordinator(params);
|
|
2132
|
+
return coordinator.execute();
|
|
2033
2133
|
}
|
|
2034
2134
|
|
|
2035
2135
|
// src/helpers/checkpoint.ts
|
|
@@ -2534,5 +2634,5 @@ async function run(runInput, options) {
|
|
|
2534
2634
|
}
|
|
2535
2635
|
|
|
2536
2636
|
export { getModel, package_default, run, runtimeStateMachine };
|
|
2537
|
-
//# sourceMappingURL=chunk-
|
|
2538
|
-
//# sourceMappingURL=chunk-
|
|
2637
|
+
//# sourceMappingURL=chunk-3RWT2GPO.js.map
|
|
2638
|
+
//# sourceMappingURL=chunk-3RWT2GPO.js.map
|