agent-phonon 0.2.11 → 0.3.0

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/cli.js CHANGED
@@ -940,7 +940,7 @@ var init_project = __esm({
940
940
 
941
941
  // ../protocol/dist/schemas/skill.js
942
942
  import { z as z10 } from "zod";
943
- var SkillScope, SkillSource, SkillDescriptor, SkillInstallParams, SkillInstallResult, SkillUninstallParams, SkillUninstallResult, SkillListParams, SkillListResult;
943
+ var SkillScope, SkillSource, SkillDescriptor, SkillInstallParams, SkillInstallResult, SkillUninstallParams, SkillUninstallResult, SkillListParams, SkillListResult, SkillDirsParams, SkillDirectoryDescriptor, SkillDirsResult;
944
944
  var init_skill = __esm({
945
945
  "../protocol/dist/schemas/skill.js"() {
946
946
  "use strict";
@@ -1028,6 +1028,29 @@ var init_skill = __esm({
1028
1028
  SkillListResult = z10.object({
1029
1029
  skills: z10.array(SkillDescriptor)
1030
1030
  });
1031
+ SkillDirsParams = z10.object({
1032
+ /** 可选:global scope 的目标 agent。 */
1033
+ agent: AgentId.optional(),
1034
+ /** 可选:project scope 的目标项目。 */
1035
+ projectId: ProjectId.optional(),
1036
+ /** 可选:只查 global 或 project;不传时按 agent/projectId 返回可查范围。 */
1037
+ scope: SkillScope.optional()
1038
+ });
1039
+ SkillDirectoryDescriptor = z10.object({
1040
+ /** skill 目录名。 */
1041
+ name: z10.string().min(1),
1042
+ scope: SkillScope,
1043
+ agent: AgentId.optional(),
1044
+ projectId: ProjectId.optional(),
1045
+ /** scope 根目录(global skill root 或 <project>/.agent/skills)。 */
1046
+ rootPath: z10.string().min(1),
1047
+ /** skill 目录真实本机路径。 */
1048
+ path: z10.string().min(1),
1049
+ exists: z10.boolean()
1050
+ });
1051
+ SkillDirsResult = z10.object({
1052
+ directories: z10.array(SkillDirectoryDescriptor)
1053
+ });
1031
1054
  }
1032
1055
  });
1033
1056
 
@@ -1311,43 +1334,166 @@ var init_env = __esm({
1311
1334
  }
1312
1335
  });
1313
1336
 
1314
- // ../protocol/dist/schemas/jsonrpc.js
1337
+ // ../protocol/dist/schemas/workflow.js
1315
1338
  import { z as z16 } from "zod";
1339
+ var ModelId, TurnId, ClientRequestId, WorkflowId, WorkflowNodeId, WorkflowEdgeId, WorkflowRoleId, WorkflowNode, WorkflowEdge, WorkflowDagPlan, WorkflowCommunicationGraph, WorkflowGraphPlan, WorkflowPlan, WorkflowStatus, WorkflowNodeStatus, WorkflowRunParams, WorkflowRunResult, WorkflowStatusParams, WorkflowNodeRuntime, WorkflowStatusResult, WorkflowCancelParams, WorkflowCancelResult, WorkflowListParams, WorkflowListResult, WorkflowEvent;
1340
+ var init_workflow = __esm({
1341
+ "../protocol/dist/schemas/workflow.js"() {
1342
+ "use strict";
1343
+ init_common();
1344
+ ModelId = z16.string().min(1);
1345
+ TurnId = z16.string().min(1);
1346
+ ClientRequestId = z16.string().min(1);
1347
+ WorkflowId = z16.string().min(1);
1348
+ WorkflowNodeId = z16.string().min(1);
1349
+ WorkflowEdgeId = z16.string().min(1);
1350
+ WorkflowRoleId = z16.string().min(1);
1351
+ WorkflowNode = z16.object({
1352
+ nodeId: WorkflowNodeId,
1353
+ agent: AgentId,
1354
+ model: ModelId,
1355
+ role: WorkflowRoleId.optional(),
1356
+ input: z16.string().optional(),
1357
+ systemPrompt: z16.string().optional(),
1358
+ dependsOn: z16.array(WorkflowNodeId).optional(),
1359
+ sessionId: SessionId.optional(),
1360
+ agentConfig: z16.record(z16.unknown()).optional(),
1361
+ metadata: z16.record(z16.unknown()).optional()
1362
+ });
1363
+ WorkflowEdge = z16.object({
1364
+ edgeId: WorkflowEdgeId.optional(),
1365
+ from: WorkflowNodeId,
1366
+ to: WorkflowNodeId,
1367
+ label: z16.string().optional(),
1368
+ condition: z16.string().optional(),
1369
+ metadata: z16.record(z16.unknown()).optional()
1370
+ });
1371
+ WorkflowDagPlan = z16.object({
1372
+ mode: z16.literal("dag"),
1373
+ nodes: z16.array(WorkflowNode).min(1),
1374
+ edges: z16.array(WorkflowEdge).optional(),
1375
+ finalNodeId: WorkflowNodeId.optional()
1376
+ });
1377
+ WorkflowCommunicationGraph = z16.object({
1378
+ edges: z16.array(WorkflowEdge).default([]),
1379
+ allowSelfLoop: z16.boolean().default(false),
1380
+ maxIterations: z16.number().int().positive().default(12)
1381
+ });
1382
+ WorkflowGraphPlan = z16.object({
1383
+ mode: z16.literal("graph"),
1384
+ executor: z16.object({
1385
+ nodeId: WorkflowNodeId,
1386
+ agent: AgentId,
1387
+ model: ModelId,
1388
+ role: z16.literal("executor").default("executor"),
1389
+ systemPrompt: z16.string().optional(),
1390
+ agentConfig: z16.record(z16.unknown()).optional()
1391
+ }),
1392
+ workers: z16.array(WorkflowNode).min(1),
1393
+ communicationGraph: WorkflowCommunicationGraph
1394
+ });
1395
+ WorkflowPlan = z16.discriminatedUnion("mode", [WorkflowDagPlan, WorkflowGraphPlan]);
1396
+ WorkflowStatus = z16.enum(["queued", "running", "completed", "failed", "cancelled", "timeout"]);
1397
+ WorkflowNodeStatus = z16.enum(["pending", "ready", "running", "completed", "failed", "skipped", "cancelled"]);
1398
+ WorkflowRunParams = z16.object({
1399
+ project: ProjectId,
1400
+ worktreeId: z16.string().optional(),
1401
+ plan: WorkflowPlan,
1402
+ input: z16.string().optional(),
1403
+ clientRequestId: ClientRequestId.optional(),
1404
+ metadata: z16.record(z16.unknown()).optional()
1405
+ });
1406
+ WorkflowRunResult = z16.object({
1407
+ workflowId: WorkflowId,
1408
+ status: WorkflowStatus,
1409
+ createdAt: Timestamp
1410
+ });
1411
+ WorkflowStatusParams = z16.object({ workflowId: WorkflowId });
1412
+ WorkflowNodeRuntime = z16.object({
1413
+ nodeId: WorkflowNodeId,
1414
+ status: WorkflowNodeStatus,
1415
+ agent: AgentId,
1416
+ model: ModelId,
1417
+ role: WorkflowRoleId.optional(),
1418
+ sessionId: SessionId.optional(),
1419
+ turnId: TurnId.optional(),
1420
+ startedAt: Timestamp.optional(),
1421
+ completedAt: Timestamp.optional(),
1422
+ error: z16.string().optional()
1423
+ });
1424
+ WorkflowStatusResult = z16.object({
1425
+ workflowId: WorkflowId,
1426
+ status: WorkflowStatus,
1427
+ project: ProjectId,
1428
+ mode: z16.enum(["dag", "graph"]),
1429
+ nodes: z16.array(WorkflowNodeRuntime),
1430
+ createdAt: Timestamp,
1431
+ updatedAt: Timestamp,
1432
+ completedAt: Timestamp.optional(),
1433
+ error: z16.string().optional()
1434
+ });
1435
+ WorkflowCancelParams = z16.object({ workflowId: WorkflowId, reason: z16.string().optional() });
1436
+ WorkflowCancelResult = z16.object({ workflowId: WorkflowId, status: z16.literal("cancelled") });
1437
+ WorkflowListParams = z16.object({
1438
+ status: WorkflowStatus.optional(),
1439
+ limit: z16.number().int().positive().max(100).default(50).optional(),
1440
+ cursor: z16.string().optional()
1441
+ });
1442
+ WorkflowListResult = z16.object({ workflows: z16.array(WorkflowStatusResult), nextCursor: z16.string().optional() });
1443
+ WorkflowEvent = z16.object({
1444
+ workflowId: WorkflowId,
1445
+ seq: z16.number().int().nonnegative(),
1446
+ type: z16.enum(["workflow.status", "node.status", "node.stream", "edge.route", "executor.decision"]),
1447
+ nodeId: WorkflowNodeId.optional(),
1448
+ sessionId: SessionId.optional(),
1449
+ turnId: TurnId.optional(),
1450
+ agent: AgentId.optional(),
1451
+ model: ModelId.optional(),
1452
+ role: WorkflowRoleId.optional(),
1453
+ status: z16.union([WorkflowStatus, WorkflowNodeStatus]).optional(),
1454
+ payload: z16.record(z16.unknown()).optional(),
1455
+ timestamp: Timestamp
1456
+ });
1457
+ }
1458
+ });
1459
+
1460
+ // ../protocol/dist/schemas/jsonrpc.js
1461
+ import { z as z17 } from "zod";
1316
1462
  var JsonRpcVersion, JsonRpcId, JsonRpcRequest, JsonRpcNotification, JsonRpcSuccess, JsonRpcErrorObject, JsonRpcError, JsonRpcMessage, JSON_RPC_CODES;
1317
1463
  var init_jsonrpc = __esm({
1318
1464
  "../protocol/dist/schemas/jsonrpc.js"() {
1319
1465
  "use strict";
1320
1466
  init_common();
1321
- JsonRpcVersion = z16.literal("2.0");
1322
- JsonRpcId = z16.union([z16.string(), z16.number()]);
1323
- JsonRpcRequest = z16.object({
1467
+ JsonRpcVersion = z17.literal("2.0");
1468
+ JsonRpcId = z17.union([z17.string(), z17.number()]);
1469
+ JsonRpcRequest = z17.object({
1324
1470
  jsonrpc: JsonRpcVersion,
1325
1471
  id: JsonRpcId,
1326
- method: z16.string(),
1327
- params: z16.unknown().optional()
1472
+ method: z17.string(),
1473
+ params: z17.unknown().optional()
1328
1474
  });
1329
- JsonRpcNotification = z16.object({
1475
+ JsonRpcNotification = z17.object({
1330
1476
  jsonrpc: JsonRpcVersion,
1331
- method: z16.string(),
1332
- params: z16.unknown().optional()
1477
+ method: z17.string(),
1478
+ params: z17.unknown().optional()
1333
1479
  });
1334
- JsonRpcSuccess = z16.object({
1480
+ JsonRpcSuccess = z17.object({
1335
1481
  jsonrpc: JsonRpcVersion,
1336
1482
  id: JsonRpcId,
1337
- result: z16.unknown()
1483
+ result: z17.unknown()
1338
1484
  });
1339
- JsonRpcErrorObject = z16.object({
1485
+ JsonRpcErrorObject = z17.object({
1340
1486
  /** JSON-RPC 传输级 code(-32700..-32600 保留;应用错误用 data.appCode 判别)。 */
1341
- code: z16.number().int(),
1342
- message: z16.string(),
1487
+ code: z17.number().int(),
1488
+ message: z17.string(),
1343
1489
  data: PhononErrorData.optional()
1344
1490
  });
1345
- JsonRpcError = z16.object({
1491
+ JsonRpcError = z17.object({
1346
1492
  jsonrpc: JsonRpcVersion,
1347
1493
  id: JsonRpcId.nullable(),
1348
1494
  error: JsonRpcErrorObject
1349
1495
  });
1350
- JsonRpcMessage = z16.union([
1496
+ JsonRpcMessage = z17.union([
1351
1497
  JsonRpcRequest,
1352
1498
  JsonRpcNotification,
1353
1499
  JsonRpcSuccess,
@@ -1366,7 +1512,7 @@ var init_jsonrpc = __esm({
1366
1512
  });
1367
1513
 
1368
1514
  // ../protocol/dist/schemas/methods.js
1369
- import { z as z17 } from "zod";
1515
+ import { z as z18 } from "zod";
1370
1516
  function parseParams(method, data) {
1371
1517
  return METHODS[method].params.parse(data);
1372
1518
  }
@@ -1386,7 +1532,8 @@ var init_methods = __esm({
1386
1532
  init_device();
1387
1533
  init_file();
1388
1534
  init_env();
1389
- z_void = z17.undefined();
1535
+ init_workflow();
1536
+ z_void = z18.undefined();
1390
1537
  METHODS = {
1391
1538
  // --- 握手(phonon 拨出后先发)---
1392
1539
  "connect.hello": {
@@ -1664,6 +1811,43 @@ var init_methods = __esm({
1664
1811
  kind: "request",
1665
1812
  params: SkillListParams,
1666
1813
  result: SkillListResult
1814
+ },
1815
+ "skill.dirs": {
1816
+ direction: "s2p",
1817
+ kind: "request",
1818
+ params: SkillDirsParams,
1819
+ result: SkillDirsResult
1820
+ },
1821
+ // --- L3 workflow orchestration (DAG / executor graph) ---
1822
+ "workflow.run": {
1823
+ direction: "s2p",
1824
+ kind: "request",
1825
+ params: WorkflowRunParams,
1826
+ result: WorkflowRunResult
1827
+ },
1828
+ "workflow.status": {
1829
+ direction: "s2p",
1830
+ kind: "request",
1831
+ params: WorkflowStatusParams,
1832
+ result: WorkflowStatusResult
1833
+ },
1834
+ "workflow.cancel": {
1835
+ direction: "s2p",
1836
+ kind: "request",
1837
+ params: WorkflowCancelParams,
1838
+ result: WorkflowCancelResult
1839
+ },
1840
+ "workflow.list": {
1841
+ direction: "s2p",
1842
+ kind: "request",
1843
+ params: WorkflowListParams,
1844
+ result: WorkflowListResult
1845
+ },
1846
+ "workflow.event": {
1847
+ direction: "p2s",
1848
+ kind: "notification",
1849
+ params: WorkflowEvent,
1850
+ result: z_void
1667
1851
  }
1668
1852
  };
1669
1853
  METHOD_NAMES = Object.keys(METHODS);
@@ -1689,6 +1873,7 @@ var init_dist = __esm({
1689
1873
  init_device();
1690
1874
  init_file();
1691
1875
  init_env();
1876
+ init_workflow();
1692
1877
  init_jsonrpc();
1693
1878
  init_methods();
1694
1879
  }
@@ -2438,7 +2623,7 @@ var ProjectManager = class {
2438
2623
 
2439
2624
  // ../core/dist/skill-manager.js
2440
2625
  init_rpc();
2441
- import { mkdir as mkdir2, rm as rm2, writeFile, readdir as readdir2, stat as stat2, mkdtemp, lstat } from "node:fs/promises";
2626
+ import { mkdir as mkdir2, rm as rm2, writeFile, readdir as readdir2, stat as stat2, mkdtemp, lstat, realpath } from "node:fs/promises";
2442
2627
  import { existsSync as existsSync2 } from "node:fs";
2443
2628
  import { join as join2, resolve as resolve2, sep } from "node:path";
2444
2629
  import { tmpdir } from "node:os";
@@ -2541,6 +2726,36 @@ var SkillManager = class {
2541
2726
  return true;
2542
2727
  });
2543
2728
  }
2729
+ async dirs(filter) {
2730
+ const rows = [];
2731
+ if (filter?.scope !== "project" && filter?.agent) {
2732
+ const adapter = this.registry.resolve(filter.agent);
2733
+ const root = adapter?.globalSkillDir?.(filter.agent);
2734
+ if (root)
2735
+ rows.push(...await this.listDirs(root, { scope: "global", agent: filter.agent }));
2736
+ }
2737
+ if (filter?.scope !== "global" && filter?.projectId) {
2738
+ const projPath = this.resolveProjectPath(filter.projectId);
2739
+ if (!projPath)
2740
+ throw new PhononError("errProjectNotFound", `project ${filter.projectId} not found`);
2741
+ rows.push(...await this.listDirs(join2(projPath, ".agent", "skills"), { scope: "project", projectId: filter.projectId }));
2742
+ }
2743
+ return rows;
2744
+ }
2745
+ async listDirs(root, base) {
2746
+ const rootPath = existsSync2(root) ? await realpath(root) : resolve2(root);
2747
+ if (!existsSync2(root))
2748
+ return [];
2749
+ const entries = await readdir2(root, { withFileTypes: true });
2750
+ const rows = [];
2751
+ for (const e of entries) {
2752
+ if (!e.isDirectory())
2753
+ continue;
2754
+ const full = join2(root, e.name);
2755
+ rows.push({ name: e.name, ...base, rootPath, path: await realpath(full), exists: true });
2756
+ }
2757
+ return rows.sort((a, b) => a.name.localeCompare(b.name));
2758
+ }
2544
2759
  async installArchive(source, dest) {
2545
2760
  if (source.format !== "tar.gz")
2546
2761
  throw new PhononError("errSkillInstallFailed", `unsupported archive format: ${source.format}`);
@@ -3142,7 +3357,7 @@ var PhononStore = class {
3142
3357
 
3143
3358
  // ../core/dist/file-manager.js
3144
3359
  init_rpc();
3145
- import { mkdir as mkdir3, readFile, readdir as readdir3, lstat as lstat2, realpath, writeFile as writeFile2 } from "node:fs/promises";
3360
+ import { mkdir as mkdir3, readFile, readdir as readdir3, lstat as lstat2, realpath as realpath2, writeFile as writeFile2 } from "node:fs/promises";
3146
3361
  import { basename as basename2, dirname as dirname3, join as join4, resolve as resolve4, sep as sep2 } from "node:path";
3147
3362
  var FileManager = class {
3148
3363
  resolver;
@@ -3159,7 +3374,7 @@ var FileManager = class {
3159
3374
  let cur = abs;
3160
3375
  for (; ; ) {
3161
3376
  try {
3162
- const real = await realpath(cur);
3377
+ const real = await realpath2(cur);
3163
3378
  return suffix.length ? resolve4(real, ...suffix) : real;
3164
3379
  } catch (e) {
3165
3380
  if (e?.code !== "ENOENT")
@@ -3365,6 +3580,190 @@ async function commandExists(cmd2) {
3365
3580
  }
3366
3581
  }
3367
3582
 
3583
+ // ../core/dist/workflow-engine.js
3584
+ init_rpc();
3585
+ var WorkflowEngine = class {
3586
+ opts;
3587
+ runs = /* @__PURE__ */ new Map();
3588
+ sessionToNode = /* @__PURE__ */ new Map();
3589
+ idSeq = 1;
3590
+ constructor(opts) {
3591
+ this.opts = opts;
3592
+ }
3593
+ async run(params) {
3594
+ const workflowId = `wf-${Date.now()}-${this.idSeq++}`;
3595
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3596
+ const nodes = this.initialNodes(params.plan);
3597
+ const run = { workflowId, tenantId: this.opts.tenantId, project: params.project, worktreeId: params.worktreeId, mode: params.plan.mode, plan: params.plan, input: params.input, status: "queued", nodes, createdAt: now, updatedAt: now, seq: 0 };
3598
+ this.runs.set(workflowId, run);
3599
+ this.emit(run, { type: "workflow.status", status: "queued" });
3600
+ void this.execute(run).catch((err) => this.fail(run, err));
3601
+ return { workflowId, status: run.status, createdAt: run.createdAt };
3602
+ }
3603
+ status(workflowId) {
3604
+ const run = this.get(workflowId);
3605
+ return this.toStatus(run);
3606
+ }
3607
+ list(filter) {
3608
+ const limit = filter?.limit ?? 50;
3609
+ const rows = [...this.runs.values()].filter((r) => !filter?.status || r.status === filter.status).slice(-limit).reverse();
3610
+ return { workflows: rows.map((r) => this.toStatus(r)) };
3611
+ }
3612
+ async cancel(workflowId, reason) {
3613
+ const run = this.get(workflowId);
3614
+ run.status = "cancelled";
3615
+ run.completedAt = (/* @__PURE__ */ new Date()).toISOString();
3616
+ run.updatedAt = run.completedAt;
3617
+ for (const n of run.nodes) {
3618
+ if (n.sessionId && (n.status === "running" || n.status === "ready" || n.status === "pending")) {
3619
+ n.status = "cancelled";
3620
+ try {
3621
+ await this.opts.engine.terminate(this.opts.tenantId, n.sessionId);
3622
+ } catch {
3623
+ }
3624
+ }
3625
+ }
3626
+ this.emit(run, { type: "workflow.status", status: "cancelled", payload: { reason } });
3627
+ return { workflowId, status: "cancelled" };
3628
+ }
3629
+ onStreamEvent(ev) {
3630
+ const sessionId = ev.sessionId;
3631
+ if (!sessionId)
3632
+ return;
3633
+ const m = this.sessionToNode.get(sessionId);
3634
+ if (!m)
3635
+ return;
3636
+ const run = this.runs.get(m.workflowId);
3637
+ const node = run?.nodes.find((n) => n.nodeId === m.nodeId);
3638
+ if (!run || !node)
3639
+ return;
3640
+ this.emit(run, {
3641
+ type: "node.stream",
3642
+ nodeId: node.nodeId,
3643
+ sessionId,
3644
+ turnId: ev.turnId,
3645
+ agent: node.agent,
3646
+ model: node.model,
3647
+ role: node.role,
3648
+ payload: ev
3649
+ });
3650
+ }
3651
+ async execute(run) {
3652
+ run.status = "running";
3653
+ run.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
3654
+ this.emit(run, { type: "workflow.status", status: "running" });
3655
+ if (run.plan.mode === "dag")
3656
+ await this.executeDag(run);
3657
+ else
3658
+ await this.executeGraph(run);
3659
+ if (!["cancelled", "failed"].includes(run.status)) {
3660
+ run.status = "completed";
3661
+ run.completedAt = (/* @__PURE__ */ new Date()).toISOString();
3662
+ run.updatedAt = run.completedAt;
3663
+ this.emit(run, { type: "workflow.status", status: "completed" });
3664
+ }
3665
+ }
3666
+ async executeDag(run) {
3667
+ const plan = run.plan.mode === "dag" ? run.plan : void 0;
3668
+ if (!plan)
3669
+ return;
3670
+ const deps = /* @__PURE__ */ new Map();
3671
+ for (const n of plan.nodes)
3672
+ deps.set(n.nodeId, new Set(n.dependsOn ?? []));
3673
+ for (const e of plan.edges ?? [])
3674
+ deps.get(e.to)?.add(e.from);
3675
+ const done = /* @__PURE__ */ new Set();
3676
+ while (done.size < plan.nodes.length) {
3677
+ const ready = plan.nodes.filter((n) => !done.has(n.nodeId) && [...deps.get(n.nodeId) ?? []].every((d) => done.has(d)));
3678
+ if (ready.length === 0)
3679
+ throw new PhononError("errInvalidParams", "workflow DAG has a cycle or missing dependency");
3680
+ await Promise.all(ready.map((n) => this.executeNode(run, n.nodeId, n.agent, n.model, n.role, n.input ?? run.input ?? "", n.agentConfig)));
3681
+ for (const n of ready)
3682
+ done.add(n.nodeId);
3683
+ }
3684
+ }
3685
+ async executeGraph(run) {
3686
+ const plan = run.plan.mode === "graph" ? run.plan : void 0;
3687
+ if (!plan)
3688
+ return;
3689
+ const prompt = [
3690
+ "You are the executor for a multi-agent graph workflow.",
3691
+ `Input: ${run.input ?? ""}`,
3692
+ `Workers: ${JSON.stringify(plan.workers.map((w) => ({ nodeId: w.nodeId, role: w.role, agent: w.agent, model: w.model })))}`,
3693
+ `Communication graph: ${JSON.stringify(plan.communicationGraph)}`,
3694
+ "Produce the first routing decision and final summary for this initial implementation."
3695
+ ].join("\n\n");
3696
+ await this.executeNode(run, plan.executor.nodeId, plan.executor.agent, plan.executor.model, plan.executor.role, prompt, plan.executor.agentConfig);
3697
+ this.emit(run, { type: "executor.decision", nodeId: plan.executor.nodeId, payload: { initialImplementation: true } });
3698
+ }
3699
+ async executeNode(run, nodeId, agent, model, role, input, agentConfig) {
3700
+ const node = run.nodes.find((n) => n.nodeId === nodeId) ?? this.addNode(run, { nodeId, agent, model, role });
3701
+ node.status = "running";
3702
+ node.startedAt = (/* @__PURE__ */ new Date()).toISOString();
3703
+ run.updatedAt = node.startedAt;
3704
+ this.emit(run, { type: "node.status", nodeId, agent, model, role, status: "running" });
3705
+ try {
3706
+ const cwd = this.opts.resolveCwd(run.project, run.worktreeId);
3707
+ const created = await this.opts.engine.create({ tenantId: run.tenantId, project: run.project, worktreeId: run.worktreeId, cwd, agent, model, verbosity: "messages", agentConfig });
3708
+ node.sessionId = created.sessionId;
3709
+ this.sessionToNode.set(created.sessionId, { workflowId: run.workflowId, nodeId });
3710
+ const sent = await this.opts.engine.send(run.tenantId, created.sessionId, input, { environment: this.opts.env.resolveForExecution({ projectId: run.project, agent }) });
3711
+ node.turnId = sent.turnId;
3712
+ await this.waitIdle(run.tenantId, created.sessionId);
3713
+ node.status = "completed";
3714
+ node.completedAt = (/* @__PURE__ */ new Date()).toISOString();
3715
+ run.updatedAt = node.completedAt;
3716
+ this.emit(run, { type: "node.status", nodeId, sessionId: node.sessionId, turnId: node.turnId, agent, model, role, status: "completed" });
3717
+ } catch (err) {
3718
+ node.status = "failed";
3719
+ node.completedAt = (/* @__PURE__ */ new Date()).toISOString();
3720
+ node.error = err?.message ?? String(err);
3721
+ run.updatedAt = node.completedAt;
3722
+ this.emit(run, { type: "node.status", nodeId, agent, model, role, status: "failed", payload: { error: node.error } });
3723
+ throw err;
3724
+ }
3725
+ }
3726
+ async waitIdle(tenantId, sessionId) {
3727
+ for (; ; ) {
3728
+ const s = await this.opts.engine.status(tenantId, sessionId);
3729
+ if (s.status === "idle" || s.status === "terminated" || s.status === "paused")
3730
+ return;
3731
+ await new Promise((r) => setTimeout(r, 250));
3732
+ }
3733
+ }
3734
+ initialNodes(plan) {
3735
+ const nodes = plan.mode === "dag" ? plan.nodes : [plan.executor, ...plan.workers];
3736
+ return nodes.map((n) => ({ nodeId: n.nodeId, status: "pending", agent: n.agent, model: n.model, role: n.role }));
3737
+ }
3738
+ addNode(run, n) {
3739
+ const node = { ...n, status: "pending" };
3740
+ run.nodes.push(node);
3741
+ return node;
3742
+ }
3743
+ get(workflowId) {
3744
+ const run = this.runs.get(workflowId);
3745
+ if (!run)
3746
+ throw new PhononError("errInvalidParams", `workflow ${workflowId} not found`);
3747
+ return run;
3748
+ }
3749
+ fail(run, err) {
3750
+ if (run.status === "cancelled")
3751
+ return;
3752
+ run.status = "failed";
3753
+ run.error = err?.message ?? String(err);
3754
+ run.completedAt = (/* @__PURE__ */ new Date()).toISOString();
3755
+ run.updatedAt = run.completedAt;
3756
+ this.emit(run, { type: "workflow.status", status: "failed", payload: { error: run.error } });
3757
+ }
3758
+ toStatus(run) {
3759
+ return { workflowId: run.workflowId, status: run.status, project: run.project, mode: run.mode, nodes: run.nodes, createdAt: run.createdAt, updatedAt: run.updatedAt, completedAt: run.completedAt, error: run.error };
3760
+ }
3761
+ emit(run, partial) {
3762
+ const ev = { workflowId: run.workflowId, seq: run.seq++, timestamp: (/* @__PURE__ */ new Date()).toISOString(), ...partial };
3763
+ this.opts.emit(ev);
3764
+ }
3765
+ };
3766
+
3368
3767
  // ../core/dist/env-manager.js
3369
3768
  init_rpc();
3370
3769
  function redact(value) {
@@ -5914,7 +6313,9 @@ var MUTATING_METHODS = /* @__PURE__ */ new Set([
5914
6313
  "file.write",
5915
6314
  "file.mkdir",
5916
6315
  "env.set",
5917
- "env.delete"
6316
+ "env.delete",
6317
+ "workflow.run",
6318
+ "workflow.cancel"
5918
6319
  ]);
5919
6320
  var PhononConnection = class {
5920
6321
  tenantId;
@@ -5930,6 +6331,7 @@ var PhononConnection = class {
5930
6331
  files;
5931
6332
  env;
5932
6333
  obs;
6334
+ workflows;
5933
6335
  /** 该 tenant 绑定的默认项目工作目录解析器(v0 简化:projectId 即绝对路径或映射)。 */
5934
6336
  resolveProjectCwd;
5935
6337
  constructor(opts) {
@@ -5940,6 +6342,7 @@ var PhononConnection = class {
5940
6342
  this.idempotency = new IdempotencyStore({ store: this.store });
5941
6343
  this.outbox = new Outbox({ store: this.store, tenantId: opts.tenantId });
5942
6344
  this.engine = new SessionEngine(opts.registry, (event) => {
6345
+ this.workflows?.onStreamEvent(event);
5943
6346
  this.outbox.enqueue(event);
5944
6347
  this.peer.notifyRaw("stream.event", event);
5945
6348
  }, opts.obs, this.store);
@@ -5969,6 +6372,13 @@ var PhononConnection = class {
5969
6372
  this.env = new EnvManager(this.store, { allowReveal: () => this.policy.allowEnvReveal() });
5970
6373
  this.resolveProjectCwd = opts.resolveProjectCwd ?? ((p) => this.projects.resolveCwd(p));
5971
6374
  this.peer = new RpcPeer(opts.transport, (method, params) => this.dispatch(method, params));
6375
+ this.workflows = new WorkflowEngine({
6376
+ tenantId: this.tenantId,
6377
+ engine: this.engine,
6378
+ resolveCwd: (projectId, worktreeId) => this.projects.resolveCwd(projectId, worktreeId),
6379
+ env: this.env,
6380
+ emit: (event) => this.peer.notifyRaw("workflow.event", event)
6381
+ });
5972
6382
  }
5973
6383
  /** 喂入收到的文本。 */
5974
6384
  handle(data) {
@@ -6210,6 +6620,17 @@ var PhononConnection = class {
6210
6620
  }
6211
6621
  case "skill.list":
6212
6622
  return { skills: this.skills.list({ agent: p.agent, scope: p.scope, projectId: p.projectId }) };
6623
+ case "skill.dirs":
6624
+ return { directories: await this.skills.dirs({ agent: p.agent, scope: p.scope, projectId: p.projectId }) };
6625
+ // ---- L3 workflow orchestration ----
6626
+ case "workflow.run":
6627
+ return this.workflows.run({ project: p.project, worktreeId: p.worktreeId, plan: p.plan, input: p.input, metadata: p.metadata });
6628
+ case "workflow.status":
6629
+ return this.workflows.status(p.workflowId);
6630
+ case "workflow.cancel":
6631
+ return this.workflows.cancel(p.workflowId, p.reason);
6632
+ case "workflow.list":
6633
+ return this.workflows.list(p);
6213
6634
  // ---- 连接/可靠性(s2p) ----
6214
6635
  case "stream.ack": {
6215
6636
  this.outbox.ack(p.sessionId, p.lastSeq);