@framers/agentos 0.1.128 → 0.1.130

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 (40) hide show
  1. package/README.md +1 -1
  2. package/dist/api/runtime/TurnExecutionPipeline.d.ts.map +1 -1
  3. package/dist/api/runtime/TurnExecutionPipeline.js +1 -0
  4. package/dist/api/runtime/TurnExecutionPipeline.js.map +1 -1
  5. package/dist/api/types/AgentOSInput.d.ts +3 -0
  6. package/dist/api/types/AgentOSInput.d.ts.map +1 -1
  7. package/dist/nlp/ai_utilities/StatisticalUtilityAI.d.ts +1 -0
  8. package/dist/nlp/ai_utilities/StatisticalUtilityAI.d.ts.map +1 -1
  9. package/dist/nlp/ai_utilities/StatisticalUtilityAI.js +46 -10
  10. package/dist/nlp/ai_utilities/StatisticalUtilityAI.js.map +1 -1
  11. package/dist/orchestration/events/GraphEvent.d.ts +6 -0
  12. package/dist/orchestration/events/GraphEvent.d.ts.map +1 -1
  13. package/dist/orchestration/events/GraphEvent.js.map +1 -1
  14. package/dist/orchestration/planning/MissionExpansionHandler.d.ts.map +1 -1
  15. package/dist/orchestration/planning/MissionExpansionHandler.js +453 -53
  16. package/dist/orchestration/planning/MissionExpansionHandler.js.map +1 -1
  17. package/dist/orchestration/planning/ProviderAssignmentEngine.d.ts.map +1 -1
  18. package/dist/orchestration/planning/ProviderAssignmentEngine.js +10 -5
  19. package/dist/orchestration/planning/ProviderAssignmentEngine.js.map +1 -1
  20. package/dist/orchestration/runtime/GraphRuntime.d.ts +2 -0
  21. package/dist/orchestration/runtime/GraphRuntime.d.ts.map +1 -1
  22. package/dist/orchestration/runtime/GraphRuntime.js +35 -4
  23. package/dist/orchestration/runtime/GraphRuntime.js.map +1 -1
  24. package/dist/rag/migration/MigrationEngine.js +2 -2
  25. package/dist/rag/migration/MigrationEngine.js.map +1 -1
  26. package/dist/rag/migration/adapters/QdrantSourceAdapter.d.ts +1 -1
  27. package/dist/rag/migration/adapters/QdrantSourceAdapter.d.ts.map +1 -1
  28. package/dist/rag/migration/adapters/QdrantSourceAdapter.js +24 -6
  29. package/dist/rag/migration/adapters/QdrantSourceAdapter.js.map +1 -1
  30. package/dist/rag/migration/adapters/QdrantTargetAdapter.d.ts +2 -1
  31. package/dist/rag/migration/adapters/QdrantTargetAdapter.d.ts.map +1 -1
  32. package/dist/rag/migration/adapters/QdrantTargetAdapter.js +54 -8
  33. package/dist/rag/migration/adapters/QdrantTargetAdapter.js.map +1 -1
  34. package/dist/rag/migration/types.d.ts +5 -0
  35. package/dist/rag/migration/types.d.ts.map +1 -1
  36. package/dist/rag/vector_stores/PineconeVectorStore.d.ts +6 -2
  37. package/dist/rag/vector_stores/PineconeVectorStore.d.ts.map +1 -1
  38. package/dist/rag/vector_stores/PineconeVectorStore.js +95 -12
  39. package/dist/rag/vector_stores/PineconeVectorStore.js.map +1 -1
  40. package/package.json +1 -1
@@ -68,6 +68,77 @@ function estimateComplexity(node) {
68
68
  return 0.5;
69
69
  return 0.35;
70
70
  }
71
+ function buildExecutorConfig(raw, fallbackId) {
72
+ if (isRecord(raw.executorConfig)) {
73
+ return raw.executorConfig;
74
+ }
75
+ const type = String(raw.type ?? 'gmi');
76
+ switch (type) {
77
+ case 'tool':
78
+ return {
79
+ type: 'tool',
80
+ toolName: String(raw.toolName ?? raw.name ?? fallbackId),
81
+ ...(isRecord(raw.args) ? { args: raw.args } : {}),
82
+ };
83
+ case 'extension':
84
+ return {
85
+ type: 'extension',
86
+ extensionId: String(raw.extensionId ?? 'extension'),
87
+ method: String(raw.method ?? 'run'),
88
+ };
89
+ case 'human':
90
+ return {
91
+ type: 'human',
92
+ prompt: String(raw.prompt ?? raw.instructions ?? raw.role ?? `Review ${fallbackId}`),
93
+ };
94
+ case 'guardrail':
95
+ return {
96
+ type: 'guardrail',
97
+ guardrailIds: Array.isArray(raw.guardrailIds)
98
+ ? raw.guardrailIds.filter((value) => typeof value === 'string')
99
+ : [],
100
+ onViolation: raw.onViolation === 'reroute'
101
+ || raw.onViolation === 'warn'
102
+ || raw.onViolation === 'sanitize'
103
+ ? raw.onViolation
104
+ : 'block',
105
+ ...(typeof raw.rerouteTarget === 'string' ? { rerouteTarget: raw.rerouteTarget } : {}),
106
+ };
107
+ case 'router':
108
+ return {
109
+ type: 'router',
110
+ condition: (raw.condition?.type
111
+ ? raw.condition
112
+ : { type: 'expression', expr: `'${END}'` }),
113
+ };
114
+ case 'subgraph':
115
+ return {
116
+ type: 'subgraph',
117
+ graphId: String(raw.graphId ?? fallbackId),
118
+ ...(isRecord(raw.inputMapping) ? { inputMapping: raw.inputMapping } : {}),
119
+ ...(isRecord(raw.outputMapping) ? { outputMapping: raw.outputMapping } : {}),
120
+ };
121
+ case 'voice':
122
+ return {
123
+ type: 'voice',
124
+ voiceConfig: isRecord(raw.voiceConfig)
125
+ ? raw.voiceConfig
126
+ : { mode: 'conversation' },
127
+ };
128
+ case 'gmi':
129
+ default:
130
+ return {
131
+ type: 'gmi',
132
+ instructions: String(raw.instructions ?? raw.role ?? `Execute ${fallbackId}`),
133
+ ...(typeof raw.maxInternalIterations === 'number'
134
+ ? { maxInternalIterations: raw.maxInternalIterations }
135
+ : {}),
136
+ ...(typeof raw.parallelTools === 'boolean' ? { parallelTools: raw.parallelTools } : {}),
137
+ ...(typeof raw.temperature === 'number' ? { temperature: raw.temperature } : {}),
138
+ ...(typeof raw.maxTokens === 'number' ? { maxTokens: raw.maxTokens } : {}),
139
+ };
140
+ }
141
+ }
71
142
  function normalizeNode(raw) {
72
143
  const checkpoint = raw.checkpoint === true
73
144
  ? 'after'
@@ -80,15 +151,28 @@ function normalizeNode(raw) {
80
151
  return {
81
152
  id,
82
153
  type: String(raw.type ?? 'gmi'),
83
- executorConfig: raw.executorConfig ?? {
84
- type: 'gmi',
85
- instructions: String(raw.instructions ?? raw.role ?? `Execute ${id}`),
86
- },
154
+ executorConfig: buildExecutorConfig(raw, id),
87
155
  executionMode: raw.executionMode ?? 'single_turn',
88
156
  effectClass: raw.effectClass ?? 'read',
89
157
  checkpoint,
90
158
  complexity: clampComplexity(raw.complexity),
91
159
  ...(isRecord(raw.llm) ? { llm: raw.llm } : {}),
160
+ ...(typeof raw.timeout === 'number' ? { timeout: raw.timeout } : {}),
161
+ ...(isRecord(raw.retryPolicy) ? { retryPolicy: raw.retryPolicy } : {}),
162
+ ...(isRecord(raw.inputSchema) ? { inputSchema: raw.inputSchema } : {}),
163
+ ...(isRecord(raw.outputSchema) ? { outputSchema: raw.outputSchema } : {}),
164
+ ...(isRecord(raw.memoryPolicy)
165
+ ? { memoryPolicy: raw.memoryPolicy }
166
+ : {}),
167
+ ...(isRecord(raw.discoveryPolicy)
168
+ ? { discoveryPolicy: raw.discoveryPolicy }
169
+ : {}),
170
+ ...(isRecord(raw.personaPolicy)
171
+ ? { personaPolicy: raw.personaPolicy }
172
+ : {}),
173
+ ...(isRecord(raw.guardrailPolicy)
174
+ ? { guardrailPolicy: raw.guardrailPolicy }
175
+ : {}),
92
176
  };
93
177
  }
94
178
  function normalizeEdge(raw, index) {
@@ -149,71 +233,384 @@ function describeRole(node) {
149
233
  return `tool:${node.executorConfig.toolName}`;
150
234
  return node.id;
151
235
  }
236
+ function isToolLikeNode(node) {
237
+ return node.type === 'tool' || node.executorConfig.type === 'tool';
238
+ }
239
+ function computeGraphDepth(graph) {
240
+ const depths = new Map();
241
+ const queue = [];
242
+ for (const edge of graph.edges) {
243
+ if (edge.source !== START || edge.target === END)
244
+ continue;
245
+ queue.push({ nodeId: edge.target, depth: 1 });
246
+ }
247
+ while (queue.length > 0) {
248
+ const current = queue.shift();
249
+ const previousDepth = depths.get(current.nodeId) ?? Number.POSITIVE_INFINITY;
250
+ if (current.depth >= previousDepth)
251
+ continue;
252
+ depths.set(current.nodeId, current.depth);
253
+ for (const edge of graph.edges) {
254
+ if (edge.source !== current.nodeId || edge.target === END)
255
+ continue;
256
+ queue.push({ nodeId: edge.target, depth: current.depth + 1 });
257
+ }
258
+ }
259
+ return Math.max(0, ...depths.values());
260
+ }
261
+ function resolveSourceNodeId(spec, requesterNodeId) {
262
+ for (const key of ['afterNodeId', 'sourceNodeId', 'parentNodeId']) {
263
+ const value = spec[key];
264
+ if (typeof value === 'string' && value.trim())
265
+ return value.trim();
266
+ }
267
+ return requesterNodeId;
268
+ }
269
+ function resolveTargetNodeId(spec) {
270
+ for (const key of ['nodeId', 'targetNodeId', 'agentId']) {
271
+ const value = spec[key];
272
+ if (typeof value === 'string' && value.trim())
273
+ return value.trim();
274
+ }
275
+ return '';
276
+ }
277
+ function cloneEdge(edge, overrides, suffix) {
278
+ return {
279
+ ...edge,
280
+ ...overrides,
281
+ id: `${edge.id}_${suffix}`,
282
+ };
283
+ }
284
+ function dedupeEdges(edges) {
285
+ const seen = new Set();
286
+ const result = [];
287
+ for (const edge of edges) {
288
+ const key = JSON.stringify({
289
+ source: edge.source,
290
+ target: edge.target,
291
+ type: edge.type,
292
+ condition: edge.condition,
293
+ discoveryQuery: edge.discoveryQuery,
294
+ discoveryKind: edge.discoveryKind,
295
+ discoveryFallback: edge.discoveryFallback,
296
+ personalityCondition: edge.personalityCondition,
297
+ guardrailPolicy: edge.guardrailPolicy,
298
+ });
299
+ if (seen.has(key))
300
+ continue;
301
+ seen.add(key);
302
+ result.push(edge);
303
+ }
304
+ return result;
305
+ }
306
+ function buildInlineInsertionPatch(newNode, graph, sourceNodeId, reason, estimates) {
307
+ const outEdges = graph.edges.filter((edge) => edge.source === sourceNodeId);
308
+ const staticOutEdges = outEdges.filter((edge) => edge.type === 'static');
309
+ const addEdges = [
310
+ {
311
+ id: `expansion_edge_${sourceNodeId}_${newNode.id}`,
312
+ source: sourceNodeId,
313
+ target: newNode.id,
314
+ type: 'static',
315
+ },
316
+ ];
317
+ const rewireEdges = [];
318
+ if (staticOutEdges.length > 0) {
319
+ for (const edge of staticOutEdges) {
320
+ rewireEdges.push({
321
+ from: sourceNodeId,
322
+ to: edge.target,
323
+ newTarget: newNode.id,
324
+ });
325
+ addEdges.push(cloneEdge(edge, { source: newNode.id }, `after_${newNode.id}`));
326
+ }
327
+ }
328
+ else {
329
+ addEdges.push({
330
+ id: `expansion_edge_${newNode.id}_end`,
331
+ source: newNode.id,
332
+ target: END,
333
+ type: 'static',
334
+ });
335
+ }
336
+ return {
337
+ addNodes: [newNode],
338
+ addEdges: dedupeEdges(addEdges),
339
+ removeNodes: [],
340
+ rewireEdges,
341
+ reason,
342
+ estimatedCostDelta: estimates.cost,
343
+ estimatedLatencyDelta: estimates.latencyMs,
344
+ };
345
+ }
152
346
  function buildSpawnAgentPatch(spec, graph, requesterNodeId, reason) {
153
347
  const role = String(spec.role ?? spec.nodeId ?? spec.name ?? 'support_agent');
154
348
  const instructions = String(spec.instructions ?? `Handle ${role}`);
155
349
  const nodeId = typeof spec.nodeId === 'string' && spec.nodeId.trim()
156
350
  ? spec.nodeId.trim()
157
351
  : slugify(role);
352
+ const sourceNodeId = resolveSourceNodeId(spec, requesterNodeId);
158
353
  const newNode = normalizeNode({
159
354
  id: nodeId,
160
355
  type: spec.type ?? 'gmi',
161
356
  executorConfig: isRecord(spec.executorConfig)
162
357
  ? spec.executorConfig
163
- : {
164
- type: 'gmi',
358
+ : buildExecutorConfig({
359
+ ...spec,
360
+ type: spec.type ?? 'gmi',
165
361
  instructions,
166
- },
362
+ }, nodeId),
167
363
  executionMode: spec.executionMode ?? 'single_turn',
168
364
  effectClass: spec.effectClass ?? 'read',
169
365
  checkpoint: spec.checkpoint ?? 'after',
170
366
  complexity: spec.complexity,
367
+ ...(isRecord(spec.llm) ? { llm: spec.llm } : {}),
171
368
  });
172
- const outEdges = graph.edges.filter((edge) => edge.source === requesterNodeId);
173
- const staticOutEdges = outEdges.filter((edge) => edge.type === 'static');
174
- const addEdges = [];
175
- const rewireEdges = [];
176
- addEdges.push({
177
- id: `expansion_edge_${requesterNodeId}_${nodeId}`,
178
- source: requesterNodeId,
179
- target: nodeId,
180
- type: 'static',
369
+ return buildInlineInsertionPatch(newNode, graph, sourceNodeId, reason, {
370
+ cost: typeof spec.estimatedCostDelta === 'number' && Number.isFinite(spec.estimatedCostDelta)
371
+ ? spec.estimatedCostDelta
372
+ : 0.5,
373
+ latencyMs: typeof spec.estimatedLatencyDelta === 'number' && Number.isFinite(spec.estimatedLatencyDelta)
374
+ ? spec.estimatedLatencyDelta
375
+ : 30000,
181
376
  });
182
- if (staticOutEdges.length > 0) {
183
- for (const edge of staticOutEdges) {
184
- rewireEdges.push({
185
- from: requesterNodeId,
186
- to: edge.target,
187
- newTarget: nodeId,
188
- });
189
- addEdges.push({
190
- id: `expansion_edge_${nodeId}_${edge.target}`,
191
- source: nodeId,
192
- target: edge.target,
193
- type: 'static',
194
- });
377
+ }
378
+ function buildRemoveAgentPatch(spec, graph, reason) {
379
+ const nodeId = resolveTargetNodeId(spec);
380
+ if (!nodeId)
381
+ return null;
382
+ const incomingStatic = graph.edges.filter((edge) => edge.target === nodeId && edge.type === 'static');
383
+ const outgoingStatic = graph.edges.filter((edge) => edge.source === nodeId && edge.type === 'static');
384
+ const nextTargets = outgoingStatic.length > 0
385
+ ? outgoingStatic.map((edge) => edge.target)
386
+ : [END];
387
+ const addEdges = [];
388
+ for (const edge of incomingStatic) {
389
+ for (const nextTarget of nextTargets) {
390
+ if (edge.source === nextTarget)
391
+ continue;
392
+ addEdges.push(cloneEdge(edge, { target: nextTarget }, `remove_${nodeId}_${nextTarget.replace(/[^a-zA-Z0-9_-]/g, '_')}`));
195
393
  }
196
394
  }
395
+ return {
396
+ addNodes: [],
397
+ addEdges: dedupeEdges(addEdges),
398
+ removeNodes: [nodeId],
399
+ rewireEdges: [],
400
+ reason,
401
+ estimatedCostDelta: 0,
402
+ estimatedLatencyDelta: 0,
403
+ };
404
+ }
405
+ function buildReassignedNode(existing, spec) {
406
+ const nextType = (typeof spec.type === 'string' && spec.type.trim()
407
+ ? spec.type.trim()
408
+ : existing.type);
409
+ let executorConfig = existing.executorConfig;
410
+ if (isRecord(spec.executorConfig)) {
411
+ executorConfig = spec.executorConfig;
412
+ }
197
413
  else {
414
+ const nextRole = typeof spec.role === 'string' ? spec.role : undefined;
415
+ const nextInstructions = typeof spec.instructions === 'string' ? spec.instructions : nextRole;
416
+ if (nextType === 'gmi' && nextInstructions) {
417
+ executorConfig = {
418
+ type: 'gmi',
419
+ ...(existing.executorConfig.type === 'gmi' ? existing.executorConfig : {}),
420
+ instructions: nextInstructions,
421
+ };
422
+ }
423
+ else if (nextType === 'tool') {
424
+ const existingArgs = existing.executorConfig.type === 'tool' ? existing.executorConfig.args : undefined;
425
+ executorConfig = {
426
+ type: 'tool',
427
+ toolName: String(spec.toolName
428
+ ?? (existing.executorConfig.type === 'tool' ? existing.executorConfig.toolName : existing.id)),
429
+ ...(isRecord(spec.args)
430
+ ? { args: spec.args }
431
+ : existingArgs
432
+ ? { args: existingArgs }
433
+ : {}),
434
+ };
435
+ }
436
+ else if (nextType === 'human' && nextInstructions) {
437
+ executorConfig = { type: 'human', prompt: nextInstructions };
438
+ }
439
+ else if (nextType !== existing.type) {
440
+ executorConfig = buildExecutorConfig({ ...spec, type: nextType }, existing.id);
441
+ }
442
+ }
443
+ return {
444
+ ...existing,
445
+ type: nextType,
446
+ executorConfig,
447
+ executionMode: spec.executionMode ?? existing.executionMode,
448
+ effectClass: spec.effectClass ?? existing.effectClass,
449
+ checkpoint: spec.checkpoint === true
450
+ ? 'after'
451
+ : spec.checkpoint === false
452
+ ? 'none'
453
+ : spec.checkpoint ?? existing.checkpoint,
454
+ complexity: clampComplexity(spec.complexity)
455
+ ?? existing.complexity,
456
+ ...(nextType === 'gmi'
457
+ ? isRecord(spec.llm)
458
+ ? { llm: spec.llm }
459
+ : existing.llm
460
+ ? { llm: existing.llm }
461
+ : {}
462
+ : {}),
463
+ ...('timeout' in spec && typeof spec.timeout === 'number' ? { timeout: spec.timeout } : {}),
464
+ };
465
+ }
466
+ function buildReassignRolePatch(spec, graph, reason) {
467
+ const nodeId = resolveTargetNodeId(spec);
468
+ if (!nodeId)
469
+ return null;
470
+ const existing = graph.nodes.find((node) => node.id === nodeId);
471
+ if (!existing)
472
+ return null;
473
+ const replacementNode = buildReassignedNode(existing, spec);
474
+ const incomingEdges = graph.edges.filter((edge) => edge.target === nodeId);
475
+ const outgoingEdges = graph.edges.filter((edge) => edge.source === nodeId);
476
+ const addEdges = dedupeEdges([
477
+ ...incomingEdges.map((edge) => cloneEdge(edge, { target: nodeId }, 'reassign_in')),
478
+ ...outgoingEdges.map((edge) => cloneEdge(edge, { source: nodeId }, 'reassign_out')),
479
+ ]);
480
+ return {
481
+ addNodes: [replacementNode],
482
+ addEdges,
483
+ removeNodes: [nodeId],
484
+ rewireEdges: [],
485
+ reason,
486
+ estimatedCostDelta: typeof spec.estimatedCostDelta === 'number' && Number.isFinite(spec.estimatedCostDelta)
487
+ ? spec.estimatedCostDelta
488
+ : 0.1,
489
+ estimatedLatencyDelta: typeof spec.estimatedLatencyDelta === 'number' && Number.isFinite(spec.estimatedLatencyDelta)
490
+ ? spec.estimatedLatencyDelta
491
+ : 0,
492
+ };
493
+ }
494
+ function buildAddToolPatch(spec, graph, requesterNodeId, reason) {
495
+ const sourceNodeId = resolveSourceNodeId(spec, requesterNodeId);
496
+ const toolName = typeof spec.toolName === 'string' && spec.toolName.trim()
497
+ ? spec.toolName.trim()
498
+ : typeof spec.name === 'string' && spec.name.trim()
499
+ ? spec.name.trim()
500
+ : '';
501
+ if (!toolName && !isRecord(spec.executorConfig))
502
+ return null;
503
+ const nodeId = typeof spec.nodeId === 'string' && spec.nodeId.trim()
504
+ ? spec.nodeId.trim()
505
+ : slugify(toolName || 'tool_step');
506
+ const newNode = normalizeNode({
507
+ ...spec,
508
+ id: nodeId,
509
+ type: 'tool',
510
+ executorConfig: isRecord(spec.executorConfig)
511
+ ? spec.executorConfig
512
+ : {
513
+ type: 'tool',
514
+ toolName,
515
+ ...(isRecord(spec.args) ? { args: spec.args } : {}),
516
+ },
517
+ executionMode: spec.executionMode ?? 'single_turn',
518
+ effectClass: spec.effectClass ?? 'read',
519
+ checkpoint: spec.checkpoint ?? 'after',
520
+ });
521
+ return buildInlineInsertionPatch(newNode, graph, sourceNodeId, reason, {
522
+ cost: typeof spec.estimatedCostDelta === 'number' && Number.isFinite(spec.estimatedCostDelta)
523
+ ? spec.estimatedCostDelta
524
+ : 0.15,
525
+ latencyMs: typeof spec.estimatedLatencyDelta === 'number' && Number.isFinite(spec.estimatedLatencyDelta)
526
+ ? spec.estimatedLatencyDelta
527
+ : 5000,
528
+ });
529
+ }
530
+ function buildForkBranchPatch(spec, graph, requesterNodeId, reason) {
531
+ const sourceNodeId = resolveSourceNodeId(spec, requesterNodeId);
532
+ const branchNodes = Array.isArray(spec.nodes)
533
+ ? spec.nodes.filter(isRecord).map((node) => normalizeNode(node))
534
+ : [];
535
+ if (branchNodes.length === 0) {
536
+ const role = String(spec.role ?? spec.nodeId ?? spec.name ?? 'branch_worker');
537
+ const branchNodeId = typeof spec.nodeId === 'string' && spec.nodeId.trim()
538
+ ? spec.nodeId.trim()
539
+ : slugify(role);
540
+ branchNodes.push(normalizeNode({
541
+ ...spec,
542
+ id: branchNodeId,
543
+ type: spec.type ?? 'gmi',
544
+ executorConfig: isRecord(spec.executorConfig)
545
+ ? spec.executorConfig
546
+ : buildExecutorConfig({
547
+ ...spec,
548
+ type: spec.type ?? 'gmi',
549
+ instructions: spec.instructions ?? `Handle ${role}`,
550
+ }, branchNodeId),
551
+ executionMode: spec.executionMode ?? 'single_turn',
552
+ effectClass: spec.effectClass ?? 'read',
553
+ checkpoint: spec.checkpoint ?? 'after',
554
+ complexity: spec.complexity,
555
+ }));
556
+ }
557
+ if (branchNodes.length === 0)
558
+ return null;
559
+ const explicitJoinTargets = Array.isArray(spec.joinTargets)
560
+ ? spec.joinTargets.filter((value) => typeof value === 'string' && value.trim().length > 0)
561
+ : [];
562
+ const joinTarget = typeof spec.joinTarget === 'string' && spec.joinTarget.trim()
563
+ ? spec.joinTarget.trim()
564
+ : typeof spec.joinTargetId === 'string' && spec.joinTargetId.trim()
565
+ ? spec.joinTargetId.trim()
566
+ : '';
567
+ const sourceStaticTargets = graph.edges
568
+ .filter((edge) => edge.source === sourceNodeId && edge.type === 'static')
569
+ .map((edge) => edge.target);
570
+ const joinTargets = explicitJoinTargets.length > 0
571
+ ? explicitJoinTargets
572
+ : joinTarget
573
+ ? [joinTarget]
574
+ : sourceStaticTargets.length > 0
575
+ ? sourceStaticTargets
576
+ : [END];
577
+ const addEdges = [
578
+ {
579
+ id: `expansion_edge_${sourceNodeId}_${branchNodes[0].id}`,
580
+ source: sourceNodeId,
581
+ target: branchNodes[0].id,
582
+ type: 'static',
583
+ },
584
+ ];
585
+ for (let index = 0; index < branchNodes.length - 1; index++) {
198
586
  addEdges.push({
199
- id: `expansion_edge_${nodeId}_end`,
200
- source: nodeId,
201
- target: END,
587
+ id: `expansion_edge_${branchNodes[index].id}_${branchNodes[index + 1].id}`,
588
+ source: branchNodes[index].id,
589
+ target: branchNodes[index + 1].id,
590
+ type: 'static',
591
+ });
592
+ }
593
+ const lastNodeId = branchNodes[branchNodes.length - 1].id;
594
+ for (const target of joinTargets) {
595
+ addEdges.push({
596
+ id: `expansion_edge_${lastNodeId}_${target.replace(/[^a-zA-Z0-9_-]/g, '_')}`,
597
+ source: lastNodeId,
598
+ target,
202
599
  type: 'static',
203
600
  });
204
601
  }
205
602
  return {
206
- addNodes: [newNode],
207
- addEdges,
603
+ addNodes: branchNodes,
604
+ addEdges: dedupeEdges(addEdges),
208
605
  removeNodes: [],
209
- rewireEdges,
606
+ rewireEdges: [],
210
607
  reason,
211
608
  estimatedCostDelta: typeof spec.estimatedCostDelta === 'number' && Number.isFinite(spec.estimatedCostDelta)
212
609
  ? spec.estimatedCostDelta
213
- : 0.5,
610
+ : 0.35 * branchNodes.length,
214
611
  estimatedLatencyDelta: typeof spec.estimatedLatencyDelta === 'number' && Number.isFinite(spec.estimatedLatencyDelta)
215
612
  ? spec.estimatedLatencyDelta
216
- : 30000,
613
+ : 20000 * branchNodes.length,
217
614
  };
218
615
  }
219
616
  function buildManageGraphPatch(request, graph, requesterNodeId) {
@@ -229,18 +626,16 @@ function buildManageGraphPatch(request, graph, requesterNodeId) {
229
626
  return buildSpawnAgentPatch(spec, graph, requesterNodeId, reason);
230
627
  }
231
628
  if (action === 'remove_agent') {
232
- const nodeId = typeof spec.nodeId === 'string' ? spec.nodeId.trim() : '';
233
- if (!nodeId)
234
- return null;
235
- return {
236
- addNodes: [],
237
- addEdges: [],
238
- removeNodes: [nodeId],
239
- rewireEdges: [],
240
- reason,
241
- estimatedCostDelta: 0,
242
- estimatedLatencyDelta: 0,
243
- };
629
+ return buildRemoveAgentPatch(spec, graph, reason);
630
+ }
631
+ if (action === 'reassign_role') {
632
+ return buildReassignRolePatch(spec, graph, reason);
633
+ }
634
+ if (action === 'add_tool') {
635
+ return buildAddToolPatch(spec, graph, requesterNodeId, reason);
636
+ }
637
+ if (action === 'fork_branch') {
638
+ return buildForkBranchPatch(spec, graph, requesterNodeId, reason);
244
639
  }
245
640
  return null;
246
641
  }
@@ -253,7 +648,6 @@ export function createMissionExpansionHandler(options) {
253
648
  let currentEstimatedCost = options.initialEstimatedCost ?? 0;
254
649
  let currentExpansions = 0;
255
650
  let currentToolForges = 0;
256
- let currentDepth = 0;
257
651
  const assignProviders = (patch) => {
258
652
  const nodes = [...patch.addNodes];
259
653
  const gmiNodes = nodes.filter((node) => node.type === 'gmi' && !node.llm);
@@ -358,6 +752,11 @@ export function createMissionExpansionHandler(options) {
358
752
  return null;
359
753
  }
360
754
  const assignedPatch = assignProviders(patch);
755
+ const currentDepth = computeGraphDepth(context.graph);
756
+ const previewGraph = expander.applyPatch(context.graph, assignedPatch);
757
+ const nextDepth = computeGraphDepth(previewGraph);
758
+ const patchToolForgeDelta = assignedPatch.addNodes.filter(isToolLikeNode).length;
759
+ const patchDepthDelta = Math.max(0, nextDepth - currentDepth);
361
760
  const patchAgentDelta = assignedPatch.addNodes.length - (assignedPatch.removeNodes?.length ?? 0);
362
761
  const state = {
363
762
  currentCost: currentEstimatedCost,
@@ -367,8 +766,8 @@ export function createMissionExpansionHandler(options) {
367
766
  currentDepth,
368
767
  patchCostDelta: assignedPatch.estimatedCostDelta,
369
768
  patchAgentDelta,
370
- patchToolForgeDelta: 0,
371
- patchDepthDelta: 0,
769
+ patchToolForgeDelta,
770
+ patchDepthDelta,
372
771
  };
373
772
  const events = [
374
773
  {
@@ -400,9 +799,10 @@ export function createMissionExpansionHandler(options) {
400
799
  });
401
800
  return { events };
402
801
  }
403
- const nextGraph = expander.applyPatch(context.graph, assignedPatch);
802
+ const nextGraph = previewGraph;
404
803
  currentEstimatedCost += assignedPatch.estimatedCostDelta;
405
804
  currentExpansions += 1;
805
+ currentToolForges += patchToolForgeDelta;
406
806
  events.push({ type: 'mission:expansion_approved', by: 'auto' });
407
807
  events.push({
408
808
  type: 'mission:expansion_applied',