@open-multi-agent/core 1.4.1 → 1.5.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.
Files changed (115) hide show
  1. package/README.md +103 -51
  2. package/dist/agent/agent.d.ts.map +1 -1
  3. package/dist/agent/agent.js +5 -0
  4. package/dist/agent/agent.js.map +1 -1
  5. package/dist/agent/runner.d.ts +12 -0
  6. package/dist/agent/runner.d.ts.map +1 -1
  7. package/dist/agent/runner.js +48 -12
  8. package/dist/agent/runner.js.map +1 -1
  9. package/dist/cli/oma.d.ts +10 -2
  10. package/dist/cli/oma.d.ts.map +1 -1
  11. package/dist/cli/oma.js +10 -5
  12. package/dist/cli/oma.js.map +1 -1
  13. package/dist/dashboard/render-team-run-dashboard.d.ts.map +1 -1
  14. package/dist/dashboard/render-team-run-dashboard.js +177 -84
  15. package/dist/dashboard/render-team-run-dashboard.js.map +1 -1
  16. package/dist/llm/adapter.d.ts +3 -1
  17. package/dist/llm/adapter.d.ts.map +1 -1
  18. package/dist/llm/adapter.js +10 -0
  19. package/dist/llm/adapter.js.map +1 -1
  20. package/dist/llm/ai-sdk.d.ts +5 -1
  21. package/dist/llm/ai-sdk.d.ts.map +1 -1
  22. package/dist/llm/ai-sdk.js +50 -9
  23. package/dist/llm/ai-sdk.js.map +1 -1
  24. package/dist/llm/anthropic.d.ts +3 -0
  25. package/dist/llm/anthropic.d.ts.map +1 -1
  26. package/dist/llm/anthropic.js +29 -13
  27. package/dist/llm/anthropic.js.map +1 -1
  28. package/dist/llm/azure-openai.d.ts +3 -0
  29. package/dist/llm/azure-openai.d.ts.map +1 -1
  30. package/dist/llm/azure-openai.js +8 -3
  31. package/dist/llm/azure-openai.js.map +1 -1
  32. package/dist/llm/bedrock.d.ts +3 -0
  33. package/dist/llm/bedrock.d.ts.map +1 -1
  34. package/dist/llm/bedrock.js +49 -21
  35. package/dist/llm/bedrock.js.map +1 -1
  36. package/dist/llm/copilot.d.ts +3 -0
  37. package/dist/llm/copilot.d.ts.map +1 -1
  38. package/dist/llm/copilot.js +8 -3
  39. package/dist/llm/copilot.js.map +1 -1
  40. package/dist/llm/deepseek.d.ts +9 -2
  41. package/dist/llm/deepseek.d.ts.map +1 -1
  42. package/dist/llm/deepseek.js +21 -2
  43. package/dist/llm/deepseek.js.map +1 -1
  44. package/dist/llm/doubao.d.ts +21 -0
  45. package/dist/llm/doubao.d.ts.map +1 -0
  46. package/dist/llm/doubao.js +24 -0
  47. package/dist/llm/doubao.js.map +1 -0
  48. package/dist/llm/gemini.d.ts +3 -0
  49. package/dist/llm/gemini.d.ts.map +1 -1
  50. package/dist/llm/gemini.js +40 -19
  51. package/dist/llm/gemini.js.map +1 -1
  52. package/dist/llm/mimo.d.ts +24 -0
  53. package/dist/llm/mimo.d.ts.map +1 -0
  54. package/dist/llm/mimo.js +30 -0
  55. package/dist/llm/mimo.js.map +1 -0
  56. package/dist/llm/openai-common.d.ts +10 -3
  57. package/dist/llm/openai-common.d.ts.map +1 -1
  58. package/dist/llm/openai-common.js +95 -8
  59. package/dist/llm/openai-common.js.map +1 -1
  60. package/dist/llm/openai.d.ts +24 -0
  61. package/dist/llm/openai.d.ts.map +1 -1
  62. package/dist/llm/openai.js +49 -4
  63. package/dist/llm/openai.js.map +1 -1
  64. package/dist/llm/reasoning-fallback.d.ts +132 -0
  65. package/dist/llm/reasoning-fallback.d.ts.map +1 -0
  66. package/dist/llm/reasoning-fallback.js +128 -0
  67. package/dist/llm/reasoning-fallback.js.map +1 -0
  68. package/dist/orchestrator/orchestrator.d.ts.map +1 -1
  69. package/dist/orchestrator/orchestrator.js +72 -10
  70. package/dist/orchestrator/orchestrator.js.map +1 -1
  71. package/dist/tool/built-in/bash.d.ts +1 -1
  72. package/dist/tool/built-in/bash.d.ts.map +1 -1
  73. package/dist/tool/built-in/bash.js +60 -7
  74. package/dist/tool/built-in/bash.js.map +1 -1
  75. package/dist/tool/built-in/file-edit.d.ts.map +1 -1
  76. package/dist/tool/built-in/file-edit.js +13 -8
  77. package/dist/tool/built-in/file-edit.js.map +1 -1
  78. package/dist/tool/built-in/file-read.d.ts.map +1 -1
  79. package/dist/tool/built-in/file-read.js +9 -4
  80. package/dist/tool/built-in/file-read.js.map +1 -1
  81. package/dist/tool/built-in/file-write.d.ts.map +1 -1
  82. package/dist/tool/built-in/file-write.js +11 -6
  83. package/dist/tool/built-in/file-write.js.map +1 -1
  84. package/dist/tool/built-in/fs-walk.d.ts.map +1 -1
  85. package/dist/tool/built-in/fs-walk.js +6 -3
  86. package/dist/tool/built-in/fs-walk.js.map +1 -1
  87. package/dist/tool/built-in/glob.d.ts.map +1 -1
  88. package/dist/tool/built-in/glob.js +10 -4
  89. package/dist/tool/built-in/glob.js.map +1 -1
  90. package/dist/tool/built-in/grep.d.ts.map +1 -1
  91. package/dist/tool/built-in/grep.js +15 -6
  92. package/dist/tool/built-in/grep.js.map +1 -1
  93. package/dist/tool/built-in/path-safety.d.ts +30 -0
  94. package/dist/tool/built-in/path-safety.d.ts.map +1 -0
  95. package/dist/tool/built-in/path-safety.js +106 -0
  96. package/dist/tool/built-in/path-safety.js.map +1 -0
  97. package/dist/tool/mcp.d.ts.map +1 -1
  98. package/dist/tool/mcp.js +58 -33
  99. package/dist/tool/mcp.js.map +1 -1
  100. package/dist/types.d.ts +159 -4
  101. package/dist/types.d.ts.map +1 -1
  102. package/dist/utils/redaction.d.ts +4 -0
  103. package/dist/utils/redaction.d.ts.map +1 -0
  104. package/dist/utils/redaction.js +78 -0
  105. package/dist/utils/redaction.js.map +1 -0
  106. package/package.json +1 -2
  107. package/docs/DECISIONS.md +0 -49
  108. package/docs/cli.md +0 -265
  109. package/docs/context-management.md +0 -24
  110. package/docs/featured-partner.md +0 -28
  111. package/docs/observability.md +0 -56
  112. package/docs/providers/minimax.md +0 -75
  113. package/docs/providers.md +0 -78
  114. package/docs/shared-memory.md +0 -27
  115. package/docs/tool-configuration.md +0 -152
@@ -0,0 +1,132 @@
1
+ /**
2
+ * @fileoverview Shared `<thinking>` text fallback for {@link ReasoningBlock}
3
+ * round-tripping across adapter boundaries.
4
+ *
5
+ * When an outbound IR-to-native conversion encounters a {@link ReasoningBlock}
6
+ * that the target adapter cannot natively echo — either because the wire
7
+ * protocol does not accept reasoning input at all
8
+ * ({@link LLMAdapter.capabilities.echoesReasoning} `=== 'never'`) or because
9
+ * the block's {@link ReasoningBlock.provenance} does not match the target
10
+ * adapter for an `'own-issued'` adapter — this helper converts the block to
11
+ * an inline `<thinking>...</thinking>` text snippet that callers prepend to
12
+ * the next outgoing text part.
13
+ *
14
+ * SAFETY CONTRACT (one-way invariant):
15
+ * The reverse direction — parsing `<thinking>` text back into a
16
+ * {@link ReasoningBlock} — must never happen. A reconstructed block would
17
+ * carry no verifiable signature and would be rejected if re-sent to
18
+ * Anthropic / Bedrock / Gemini 3. `ReasoningBlock` instances are only ever
19
+ * produced from native API extraction (and always stamped with
20
+ * `provenance`), never from text parsing.
21
+ *
22
+ * Wiring (post #223 Phase 2): every adapter outbound path consults
23
+ * {@link ReasoningOutboundOptions} resolved from
24
+ * {@link AgentConfig.preserveReasoningAsText} +
25
+ * {@link AgentConfig.compressReasoningText}. When opt-in is off, reasoning
26
+ * blocks that can't be native-echoed are dropped silently (preserving
27
+ * pre-Phase-2 behaviour). When opt-in is on, they pass through
28
+ * {@link reasoningBlockToInlineText} and become inline `<thinking>` text.
29
+ */
30
+ import type { ReasoningBlock } from '../types.js';
31
+ /**
32
+ * Default maximum character budget per `<thinking>` block after truncation.
33
+ * Aligned with the value used by the OpenAI-family private replay helper
34
+ * shipped in #234 so the Phase 2 consolidation is behaviour-preserving.
35
+ */
36
+ export declare const DEFAULT_REASONING_FALLBACK_MAX_CHARS = 1200;
37
+ /**
38
+ * Sentinel value the resolver returns when the caller explicitly disables
39
+ * truncation (`compressReasoningText: false`). Exported so the special-case
40
+ * branch in {@link resolveMaxChars} stays searchable — removing the branch
41
+ * would silently re-introduce truncation for users who opted out.
42
+ *
43
+ * Equal to `Number.POSITIVE_INFINITY`; the resolver maps it to
44
+ * `Number.MAX_SAFE_INTEGER` so `text.length <= maxChars` is always true.
45
+ */
46
+ export declare const NO_TRUNCATION: number;
47
+ export interface ReasoningFallbackOptions {
48
+ /**
49
+ * Hard upper bound on the inner text length (the `<thinking>` wrapper
50
+ * itself is not counted). Special values:
51
+ * - `undefined` → defaults to {@link DEFAULT_REASONING_FALLBACK_MAX_CHARS}.
52
+ * - {@link NO_TRUNCATION} (`Number.POSITIVE_INFINITY`) → no truncation;
53
+ * the entire reasoning text passes through unchanged. This is the
54
+ * sentinel returned by {@link resolveReasoningOutboundMaxChars} when
55
+ * the caller sets `compressReasoningText: false`.
56
+ * - Any other non-finite value (`NaN`, `-Infinity`) → clamped to 1
57
+ * (defensive — these values are invalid input).
58
+ * - Finite values below 1 → clamped to 1.
59
+ * - Finite values ≥ 1 → used as-is (floored).
60
+ */
61
+ readonly maxChars?: number;
62
+ }
63
+ /**
64
+ * Outbound-conversion options derived from {@link AgentConfig}'s reasoning
65
+ * fields. Threaded through every adapter's outbound IR-to-native conversion
66
+ * so each can opt the user into the cross-provider `<thinking>` text
67
+ * fallback (see #223). Internal to the LLM layer — adapters pick the values
68
+ * out of {@link LLMChatOptions} themselves.
69
+ */
70
+ export interface ReasoningOutboundOptions {
71
+ /** Mirrors {@link LLMChatOptions.preserveReasoningAsText}. */
72
+ readonly preserveReasoningAsText?: boolean;
73
+ /** Mirrors {@link LLMChatOptions.compressReasoningText}. */
74
+ readonly compressReasoningText?: boolean | {
75
+ readonly minChars?: number;
76
+ };
77
+ /**
78
+ * Adapter name to use for native `reasoning_content` echo on outbound
79
+ * assistant messages inside a tool-calling conversation. Wired by adapters
80
+ * whose {@link LLMAdapter.capabilities.echoesReasoning} is `'tool-use-only'`
81
+ * (currently DeepSeek V4 thinking-mode — see PR #251 / DeepSeek API spec).
82
+ *
83
+ * When set, each assistant message is scanned for {@link ReasoningBlock}s
84
+ * whose {@link ReasoningBlock.provenance} matches this value. The collected
85
+ * reasoning is attached as a `reasoning_content` field on the outbound
86
+ * payload (a non-standard OpenAI-compat field) IF AND ONLY IF the overall
87
+ * conversation contains at least one `tool_use` block somewhere in its
88
+ * history. Non-tool conversations skip the attachment entirely.
89
+ *
90
+ * Foreign-provenance blocks fall through to the {@link preserveReasoningAsText}
91
+ * `<thinking>` text path when that flag is on, so the two mechanisms
92
+ * compose: native echo for own-provenance + tool-use; text fallback for
93
+ * everything else.
94
+ *
95
+ * Adapters that don't need this leave it unset (default `'never'` and
96
+ * `'own-issued'` capability paths).
97
+ */
98
+ readonly nativeReasoningEchoProvider?: string;
99
+ }
100
+ /**
101
+ * Resolve {@link ReasoningOutboundOptions} to the `maxChars` value accepted
102
+ * by {@link reasoningBlockToInlineText}. Encodes the
103
+ * default-on-when-preserve-on semantics documented in #223:
104
+ *
105
+ * - `preserve=false` → returns `undefined` (fallback never runs;
106
+ * callers must check `preserve` themselves)
107
+ * - `compress=undefined`/`true`→ returns `undefined` so the helper applies
108
+ * its own default head+tail budget
109
+ * - `compress=false` → returns {@link NO_TRUNCATION}; the helper
110
+ * treats this as a no-op cap (full text)
111
+ * - `compress={minChars: N}` → `N` (the threshold value also serves as
112
+ * the truncation cap)
113
+ */
114
+ export declare function resolveReasoningOutboundMaxChars(options: ReasoningOutboundOptions | undefined): number | undefined;
115
+ /**
116
+ * Convert a {@link ReasoningBlock} into its `<thinking>...</thinking>` text
117
+ * representation for outbound replay through adapters that cannot natively
118
+ * echo reasoning.
119
+ *
120
+ * Behaviour:
121
+ * - `redactedData` non-empty → returns {@link REDACTED_PLACEHOLDER} exactly.
122
+ * Plaintext is unavailable so emitting the original `text` (which is
123
+ * conventionally empty for redacted blocks) would yield an empty
124
+ * `<thinking></thinking>` and confuse the next model; the placeholder
125
+ * signals that reasoning occurred without leaking any content.
126
+ * - Empty non-redacted text → returns the empty string. Callers should
127
+ * skip emitting an assistant-message slot rather than pushing an empty
128
+ * payload.
129
+ * - Otherwise → returns `<thinking>${truncate(text)}</thinking>`.
130
+ */
131
+ export declare function reasoningBlockToInlineText(block: ReasoningBlock, options?: ReasoningFallbackOptions): string;
132
+ //# sourceMappingURL=reasoning-fallback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reasoning-fallback.d.ts","sourceRoot":"","sources":["../../src/llm/reasoning-fallback.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAEjD;;;;GAIG;AACH,eAAO,MAAM,oCAAoC,OAAO,CAAA;AAExD;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,QAA2B,CAAA;AAQrD,MAAM,WAAW,wBAAwB;IACvC;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAC3B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,wBAAwB;IACvC,8DAA8D;IAC9D,QAAQ,CAAC,uBAAuB,CAAC,EAAE,OAAO,CAAA;IAC1C,4DAA4D;IAC5D,QAAQ,CAAC,qBAAqB,CAAC,EAAE,OAAO,GAAG;QAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACzE;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,QAAQ,CAAC,2BAA2B,CAAC,EAAE,MAAM,CAAA;CAC9C;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gCAAgC,CAC9C,OAAO,EAAE,wBAAwB,GAAG,SAAS,GAC5C,MAAM,GAAG,SAAS,CAQpB;AA0BD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,cAAc,EACrB,OAAO,CAAC,EAAE,wBAAwB,GACjC,MAAM,CAOR"}
@@ -0,0 +1,128 @@
1
+ /**
2
+ * @fileoverview Shared `<thinking>` text fallback for {@link ReasoningBlock}
3
+ * round-tripping across adapter boundaries.
4
+ *
5
+ * When an outbound IR-to-native conversion encounters a {@link ReasoningBlock}
6
+ * that the target adapter cannot natively echo — either because the wire
7
+ * protocol does not accept reasoning input at all
8
+ * ({@link LLMAdapter.capabilities.echoesReasoning} `=== 'never'`) or because
9
+ * the block's {@link ReasoningBlock.provenance} does not match the target
10
+ * adapter for an `'own-issued'` adapter — this helper converts the block to
11
+ * an inline `<thinking>...</thinking>` text snippet that callers prepend to
12
+ * the next outgoing text part.
13
+ *
14
+ * SAFETY CONTRACT (one-way invariant):
15
+ * The reverse direction — parsing `<thinking>` text back into a
16
+ * {@link ReasoningBlock} — must never happen. A reconstructed block would
17
+ * carry no verifiable signature and would be rejected if re-sent to
18
+ * Anthropic / Bedrock / Gemini 3. `ReasoningBlock` instances are only ever
19
+ * produced from native API extraction (and always stamped with
20
+ * `provenance`), never from text parsing.
21
+ *
22
+ * Wiring (post #223 Phase 2): every adapter outbound path consults
23
+ * {@link ReasoningOutboundOptions} resolved from
24
+ * {@link AgentConfig.preserveReasoningAsText} +
25
+ * {@link AgentConfig.compressReasoningText}. When opt-in is off, reasoning
26
+ * blocks that can't be native-echoed are dropped silently (preserving
27
+ * pre-Phase-2 behaviour). When opt-in is on, they pass through
28
+ * {@link reasoningBlockToInlineText} and become inline `<thinking>` text.
29
+ */
30
+ /**
31
+ * Default maximum character budget per `<thinking>` block after truncation.
32
+ * Aligned with the value used by the OpenAI-family private replay helper
33
+ * shipped in #234 so the Phase 2 consolidation is behaviour-preserving.
34
+ */
35
+ export const DEFAULT_REASONING_FALLBACK_MAX_CHARS = 1200;
36
+ /**
37
+ * Sentinel value the resolver returns when the caller explicitly disables
38
+ * truncation (`compressReasoningText: false`). Exported so the special-case
39
+ * branch in {@link resolveMaxChars} stays searchable — removing the branch
40
+ * would silently re-introduce truncation for users who opted out.
41
+ *
42
+ * Equal to `Number.POSITIVE_INFINITY`; the resolver maps it to
43
+ * `Number.MAX_SAFE_INTEGER` so `text.length <= maxChars` is always true.
44
+ */
45
+ export const NO_TRUNCATION = Number.POSITIVE_INFINITY;
46
+ /** Marker inserted between head and tail when reasoning text is truncated. */
47
+ const TRUNCATION_MARKER = '...[truncated]...';
48
+ /** Placeholder emitted in place of opaque encrypted (redacted) reasoning. */
49
+ const REDACTED_PLACEHOLDER = '<thinking>[redacted]</thinking>';
50
+ /**
51
+ * Resolve {@link ReasoningOutboundOptions} to the `maxChars` value accepted
52
+ * by {@link reasoningBlockToInlineText}. Encodes the
53
+ * default-on-when-preserve-on semantics documented in #223:
54
+ *
55
+ * - `preserve=false` → returns `undefined` (fallback never runs;
56
+ * callers must check `preserve` themselves)
57
+ * - `compress=undefined`/`true`→ returns `undefined` so the helper applies
58
+ * its own default head+tail budget
59
+ * - `compress=false` → returns {@link NO_TRUNCATION}; the helper
60
+ * treats this as a no-op cap (full text)
61
+ * - `compress={minChars: N}` → `N` (the threshold value also serves as
62
+ * the truncation cap)
63
+ */
64
+ export function resolveReasoningOutboundMaxChars(options) {
65
+ if (options?.preserveReasoningAsText !== true)
66
+ return undefined;
67
+ const compress = options.compressReasoningText;
68
+ if (compress === false)
69
+ return NO_TRUNCATION;
70
+ if (compress === undefined || compress === true) {
71
+ return undefined; // helper applies its own default when maxChars omitted
72
+ }
73
+ return compress.minChars; // undefined → helper default, number → that cap
74
+ }
75
+ function resolveMaxChars(value) {
76
+ if (value === undefined)
77
+ return DEFAULT_REASONING_FALLBACK_MAX_CHARS;
78
+ if (value === NO_TRUNCATION)
79
+ return Number.MAX_SAFE_INTEGER;
80
+ if (!Number.isFinite(value))
81
+ return 1;
82
+ const floored = Math.floor(value);
83
+ if (floored < 1)
84
+ return 1;
85
+ return floored;
86
+ }
87
+ /**
88
+ * Truncate `text` to at most `maxChars` characters via a head+tail excerpt
89
+ * with a `...[truncated]...` marker. The head receives ~70% of the budget
90
+ * so the model sees more of the leading reasoning steps. When `maxChars` is
91
+ * smaller than the marker itself, falls back to a simple head slice.
92
+ */
93
+ function truncate(text, maxChars) {
94
+ if (text.length <= maxChars)
95
+ return text;
96
+ if (TRUNCATION_MARKER.length >= maxChars)
97
+ return text.slice(0, maxChars);
98
+ const budget = maxChars - TRUNCATION_MARKER.length;
99
+ const head = Math.ceil(budget * 0.7);
100
+ const tail = budget - head;
101
+ return `${text.slice(0, head)}${TRUNCATION_MARKER}${text.slice(text.length - tail)}`;
102
+ }
103
+ /**
104
+ * Convert a {@link ReasoningBlock} into its `<thinking>...</thinking>` text
105
+ * representation for outbound replay through adapters that cannot natively
106
+ * echo reasoning.
107
+ *
108
+ * Behaviour:
109
+ * - `redactedData` non-empty → returns {@link REDACTED_PLACEHOLDER} exactly.
110
+ * Plaintext is unavailable so emitting the original `text` (which is
111
+ * conventionally empty for redacted blocks) would yield an empty
112
+ * `<thinking></thinking>` and confuse the next model; the placeholder
113
+ * signals that reasoning occurred without leaking any content.
114
+ * - Empty non-redacted text → returns the empty string. Callers should
115
+ * skip emitting an assistant-message slot rather than pushing an empty
116
+ * payload.
117
+ * - Otherwise → returns `<thinking>${truncate(text)}</thinking>`.
118
+ */
119
+ export function reasoningBlockToInlineText(block, options) {
120
+ if (typeof block.redactedData === 'string' && block.redactedData.length > 0) {
121
+ return REDACTED_PLACEHOLDER;
122
+ }
123
+ if (block.text.length === 0)
124
+ return '';
125
+ const max = resolveMaxChars(options?.maxChars);
126
+ return `<thinking>${truncate(block.text, max)}</thinking>`;
127
+ }
128
+ //# sourceMappingURL=reasoning-fallback.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reasoning-fallback.js","sourceRoot":"","sources":["../../src/llm/reasoning-fallback.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAIH;;;;GAIG;AACH,MAAM,CAAC,MAAM,oCAAoC,GAAG,IAAI,CAAA;AAExD;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,MAAM,CAAC,iBAAiB,CAAA;AAErD,8EAA8E;AAC9E,MAAM,iBAAiB,GAAG,mBAAmB,CAAA;AAE7C,6EAA6E;AAC7E,MAAM,oBAAoB,GAAG,iCAAiC,CAAA;AAuD9D;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,gCAAgC,CAC9C,OAA6C;IAE7C,IAAI,OAAO,EAAE,uBAAuB,KAAK,IAAI;QAAE,OAAO,SAAS,CAAA;IAC/D,MAAM,QAAQ,GAAG,OAAO,CAAC,qBAAqB,CAAA;IAC9C,IAAI,QAAQ,KAAK,KAAK;QAAE,OAAO,aAAa,CAAA;IAC5C,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QAChD,OAAO,SAAS,CAAA,CAAE,uDAAuD;IAC3E,CAAC;IACD,OAAO,QAAQ,CAAC,QAAQ,CAAA,CAAE,gDAAgD;AAC5E,CAAC;AAED,SAAS,eAAe,CAAC,KAAyB;IAChD,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,oCAAoC,CAAA;IACpE,IAAI,KAAK,KAAK,aAAa;QAAE,OAAO,MAAM,CAAC,gBAAgB,CAAA;IAC3D,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,CAAC,CAAA;IACrC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;IACjC,IAAI,OAAO,GAAG,CAAC;QAAE,OAAO,CAAC,CAAA;IACzB,OAAO,OAAO,CAAA;AAChB,CAAC;AAED;;;;;GAKG;AACH,SAAS,QAAQ,CAAC,IAAY,EAAE,QAAgB;IAC9C,IAAI,IAAI,CAAC,MAAM,IAAI,QAAQ;QAAE,OAAO,IAAI,CAAA;IACxC,IAAI,iBAAiB,CAAC,MAAM,IAAI,QAAQ;QAAE,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAA;IACxE,MAAM,MAAM,GAAG,QAAQ,GAAG,iBAAiB,CAAC,MAAM,CAAA;IAClD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC,CAAA;IACpC,MAAM,IAAI,GAAG,MAAM,GAAG,IAAI,CAAA;IAC1B,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,iBAAiB,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAA;AACtF,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,0BAA0B,CACxC,KAAqB,EACrB,OAAkC;IAElC,IAAI,OAAO,KAAK,CAAC,YAAY,KAAK,QAAQ,IAAI,KAAK,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5E,OAAO,oBAAoB,CAAA;IAC7B,CAAC;IACD,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAA;IACtC,MAAM,GAAG,GAAG,eAAe,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAA;IAC9C,OAAO,aAAa,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,CAAC,aAAa,CAAA;AAC5D,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../src/orchestrator/orchestrator.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../src/orchestrator/orchestrator.ts"],"names":[],"mappings":"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"}
@@ -46,6 +46,7 @@ import { emitTrace, generateRunId } from '../utils/trace.js';
46
46
  import { ToolRegistry } from '../tool/framework.js';
47
47
  import { ToolExecutor } from '../tool/executor.js';
48
48
  import { registerBuiltInTools } from '../tool/built-in/index.js';
49
+ import { defaultWorkspaceDir } from '../tool/built-in/path-safety.js';
49
50
  import { Team } from '../team/team.js';
50
51
  import { TaskQueue } from '../task/queue.js';
51
52
  import { createTask } from '../task/task.js';
@@ -321,6 +322,21 @@ function parseTaskSpecs(raw) {
321
322
  return null;
322
323
  }
323
324
  }
325
+ function buildRevealCoordinatorLines(revealContext, assignee) {
326
+ return [
327
+ '## Team context',
328
+ `Goal: ${revealContext.goal}`,
329
+ `Team: ${revealContext.rosterNames.join(', ')}`,
330
+ `Your role in this team: ${assignee}`,
331
+ 'Assignment: You are responsible for the prompt below in this team run.',
332
+ '',
333
+ ];
334
+ }
335
+ function prependRevealCoordinatorContext(prompt, revealContext, assignee) {
336
+ return revealContext
337
+ ? [...buildRevealCoordinatorLines(revealContext, assignee), prompt].join('\n')
338
+ : prompt;
339
+ }
324
340
  /**
325
341
  * Build {@link TeamInfo} for tool context, including nested `runDelegatedAgent`
326
342
  * that respects pool capacity to avoid semaphore deadlocks.
@@ -365,6 +381,7 @@ function buildTaskAgentTeamInfo(ctx, taskId, traceBase, delegationDepth, delegat
365
381
  provider: targetConfig.provider ?? ctx.config.defaultProvider,
366
382
  baseURL: targetConfig.baseURL ?? ctx.config.defaultBaseURL,
367
383
  apiKey: targetConfig.apiKey ?? ctx.config.defaultApiKey,
384
+ cwd: targetConfig.cwd === undefined ? ctx.config.defaultCwd : targetConfig.cwd,
368
385
  };
369
386
  const tempAgent = buildAgent(effective, { includeDelegateTool: true });
370
387
  const nestedTeam = buildTaskAgentTeamInfo(ctx, taskId, traceBase, delegationDepth + 1, [...delegationChain, targetAgent]);
@@ -374,7 +391,7 @@ function buildTaskAgentTeamInfo(ctx, taskId, traceBase, delegationDepth, delegat
374
391
  taskId,
375
392
  team: nestedTeam,
376
393
  };
377
- return pool.runEphemeral(tempAgent, prompt, childOpts);
394
+ return pool.runEphemeral(tempAgent, prependRevealCoordinatorContext(prompt, ctx.revealCoordinatorContext, targetAgent), childOpts);
378
395
  };
379
396
  return {
380
397
  name: ctx.team.name,
@@ -469,7 +486,7 @@ async function executeQueue(queue, ctx) {
469
486
  data: task,
470
487
  });
471
488
  // Build the prompt: task description + dependency-only context by default.
472
- const prompt = await buildTaskPrompt(task, team, queue);
489
+ const prompt = await buildTaskPrompt(task, team, queue, ctx.revealCoordinatorContext);
473
490
  // Trace + abort + team tool context (delegate_to_agent)
474
491
  const traceBase = {
475
492
  ...(config.onTrace
@@ -622,17 +639,23 @@ async function executeQueue(queue, ctx) {
622
639
  * Build the agent prompt for a specific task.
623
640
  *
624
641
  * Injects:
642
+ * - Optional team-context block at the top when `revealContext` is provided
643
+ * (set via `RunTeamOptions.revealCoordinator`)
625
644
  * - Task title and description
626
645
  * - Direct dependency task results by default (clean slate when none)
627
646
  * - Optional full shared-memory context when `task.memoryScope === 'all'`
628
647
  * - Any messages addressed to this agent from the team bus
629
648
  */
630
- async function buildTaskPrompt(task, team, queue) {
631
- const lines = [
632
- `# Task: ${task.title}`,
633
- '',
634
- task.description,
635
- ];
649
+ async function buildTaskPrompt(task, team, queue, revealContext) {
650
+ const lines = [];
651
+ // `task.assignee` is belt-and-suspenders: `executeQueue` already fails any
652
+ // task without an assignee before reaching this function (see the assignee
653
+ // check in the dispatch loop). The guard here documents the precondition and
654
+ // protects against future refactors that move the call site.
655
+ if (revealContext && task.assignee) {
656
+ lines.push(...buildRevealCoordinatorLines(revealContext, task.assignee));
657
+ }
658
+ lines.push(`# Task: ${task.title}`, '', task.description);
636
659
  if (task.memoryScope === 'all') {
637
660
  // Explicit opt-in for full visibility (legacy/shared-memory behavior).
638
661
  const sharedMem = team.getSharedMemoryInstance();
@@ -698,6 +721,10 @@ export class OpenMultiAgent {
698
721
  defaultProvider: config.defaultProvider ?? 'anthropic',
699
722
  defaultBaseURL: config.defaultBaseURL,
700
723
  defaultApiKey: config.defaultApiKey,
724
+ // `defaultCwd === undefined` means "use the default sandbox rooted at
725
+ // <cwd>/.agent-workspace". An explicit `null` propagates through to
726
+ // disable the filesystem sandbox; a string sets a custom sandbox root.
727
+ defaultCwd: config.defaultCwd === undefined ? defaultWorkspaceDir() : config.defaultCwd,
701
728
  maxTokenBudget: config.maxTokenBudget,
702
729
  onApproval: config.onApproval,
703
730
  onPlanReady: config.onPlanReady,
@@ -748,6 +775,7 @@ export class OpenMultiAgent {
748
775
  provider: config.provider ?? this.config.defaultProvider,
749
776
  baseURL: config.baseURL ?? this.config.defaultBaseURL,
750
777
  apiKey: config.apiKey ?? this.config.defaultApiKey,
778
+ cwd: config.cwd === undefined ? this.config.defaultCwd : config.cwd,
751
779
  maxTokenBudget: effectiveBudget,
752
780
  };
753
781
  const agent = buildAgent(effective);
@@ -835,6 +863,7 @@ export class OpenMultiAgent {
835
863
  provider: bestAgent.provider ?? this.config.defaultProvider,
836
864
  baseURL: bestAgent.baseURL ?? this.config.defaultBaseURL,
837
865
  apiKey: bestAgent.apiKey ?? this.config.defaultApiKey,
866
+ cwd: bestAgent.cwd === undefined ? this.config.defaultCwd : bestAgent.cwd,
838
867
  maxTokenBudget: effectiveBudget,
839
868
  };
840
869
  const agent = buildAgent(effective);
@@ -907,6 +936,9 @@ export class OpenMultiAgent {
907
936
  toolPreset: coordinatorOverrides?.toolPreset,
908
937
  tools: coordinatorOverrides?.tools,
909
938
  disallowedTools: coordinatorOverrides?.disallowedTools,
939
+ cwd: coordinatorOverrides?.cwd === undefined
940
+ ? this.config.defaultCwd
941
+ : coordinatorOverrides.cwd,
910
942
  loopDetection: coordinatorOverrides?.loopDetection,
911
943
  timeoutMs: coordinatorOverrides?.timeoutMs,
912
944
  };
@@ -980,6 +1012,14 @@ export class OpenMultiAgent {
980
1012
  budgetExceededTriggered: false,
981
1013
  budgetExceededReason: undefined,
982
1014
  taskMetrics,
1015
+ ...(options?.revealCoordinator
1016
+ ? {
1017
+ revealCoordinatorContext: {
1018
+ goal,
1019
+ rosterNames: agentConfigs.map((a) => a.name),
1020
+ },
1021
+ }
1022
+ : {}),
983
1023
  };
984
1024
  const planTasks = queue.list();
985
1025
  const planReadyStartMs = Date.now();
@@ -1040,6 +1080,9 @@ export class OpenMultiAgent {
1040
1080
  // ------------------------------------------------------------------
1041
1081
  // Step 5: Coordinator synthesises final result
1042
1082
  // ------------------------------------------------------------------
1083
+ if (options?.abortSignal?.aborted) {
1084
+ return this.buildTeamRunResult(agentResults, goal, taskRecords);
1085
+ }
1043
1086
  if (maxTokenBudget !== undefined
1044
1087
  && cumulativeUsage.input_tokens + cumulativeUsage.output_tokens > maxTokenBudget) {
1045
1088
  return this.buildTeamRunResult(agentResults, goal, taskRecords);
@@ -1290,6 +1333,12 @@ export class OpenMultiAgent {
1290
1333
  */
1291
1334
  loadSpecsIntoQueue(specs, agentConfigs, queue) {
1292
1335
  const agentNames = new Set(agentConfigs.map((a) => a.name));
1336
+ const normalizeTitle = (title) => title.toLowerCase().trim();
1337
+ const titleCounts = new Map();
1338
+ for (const spec of specs) {
1339
+ const key = normalizeTitle(spec.title);
1340
+ titleCounts.set(key, (titleCounts.get(key) ?? 0) + 1);
1341
+ }
1293
1342
  // First pass: create tasks (without dependencies) to get stable IDs.
1294
1343
  const titleToId = new Map();
1295
1344
  const createdTasks = [];
@@ -1305,7 +1354,10 @@ export class OpenMultiAgent {
1305
1354
  retryDelayMs: spec.retryDelayMs,
1306
1355
  retryBackoff: spec.retryBackoff,
1307
1356
  });
1308
- titleToId.set(spec.title.toLowerCase().trim(), task.id);
1357
+ const titleKey = normalizeTitle(spec.title);
1358
+ if ((titleCounts.get(titleKey) ?? 0) === 1) {
1359
+ titleToId.set(titleKey, task.id);
1360
+ }
1309
1361
  createdTasks.push(task);
1310
1362
  }
1311
1363
  // Second pass: resolve title-based dependsOn to IDs.
@@ -1317,20 +1369,29 @@ export class OpenMultiAgent {
1317
1369
  continue;
1318
1370
  }
1319
1371
  const resolvedDeps = [];
1372
+ const unresolvedDeps = [];
1320
1373
  for (const depRef of spec.dependsOn) {
1321
1374
  // Accept both raw IDs and title strings
1322
1375
  const byId = createdTasks.find((t) => t.id === depRef);
1323
- const byTitle = titleToId.get(depRef.toLowerCase().trim());
1376
+ const depTitleKey = normalizeTitle(depRef);
1377
+ const byTitle = titleToId.get(depTitleKey);
1324
1378
  const resolvedId = byId?.id ?? byTitle;
1325
1379
  if (resolvedId) {
1326
1380
  resolvedDeps.push(resolvedId);
1327
1381
  }
1382
+ else {
1383
+ const count = titleCounts.get(depTitleKey) ?? 0;
1384
+ unresolvedDeps.push(count > 1 ? `${depRef} (ambiguous duplicate title)` : depRef);
1385
+ }
1328
1386
  }
1329
1387
  const taskWithDeps = {
1330
1388
  ...task,
1331
1389
  dependsOn: resolvedDeps.length > 0 ? resolvedDeps : undefined,
1332
1390
  };
1333
1391
  queue.add(taskWithDeps);
1392
+ if (unresolvedDeps.length > 0) {
1393
+ queue.fail(task.id, `Unresolved dependency reference(s): ${unresolvedDeps.join(', ')}`);
1394
+ }
1334
1395
  }
1335
1396
  }
1336
1397
  /** Build an {@link AgentPool} from a list of agent configurations. */
@@ -1343,6 +1404,7 @@ export class OpenMultiAgent {
1343
1404
  provider: config.provider ?? this.config.defaultProvider,
1344
1405
  baseURL: config.baseURL ?? this.config.defaultBaseURL,
1345
1406
  apiKey: config.apiKey ?? this.config.defaultApiKey,
1407
+ cwd: config.cwd === undefined ? this.config.defaultCwd : config.cwd,
1346
1408
  };
1347
1409
  pool.add(buildAgent(effective, { includeDelegateTool: true }));
1348
1410
  }