@bubblebrain-ai/bubble 0.0.34 → 0.0.36
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/README.md +27 -7
- package/dist/agent/abort-errors.d.ts +2 -2
- package/dist/agent/budget-ledger.d.ts +4 -45
- package/dist/agent/budget-ledger.js +5 -75
- package/dist/agent/categories.d.ts +66 -2
- package/dist/agent/categories.js +120 -19
- package/dist/agent/child-runner.d.ts +1 -3
- package/dist/agent/child-runner.js +6 -37
- package/dist/agent/discovery-barrier.js +1 -1
- package/dist/agent/profiles.d.ts +1 -2
- package/dist/agent/profiles.js +25 -14
- package/dist/agent/routing-catalog.d.ts +113 -0
- package/dist/agent/routing-catalog.js +240 -0
- package/dist/agent/subagent-control.d.ts +5 -12
- package/dist/agent/subagent-control.js +0 -2
- package/dist/agent/subagent-store.d.ts +1 -1
- package/dist/agent/subagent-store.js +1 -3
- package/dist/agent/workflow/control.d.ts +21 -0
- package/dist/agent/workflow/control.js +50 -8
- package/dist/agent/workflow/runtime.d.ts +15 -2
- package/dist/agent/workflow/runtime.js +112 -30
- package/dist/agent.d.ts +82 -48
- package/dist/agent.js +360 -232
- package/dist/approval/controller.js +28 -0
- package/dist/approval/tool-helper.js +2 -0
- package/dist/approval/types.d.ts +21 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +2 -1
- package/dist/config.d.ts +5 -3
- package/dist/config.js +10 -5
- package/dist/context/compact-llm.d.ts +2 -2
- package/dist/external-runtime/grok-acp.d.ts +56 -0
- package/dist/external-runtime/grok-acp.js +386 -0
- package/dist/external-runtime/grok-binary.d.ts +25 -0
- package/dist/external-runtime/grok-binary.js +108 -0
- package/dist/external-runtime/grok-errors.d.ts +7 -0
- package/dist/external-runtime/grok-errors.js +15 -0
- package/dist/external-runtime/grok-input-policy.d.ts +58 -0
- package/dist/external-runtime/grok-input-policy.js +52 -0
- package/dist/external-runtime/grok-ndjson.d.ts +14 -0
- package/dist/external-runtime/grok-ndjson.js +193 -0
- package/dist/external-runtime/grok-network.d.ts +15 -0
- package/dist/external-runtime/grok-network.js +81 -0
- package/dist/external-runtime/grok-preflight.d.ts +28 -0
- package/dist/external-runtime/grok-preflight.js +194 -0
- package/dist/external-runtime/grok-process.d.ts +26 -0
- package/dist/external-runtime/grok-process.js +184 -0
- package/dist/external-runtime/grok-profile.d.ts +32 -0
- package/dist/external-runtime/grok-profile.js +419 -0
- package/dist/external-runtime/grok-provider.d.ts +31 -0
- package/dist/external-runtime/grok-provider.js +41 -0
- package/dist/external-runtime/grok-runtime.d.ts +91 -0
- package/dist/external-runtime/grok-runtime.js +882 -0
- package/dist/external-runtime/index.d.ts +13 -0
- package/dist/external-runtime/index.js +13 -0
- package/dist/external-runtime/session-policy.d.ts +11 -0
- package/dist/external-runtime/session-policy.js +30 -0
- package/dist/external-runtime/types.d.ts +65 -0
- package/dist/external-runtime/types.js +1 -0
- package/dist/feishu/agent-host/approval-card.js +21 -0
- package/dist/feishu/agent-host/run-driver.js +19 -3
- package/dist/feishu/serve.js +1 -0
- package/dist/goal/tools.d.ts +6 -3
- package/dist/goal/tools.js +7 -19
- package/dist/main.js +114 -28
- package/dist/memory/db.d.ts +2 -0
- package/dist/memory/db.js +10 -0
- package/dist/memory/index.d.ts +1 -0
- package/dist/memory/index.js +1 -0
- package/dist/memory/phase1.js +14 -2
- package/dist/memory/phase2.js +21 -2
- package/dist/memory/prompts.js +1 -1
- package/dist/memory/session-policy.d.ts +25 -0
- package/dist/memory/session-policy.js +55 -0
- package/dist/memory/storage.d.ts +1 -0
- package/dist/memory/storage.js +13 -0
- package/dist/model-catalog.d.ts +18 -0
- package/dist/model-catalog.js +92 -52
- package/dist/model-config.d.ts +3 -1
- package/dist/model-config.js +11 -0
- package/dist/oauth/grok.d.ts +25 -0
- package/dist/oauth/grok.js +218 -0
- package/dist/oauth/index.d.ts +1 -0
- package/dist/oauth/index.js +1 -0
- package/dist/oauth/storage.d.ts +9 -0
- package/dist/oauth/storage.js +25 -0
- package/dist/orchestrator/default-hooks.js +23 -7
- package/dist/prompt/compose.d.ts +3 -0
- package/dist/prompt/compose.js +3 -0
- package/dist/prompt/delegation.d.ts +1 -1
- package/dist/prompt/delegation.js +41 -8
- package/dist/prompt/environment.js +0 -4
- package/dist/prompt/reminders.d.ts +0 -1
- package/dist/prompt/reminders.js +1 -11
- package/dist/prompt/routing.d.ts +14 -0
- package/dist/prompt/routing.js +71 -0
- package/dist/prompt/runtime.js +1 -1
- package/dist/prompt/task-reminders.d.ts +20 -0
- package/dist/prompt/task-reminders.js +73 -1
- package/dist/provider-ai-sdk.js +2 -2
- package/dist/provider-grok.d.ts +26 -0
- package/dist/provider-grok.js +59 -0
- package/dist/provider-openai-codex.d.ts +17 -1
- package/dist/provider-openai-codex.js +101 -29
- package/dist/provider-registry.d.ts +72 -0
- package/dist/provider-registry.js +336 -59
- package/dist/provider-transform.d.ts +1 -1
- package/dist/provider-transform.js +35 -17
- package/dist/provider.d.ts +3 -0
- package/dist/provider.js +14 -0
- package/dist/session-types.d.ts +19 -1
- package/dist/session.d.ts +1 -0
- package/dist/session.js +4 -0
- package/dist/slash-commands/commands.js +224 -14
- package/dist/slash-commands/types.d.ts +14 -1
- package/dist/system-prompt.d.ts +2 -0
- package/dist/tools/agent-lifecycle.d.ts +1 -9
- package/dist/tools/agent-lifecycle.js +42 -272
- package/dist/tools/bash.js +1 -1
- package/dist/tools/child-tools.js +2 -0
- package/dist/tools/index.d.ts +2 -2
- package/dist/tools/index.js +7 -5
- package/dist/tools/lsp.js +1 -0
- package/dist/tools/memory.d.ts +7 -2
- package/dist/tools/memory.js +28 -43
- package/dist/tools/server.js +4 -0
- package/dist/tools/tool-search.d.ts +1 -1
- package/dist/tools/tool-search.js +23 -3
- package/dist/tui/model-switch.d.ts +7 -0
- package/dist/tui/model-switch.js +15 -5
- package/dist/tui-ink/app.d.ts +3 -1
- package/dist/tui-ink/app.js +672 -243
- package/dist/tui-ink/approval/approval-dialog.js +37 -1
- package/dist/tui-ink/approval/diff-view.js +4 -4
- package/dist/tui-ink/approval/select.js +2 -2
- package/dist/tui-ink/composer-buffer.d.ts +55 -0
- package/dist/tui-ink/composer-buffer.js +317 -0
- package/dist/tui-ink/feedback-dialog.js +2 -2
- package/dist/tui-ink/feishu-setup-picker.js +3 -3
- package/dist/tui-ink/footer.d.ts +1 -0
- package/dist/tui-ink/footer.js +7 -2
- package/dist/tui-ink/input-box.d.ts +36 -7
- package/dist/tui-ink/input-box.js +229 -157
- package/dist/tui-ink/markdown.js +11 -9
- package/dist/tui-ink/message-list.d.ts +5 -1
- package/dist/tui-ink/message-list.js +15 -2
- package/dist/tui-ink/model-picker.d.ts +61 -4
- package/dist/tui-ink/model-picker.js +464 -128
- package/dist/tui-ink/paste-operation-tracker.d.ts +13 -0
- package/dist/tui-ink/paste-operation-tracker.js +25 -0
- package/dist/tui-ink/plan-confirm.js +1 -1
- package/dist/tui-ink/run.d.ts +3 -0
- package/dist/tui-ink/run.js +1 -1
- package/dist/tui-ink/subagent-view.d.ts +34 -1
- package/dist/tui-ink/subagent-view.js +82 -1
- package/dist/tui-ink/theme.d.ts +19 -2
- package/dist/tui-ink/theme.js +25 -8
- package/dist/tui-ink/welcome.d.ts +3 -2
- package/dist/tui-ink/welcome.js +10 -6
- package/dist/types.d.ts +8 -31
- package/dist/types.js +1 -1
- package/dist/variant/thinking-level.d.ts +2 -2
- package/dist/variant/thinking-level.js +2 -1
- package/dist/variant/variant-resolver.d.ts +22 -0
- package/dist/variant/variant-resolver.js +43 -0
- package/package.json +6 -2
- package/dist/tools/task.d.ts +0 -2
- package/dist/tools/task.js +0 -58
|
@@ -46,7 +46,7 @@ export class DiscoveryBarrier {
|
|
|
46
46
|
return undefined;
|
|
47
47
|
return {
|
|
48
48
|
content: `Blocked speculative ${target.kind}. The path "${target.path}" has not been discovered in this repository context ` +
|
|
49
|
-
"and was not explicitly requested by the user. Run glob/grep
|
|
49
|
+
"and was not explicitly requested by the user. Run glob/grep discovery first, then read only returned paths. " +
|
|
50
50
|
"Do not infer from this blocked call whether the path exists.",
|
|
51
51
|
isError: true,
|
|
52
52
|
status: "blocked",
|
package/dist/agent/profiles.d.ts
CHANGED
|
@@ -20,8 +20,6 @@ export interface AgentProfile {
|
|
|
20
20
|
category?: string;
|
|
21
21
|
tools: AgentProfileTools;
|
|
22
22
|
maxTurns?: number;
|
|
23
|
-
/** Optional per-child token cap declared by the profile (may only lower the runtime default). */
|
|
24
|
-
maxTokens?: number;
|
|
25
23
|
approval: AgentProfileApproval;
|
|
26
24
|
nicknameCandidates?: string[];
|
|
27
25
|
prompt: string;
|
|
@@ -69,3 +67,4 @@ export declare function allowedToolEffectsForMode(mode: AgentProfileMode): Set<s
|
|
|
69
67
|
export declare function selectToolsForAgentProfile(tools: ToolRegistryEntry[], profile: AgentProfile, approval?: AgentProfileApproval): ToolRegistryEntry[];
|
|
70
68
|
export declare function validateAgentProfileTools(tools: ToolRegistryEntry[], profile: AgentProfile, approval?: AgentProfileApproval): AgentProfileDiagnostic[];
|
|
71
69
|
export declare function mergeUsage(current: SubagentRunResult["usage"], usage: TokenUsage): SubagentRunResult["usage"];
|
|
70
|
+
export declare function canonicalToolName(name: string): string;
|
package/dist/agent/profiles.js
CHANGED
|
@@ -10,21 +10,17 @@ const READONLY_PRESET = [
|
|
|
10
10
|
"lsp",
|
|
11
11
|
"web_search",
|
|
12
12
|
"web_fetch",
|
|
13
|
-
"
|
|
14
|
-
"memory_read_summary",
|
|
13
|
+
"memory",
|
|
15
14
|
"skill",
|
|
16
15
|
"todo_write",
|
|
17
16
|
];
|
|
18
17
|
const SUBAGENT_DENY_TOOLS = new Set([
|
|
19
18
|
"subagent",
|
|
20
|
-
"task",
|
|
21
19
|
"spawn_agent",
|
|
22
20
|
"wait_agent",
|
|
23
21
|
"send_input",
|
|
24
22
|
"close_agent",
|
|
25
23
|
"list_agents",
|
|
26
|
-
"agent_team",
|
|
27
|
-
"agent_batch",
|
|
28
24
|
"run_workflow",
|
|
29
25
|
"wait_workflow",
|
|
30
26
|
]);
|
|
@@ -128,12 +124,12 @@ export function builtinAgentProfiles() {
|
|
|
128
124
|
"You are an explorer subagent for codebase reconnaissance.",
|
|
129
125
|
"Answer the specific question by inspecting the repository directly. Prefer precise file paths and line-level evidence.",
|
|
130
126
|
"Keep the answer compact and avoid broad refactors or implementation plans unless asked.",
|
|
131
|
-
].join("\n"), ["read", "glob", "grep", "lsp", "
|
|
127
|
+
].join("\n"), ["read", "glob", "grep", "lsp", "memory", "skill", "todo_write"]),
|
|
132
128
|
roleProfile("worker", "Implementation-planning worker subagent", [
|
|
133
129
|
"You are a worker subagent. In this Phase 1 runtime you are read-only, so you must not modify files.",
|
|
134
130
|
"Analyze the assigned implementation slice, identify exact files to change, and return a concrete patch plan or findings.",
|
|
135
131
|
"If write-capable worker mode is needed, say so explicitly.",
|
|
136
|
-
].join("\n"), ["read", "glob", "grep", "lsp", "
|
|
132
|
+
].join("\n"), ["read", "glob", "grep", "lsp", "memory", "skill", "todo_write"]),
|
|
137
133
|
toProfile("search"),
|
|
138
134
|
toProfile("security_investigation"),
|
|
139
135
|
toProfile("evidence_correlation"),
|
|
@@ -169,7 +165,7 @@ export function allowedToolEffectsForMode(mode) {
|
|
|
169
165
|
: new Set(["read"]);
|
|
170
166
|
}
|
|
171
167
|
export function selectToolsForAgentProfile(tools, profile, approval = profile.approval) {
|
|
172
|
-
const explicitInclude = new Set(profile.tools.include ?? []);
|
|
168
|
+
const explicitInclude = new Set((profile.tools.include ?? []).map(canonicalToolName));
|
|
173
169
|
const selected = requestedToolNames(profile);
|
|
174
170
|
for (const tool of SUBAGENT_DENY_TOOLS)
|
|
175
171
|
selected.delete(tool);
|
|
@@ -185,13 +181,17 @@ export function selectToolsForAgentProfile(tools, profile, approval = profile.ap
|
|
|
185
181
|
continue;
|
|
186
182
|
if (approval === "disabled" && tool.requiresApproval)
|
|
187
183
|
continue;
|
|
188
|
-
|
|
184
|
+
// Explicit include is the profile author pre-unlocking the tool: the
|
|
185
|
+
// child is a fresh Agent with its own (empty) unlock set, so a deferred
|
|
186
|
+
// flag surviving here would re-lock the tool inside the child.
|
|
187
|
+
const entry = tool.deferred ? { ...tool, deferred: false } : tool;
|
|
188
|
+
out.push(wrapApprovalFailTool(entry, approval));
|
|
189
189
|
}
|
|
190
190
|
return out;
|
|
191
191
|
}
|
|
192
192
|
export function validateAgentProfileTools(tools, profile, approval = profile.approval) {
|
|
193
193
|
const available = new Map(tools.map((tool) => [tool.name, tool]));
|
|
194
|
-
const explicitInclude = new Set(profile.tools.include ?? []);
|
|
194
|
+
const explicitInclude = new Set((profile.tools.include ?? []).map(canonicalToolName));
|
|
195
195
|
const allowedEffects = allowedToolEffectsForMode(profile.mode);
|
|
196
196
|
const diagnostics = [];
|
|
197
197
|
for (const name of requestedToolNames(profile)) {
|
|
@@ -258,6 +258,18 @@ function wrapApprovalFailTool(tool, approval) {
|
|
|
258
258
|
}),
|
|
259
259
|
};
|
|
260
260
|
}
|
|
261
|
+
/**
|
|
262
|
+
* Renamed/merged tools keep working for user-defined profiles written
|
|
263
|
+
* against the old names (memory_search/memory_read_summary merged into
|
|
264
|
+
* "memory", 2026-07-13).
|
|
265
|
+
*/
|
|
266
|
+
const LEGACY_TOOL_ALIASES = {
|
|
267
|
+
memory_search: "memory",
|
|
268
|
+
memory_read_summary: "memory",
|
|
269
|
+
};
|
|
270
|
+
export function canonicalToolName(name) {
|
|
271
|
+
return LEGACY_TOOL_ALIASES[name] ?? name;
|
|
272
|
+
}
|
|
261
273
|
function requestedToolNames(profile) {
|
|
262
274
|
const selected = new Set();
|
|
263
275
|
if (profile.tools.preset === "readonly") {
|
|
@@ -266,12 +278,12 @@ function requestedToolNames(profile) {
|
|
|
266
278
|
}
|
|
267
279
|
else if (profile.tools.preset === "explicit") {
|
|
268
280
|
for (const tool of profile.tools.include ?? [])
|
|
269
|
-
selected.add(tool);
|
|
281
|
+
selected.add(canonicalToolName(tool));
|
|
270
282
|
}
|
|
271
283
|
for (const tool of profile.tools.include ?? [])
|
|
272
|
-
selected.add(tool);
|
|
284
|
+
selected.add(canonicalToolName(tool));
|
|
273
285
|
for (const tool of profile.tools.exclude ?? [])
|
|
274
|
-
selected.delete(tool);
|
|
286
|
+
selected.delete(canonicalToolName(tool));
|
|
275
287
|
return selected;
|
|
276
288
|
}
|
|
277
289
|
function loadProfilesFromDir(dir, source, diagnostics) {
|
|
@@ -323,7 +335,6 @@ function parseAgentProfileFile(raw, source, filePath) {
|
|
|
323
335
|
category: stringValue(frontmatter.category),
|
|
324
336
|
tools: toolsValue(frontmatter.tools),
|
|
325
337
|
maxTurns: numberValue(frontmatter.maxTurns),
|
|
326
|
-
maxTokens: numberValue(frontmatter.maxTokens),
|
|
327
338
|
approval: approvalValue(frontmatter.approval),
|
|
328
339
|
nicknameCandidates: stringArray(frontmatter.nicknameCandidates) ?? stringArray(frontmatter.nicknames),
|
|
329
340
|
prompt: parsed.body.trim(),
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Routing catalog: the single source of catalog truth for subagent model
|
|
3
|
+
* routing (docs/model-routing-design.md §1).
|
|
4
|
+
*
|
|
5
|
+
* No routing consumer reads `listBuiltinModels` / `getConfigured` directly —
|
|
6
|
+
* neither reflects what is actually runnable. Consumers read a
|
|
7
|
+
* `RoutingSnapshot` through the live accessor created here; the accessor
|
|
8
|
+
* caches by the registry's routing revision and rebuilds lazily, so tier
|
|
9
|
+
* resolution, validation, the detector, and the menu are never stale (§1.5).
|
|
10
|
+
*/
|
|
11
|
+
import { type ModelTier } from "../model-catalog.js";
|
|
12
|
+
import type { ProviderRegistry } from "../provider-registry.js";
|
|
13
|
+
import { selectTierCandidates, type AgentCategoriesConfig, type TierRoutingContext } from "./categories.js";
|
|
14
|
+
import type { ThinkingLevel } from "../types.js";
|
|
15
|
+
export interface AgentRoutingConfig {
|
|
16
|
+
/** Automatic builtin-tier downgrade routing (§3.2). */
|
|
17
|
+
autoTier: boolean;
|
|
18
|
+
/** Call-site cross-provider routing; default open, set false to lock (§7.2). */
|
|
19
|
+
allowCrossProvider: boolean;
|
|
20
|
+
}
|
|
21
|
+
export declare const DEFAULT_AGENT_ROUTING: AgentRoutingConfig;
|
|
22
|
+
export declare function sanitizeAgentRouting(value: unknown): AgentRoutingConfig;
|
|
23
|
+
export interface RoutingModelEntry {
|
|
24
|
+
id: string;
|
|
25
|
+
name: string;
|
|
26
|
+
tier?: ModelTier;
|
|
27
|
+
routingPriority?: number;
|
|
28
|
+
/** Index in the builtin catalog order, when the id is builtin (§3.2 tiebreak). */
|
|
29
|
+
builtinIndex?: number;
|
|
30
|
+
source: "builtin" | "dynamic" | "custom";
|
|
31
|
+
}
|
|
32
|
+
export type RoutingMembershipSource = "custom-allowlist" | "complete-discovery" | "fallback-union";
|
|
33
|
+
export interface RoutingSnapshot {
|
|
34
|
+
parent: {
|
|
35
|
+
providerId: string;
|
|
36
|
+
model: string;
|
|
37
|
+
tier?: ModelTier;
|
|
38
|
+
};
|
|
39
|
+
/** Catalog-effective provider id (openai OAuth -> "openai-codex" alias). */
|
|
40
|
+
effectiveProviderId: string;
|
|
41
|
+
membershipSource: RoutingMembershipSource;
|
|
42
|
+
/** Model list for the PARENT provider only (§1.2 scope note). */
|
|
43
|
+
models: RoutingModelEntry[];
|
|
44
|
+
/** True iff membershipSource === "custom-allowlist" — the only catalog
|
|
45
|
+
* that hard-rejects unknown ids (§1.4). */
|
|
46
|
+
authoritative: boolean;
|
|
47
|
+
registryRevision: number;
|
|
48
|
+
/**
|
|
49
|
+
* Earliest expiry of the discovery data this snapshot consumed. TTL expiry
|
|
50
|
+
* is passive — it bumps no revision — so the accessor must rebuild past
|
|
51
|
+
* this instant or it serves a catalog the registry itself no longer would.
|
|
52
|
+
*/
|
|
53
|
+
discoveryExpiresAt?: number;
|
|
54
|
+
/** Provider ids with active credentials (getEnabled, §1.1). */
|
|
55
|
+
runnableProviderIds: string[];
|
|
56
|
+
/** Post-merge, post-tier-resolution category bindings (§3). */
|
|
57
|
+
resolvedCategories: Array<{
|
|
58
|
+
name: string;
|
|
59
|
+
model: string | "inherit";
|
|
60
|
+
thinkingLevel?: ThinkingLevel;
|
|
61
|
+
tierSource?: "builtin" | "user";
|
|
62
|
+
}>;
|
|
63
|
+
}
|
|
64
|
+
export type RoutingSnapshotAccessor = (parent: {
|
|
65
|
+
providerId: string;
|
|
66
|
+
model: string;
|
|
67
|
+
}) => RoutingSnapshot;
|
|
68
|
+
/**
|
|
69
|
+
* Membership versus metadata (§1.3): an authoritative source alone decides
|
|
70
|
+
* which ids exist; lower-priority sources only enrich per-field metadata
|
|
71
|
+
* (tier: dynamic ?? custom ?? builtin).
|
|
72
|
+
*/
|
|
73
|
+
export declare function buildRoutingSnapshot(registry: ProviderRegistry, parent: {
|
|
74
|
+
providerId: string;
|
|
75
|
+
model: string;
|
|
76
|
+
}, agentCategories: AgentCategoriesConfig, agentRouting: AgentRoutingConfig): RoutingSnapshot;
|
|
77
|
+
/** Tier-resolution context derived from a snapshot (consumed by categories §3.2). */
|
|
78
|
+
export declare function tierContextFromSnapshot(snapshot: RoutingSnapshot, agentRouting: AgentRoutingConfig): TierRoutingContext;
|
|
79
|
+
/**
|
|
80
|
+
* Live accessor (§1.5): caches by (routing revision, parent route) and
|
|
81
|
+
* rebuilds lazily, so a discovery completion or provider mutation is
|
|
82
|
+
* reflected at the very next read. Building is synchronous over cached
|
|
83
|
+
* registry state — microseconds, never I/O.
|
|
84
|
+
*/
|
|
85
|
+
export declare function createRoutingSnapshotAccessor(registry: ProviderRegistry, getAgentCategories: () => AgentCategoriesConfig, getAgentRouting: () => AgentRoutingConfig): RoutingSnapshotAccessor;
|
|
86
|
+
/** Tier of a model within a snapshot, when known. */
|
|
87
|
+
export declare function snapshotModelTier(snapshot: RoutingSnapshot, modelId: string): ModelTier | undefined;
|
|
88
|
+
export interface RoutableModelEntry {
|
|
89
|
+
/** Routable provider id — what goes before the colon in provider:model. */
|
|
90
|
+
providerId: string;
|
|
91
|
+
id: string;
|
|
92
|
+
name: string;
|
|
93
|
+
}
|
|
94
|
+
export type RoutableModelIndex = () => RoutableModelEntry[];
|
|
95
|
+
/** Revision-cached index over all runnable providers' catalogs. */
|
|
96
|
+
export declare function createRoutableModelIndex(registry: ProviderRegistry): RoutableModelIndex;
|
|
97
|
+
/** Loose normalization for model-name matching: case/punctuation-insensitive. */
|
|
98
|
+
export declare function normalizeModelToken(value: string): string;
|
|
99
|
+
/**
|
|
100
|
+
* Near-match candidates for a (probably mistyped) model id within one
|
|
101
|
+
* provider's catalog. "suggest" matches normalized prefixes in either
|
|
102
|
+
* direction (soft "did you mean" notes). "truncation" matches only inputs
|
|
103
|
+
* the catalog EXTENDS ("gpt-5.6" -> gpt-5.6-sol/-terra/-luna) — the one
|
|
104
|
+
* direction that is typo evidence. An input extending a catalog id
|
|
105
|
+
* (gpt-5.6-sol-20260701 vs cataloged gpt-5.6-sol) is more likely a newly
|
|
106
|
+
* released variant the local catalog lags on, and must not be treated as a
|
|
107
|
+
* typo by hard-reject paths. Capped, deterministic order.
|
|
108
|
+
*/
|
|
109
|
+
export declare function nearModelMatches(input: string, candidates: RoutableModelEntry[], options?: {
|
|
110
|
+
limit?: number;
|
|
111
|
+
mode?: "suggest" | "truncation";
|
|
112
|
+
}): string[];
|
|
113
|
+
export { selectTierCandidates };
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Routing catalog: the single source of catalog truth for subagent model
|
|
3
|
+
* routing (docs/model-routing-design.md §1).
|
|
4
|
+
*
|
|
5
|
+
* No routing consumer reads `listBuiltinModels` / `getConfigured` directly —
|
|
6
|
+
* neither reflects what is actually runnable. Consumers read a
|
|
7
|
+
* `RoutingSnapshot` through the live accessor created here; the accessor
|
|
8
|
+
* caches by the registry's routing revision and rebuilds lazily, so tier
|
|
9
|
+
* resolution, validation, the detector, and the menu are never stale (§1.5).
|
|
10
|
+
*/
|
|
11
|
+
import { getBuiltinModel, listBuiltinModels, listDynamicModelMetadata, } from "../model-catalog.js";
|
|
12
|
+
import { mergeAgentCategoriesWithProvenance, resolveSubagentRoute, selectTierCandidates, } from "./categories.js";
|
|
13
|
+
export const DEFAULT_AGENT_ROUTING = {
|
|
14
|
+
autoTier: true,
|
|
15
|
+
allowCrossProvider: true,
|
|
16
|
+
};
|
|
17
|
+
export function sanitizeAgentRouting(value) {
|
|
18
|
+
const config = { ...DEFAULT_AGENT_ROUTING };
|
|
19
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
20
|
+
const record = value;
|
|
21
|
+
if (typeof record.autoTier === "boolean")
|
|
22
|
+
config.autoTier = record.autoTier;
|
|
23
|
+
if (typeof record.allowCrossProvider === "boolean") {
|
|
24
|
+
config.allowCrossProvider = record.allowCrossProvider;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return config;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Membership versus metadata (§1.3): an authoritative source alone decides
|
|
31
|
+
* which ids exist; lower-priority sources only enrich per-field metadata
|
|
32
|
+
* (tier: dynamic ?? custom ?? builtin).
|
|
33
|
+
*/
|
|
34
|
+
export function buildRoutingSnapshot(registry, parent, agentCategories, agentRouting) {
|
|
35
|
+
const configured = registry.getConfigured().find((item) => item.id === parent.providerId);
|
|
36
|
+
const effectiveProviderId = parent.providerId === "openai" && configured?.authType === "oauth"
|
|
37
|
+
? "openai-codex"
|
|
38
|
+
: parent.providerId;
|
|
39
|
+
const builtins = listBuiltinModels(effectiveProviderId);
|
|
40
|
+
const builtinIndex = new Map(builtins.map((model, index) => [model.id, index]));
|
|
41
|
+
const dynamic = listDynamicModelMetadata(effectiveProviderId);
|
|
42
|
+
const custom = registry.getModelConfig().getCustomModels(parent.providerId);
|
|
43
|
+
const discovery = registry.getCachedDiscoverySnapshot(parent.providerId);
|
|
44
|
+
const metadataFor = (id) => {
|
|
45
|
+
const dynamicEntry = dynamic.find((model) => model.id === id);
|
|
46
|
+
const discoveryEntry = discovery?.models.find((model) => model.id === id);
|
|
47
|
+
const customEntry = custom.find((model) => model.id === id);
|
|
48
|
+
const builtinEntry = builtins.find((model) => model.id === id);
|
|
49
|
+
return {
|
|
50
|
+
id,
|
|
51
|
+
name: dynamicEntry?.name ?? discoveryEntry?.name ?? customEntry?.name ?? builtinEntry?.name ?? id,
|
|
52
|
+
// Field-level merge (§1.3): discovery decides existence, but tier falls
|
|
53
|
+
// back through custom to builtin when the fresher source omits it.
|
|
54
|
+
tier: dynamicEntry?.tier ?? discoveryEntry?.tier ?? customEntry?.tier ?? builtinEntry?.tier,
|
|
55
|
+
routingPriority: customEntry?.routingPriority,
|
|
56
|
+
builtinIndex: builtinIndex.get(id),
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
let membershipSource;
|
|
60
|
+
let memberIds;
|
|
61
|
+
const memberSource = new Map();
|
|
62
|
+
if (custom.length > 0) {
|
|
63
|
+
membershipSource = "custom-allowlist";
|
|
64
|
+
memberIds = custom.map((model) => model.id);
|
|
65
|
+
for (const id of memberIds)
|
|
66
|
+
memberSource.set(id, "custom");
|
|
67
|
+
}
|
|
68
|
+
else if (discovery && discovery.complete) {
|
|
69
|
+
membershipSource = "complete-discovery";
|
|
70
|
+
memberIds = discovery.models.map((model) => model.id);
|
|
71
|
+
for (const id of memberIds)
|
|
72
|
+
memberSource.set(id, "dynamic");
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
membershipSource = "fallback-union";
|
|
76
|
+
memberIds = [];
|
|
77
|
+
for (const model of dynamic) {
|
|
78
|
+
if (!memberSource.has(model.id)) {
|
|
79
|
+
memberSource.set(model.id, "dynamic");
|
|
80
|
+
memberIds.push(model.id);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
for (const model of custom) {
|
|
84
|
+
if (!memberSource.has(model.id)) {
|
|
85
|
+
memberSource.set(model.id, "custom");
|
|
86
|
+
memberIds.push(model.id);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
for (const model of builtins) {
|
|
90
|
+
if (!memberSource.has(model.id)) {
|
|
91
|
+
memberSource.set(model.id, "builtin");
|
|
92
|
+
memberIds.push(model.id);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const models = memberIds.map((id) => ({
|
|
97
|
+
...metadataFor(id),
|
|
98
|
+
source: memberSource.get(id) ?? "builtin",
|
|
99
|
+
}));
|
|
100
|
+
const parentTier = models.find((model) => model.id === parent.model)?.tier
|
|
101
|
+
?? getBuiltinModel(effectiveProviderId, parent.model)?.tier;
|
|
102
|
+
const runnableProviderIds = registry.getEnabled().map((provider) => provider.id);
|
|
103
|
+
return {
|
|
104
|
+
parent: { ...parent, tier: parentTier },
|
|
105
|
+
effectiveProviderId,
|
|
106
|
+
membershipSource,
|
|
107
|
+
models,
|
|
108
|
+
authoritative: membershipSource === "custom-allowlist",
|
|
109
|
+
registryRevision: registry.getRoutingRevision(),
|
|
110
|
+
discoveryExpiresAt: discovery?.expiresAt,
|
|
111
|
+
runnableProviderIds,
|
|
112
|
+
resolvedCategories: resolveCategoriesForMenu(parent, parentTier, models, agentCategories, agentRouting),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/** Tier-resolution context derived from a snapshot (consumed by categories §3.2). */
|
|
116
|
+
export function tierContextFromSnapshot(snapshot, agentRouting) {
|
|
117
|
+
return {
|
|
118
|
+
parentTier: snapshot.parent.tier,
|
|
119
|
+
models: snapshot.models.map((model) => ({
|
|
120
|
+
id: model.id,
|
|
121
|
+
tier: model.tier,
|
|
122
|
+
routingPriority: model.routingPriority,
|
|
123
|
+
builtinIndex: model.builtinIndex,
|
|
124
|
+
})),
|
|
125
|
+
autoTier: agentRouting.autoTier,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Live accessor (§1.5): caches by (routing revision, parent route) and
|
|
130
|
+
* rebuilds lazily, so a discovery completion or provider mutation is
|
|
131
|
+
* reflected at the very next read. Building is synchronous over cached
|
|
132
|
+
* registry state — microseconds, never I/O.
|
|
133
|
+
*/
|
|
134
|
+
export function createRoutingSnapshotAccessor(registry, getAgentCategories, getAgentRouting) {
|
|
135
|
+
let cached;
|
|
136
|
+
return (parent) => {
|
|
137
|
+
if (cached
|
|
138
|
+
&& cached.registryRevision === registry.getRoutingRevision()
|
|
139
|
+
&& cached.parent.providerId === parent.providerId
|
|
140
|
+
&& cached.parent.model === parent.model
|
|
141
|
+
&& (cached.discoveryExpiresAt === undefined || Date.now() < cached.discoveryExpiresAt)) {
|
|
142
|
+
return cached;
|
|
143
|
+
}
|
|
144
|
+
cached = buildRoutingSnapshot(registry, parent, getAgentCategories(), getAgentRouting());
|
|
145
|
+
return cached;
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function resolveCategoriesForMenu(parent, parentTier, models, agentCategories, agentRouting) {
|
|
149
|
+
const merged = mergeAgentCategoriesWithProvenance(agentCategories);
|
|
150
|
+
const tierContext = {
|
|
151
|
+
parentTier,
|
|
152
|
+
models: models.map((model) => ({
|
|
153
|
+
id: model.id,
|
|
154
|
+
tier: model.tier,
|
|
155
|
+
routingPriority: model.routingPriority,
|
|
156
|
+
builtinIndex: model.builtinIndex,
|
|
157
|
+
})),
|
|
158
|
+
autoTier: agentRouting.autoTier,
|
|
159
|
+
};
|
|
160
|
+
return Object.entries(merged).map(([name, entry]) => {
|
|
161
|
+
const resolution = resolveSubagentRoute(name, { providerId: parent.providerId, model: parent.model, thinkingLevel: "medium" }, agentCategories, tierContext);
|
|
162
|
+
const route = "route" in resolution ? resolution.route : undefined;
|
|
163
|
+
return {
|
|
164
|
+
name,
|
|
165
|
+
model: route && route.model !== parent.model ? route.model : "inherit",
|
|
166
|
+
thinkingLevel: entry.config.thinkingLevel,
|
|
167
|
+
tierSource: entry.provenance.tierSource,
|
|
168
|
+
};
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
/** Tier of a model within a snapshot, when known. */
|
|
172
|
+
export function snapshotModelTier(snapshot, modelId) {
|
|
173
|
+
return snapshot.models.find((model) => model.id === modelId)?.tier;
|
|
174
|
+
}
|
|
175
|
+
/** Revision-cached index over all runnable providers' catalogs. */
|
|
176
|
+
export function createRoutableModelIndex(registry) {
|
|
177
|
+
let cachedRevision = -1;
|
|
178
|
+
let cached = [];
|
|
179
|
+
return () => {
|
|
180
|
+
const revision = registry.getRoutingRevision();
|
|
181
|
+
if (revision === cachedRevision)
|
|
182
|
+
return cached;
|
|
183
|
+
const entries = [];
|
|
184
|
+
for (const provider of registry.getEnabled()) {
|
|
185
|
+
const effectiveProviderId = provider.id === "openai" && provider.authType === "oauth"
|
|
186
|
+
? "openai-codex"
|
|
187
|
+
: provider.id;
|
|
188
|
+
const seen = new Set();
|
|
189
|
+
const push = (id, name) => {
|
|
190
|
+
if (seen.has(id))
|
|
191
|
+
return;
|
|
192
|
+
seen.add(id);
|
|
193
|
+
entries.push({ providerId: provider.id, id, name });
|
|
194
|
+
};
|
|
195
|
+
for (const model of registry.getModelConfig().getCustomModels(provider.id))
|
|
196
|
+
push(model.id, model.name);
|
|
197
|
+
for (const model of listDynamicModelMetadata(effectiveProviderId))
|
|
198
|
+
push(model.id, model.name);
|
|
199
|
+
for (const model of listBuiltinModels(effectiveProviderId))
|
|
200
|
+
push(model.id, model.name);
|
|
201
|
+
}
|
|
202
|
+
cachedRevision = revision;
|
|
203
|
+
cached = entries;
|
|
204
|
+
return cached;
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
/** Loose normalization for model-name matching: case/punctuation-insensitive. */
|
|
208
|
+
export function normalizeModelToken(value) {
|
|
209
|
+
return value.toLowerCase().replace(/[-._\s]/g, "");
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Near-match candidates for a (probably mistyped) model id within one
|
|
213
|
+
* provider's catalog. "suggest" matches normalized prefixes in either
|
|
214
|
+
* direction (soft "did you mean" notes). "truncation" matches only inputs
|
|
215
|
+
* the catalog EXTENDS ("gpt-5.6" -> gpt-5.6-sol/-terra/-luna) — the one
|
|
216
|
+
* direction that is typo evidence. An input extending a catalog id
|
|
217
|
+
* (gpt-5.6-sol-20260701 vs cataloged gpt-5.6-sol) is more likely a newly
|
|
218
|
+
* released variant the local catalog lags on, and must not be treated as a
|
|
219
|
+
* typo by hard-reject paths. Capped, deterministic order.
|
|
220
|
+
*/
|
|
221
|
+
export function nearModelMatches(input, candidates, options = {}) {
|
|
222
|
+
const limit = options.limit ?? 5;
|
|
223
|
+
const mode = options.mode ?? "suggest";
|
|
224
|
+
const normalized = normalizeModelToken(input);
|
|
225
|
+
if (normalized.length < 3)
|
|
226
|
+
return [];
|
|
227
|
+
return candidates
|
|
228
|
+
.filter((candidate) => {
|
|
229
|
+
const other = normalizeModelToken(candidate.id);
|
|
230
|
+
if (other === normalized)
|
|
231
|
+
return false;
|
|
232
|
+
if (other.startsWith(normalized))
|
|
233
|
+
return true;
|
|
234
|
+
return mode === "suggest" && normalized.startsWith(other);
|
|
235
|
+
})
|
|
236
|
+
.map((candidate) => candidate.id)
|
|
237
|
+
.sort()
|
|
238
|
+
.slice(0, limit);
|
|
239
|
+
}
|
|
240
|
+
export { selectTierCandidates };
|
|
@@ -8,17 +8,8 @@ export type SubagentThreadStatus = "queued" | "running" | "completed" | "failed"
|
|
|
8
8
|
* rendered in lifecycle tool replies (design doc §3.1) — a resume hint is
|
|
9
9
|
* emitted iff the runtime judged the run resumable, never as a blanket string.
|
|
10
10
|
*/
|
|
11
|
-
export type SubagentFinalReason = "completed" | "failed_transient" | "failed_fatal" | "rate_limited_exhausted" | "blocked" | "cancelled_interrupt" | "cancelled_user" | "
|
|
11
|
+
export type SubagentFinalReason = "completed" | "failed_transient" | "failed_fatal" | "rate_limited_exhausted" | "blocked" | "cancelled_interrupt" | "cancelled_user" | "cancelled_parent_abort";
|
|
12
12
|
export declare function isResumableReason(reason: SubagentFinalReason): boolean;
|
|
13
|
-
/** Per-child token budget, fixed at dispatch time (design doc §6). */
|
|
14
|
-
export interface SubagentTokenCap {
|
|
15
|
-
/** Soft cap: crossing it injects a wrap-up reminder into the child. */
|
|
16
|
-
soft: number;
|
|
17
|
-
/** Hard cap: crossing it aborts this child only. Updated at turn checks. */
|
|
18
|
-
hard: number;
|
|
19
|
-
/** Ledger tokens already attributed to this child when the run started. */
|
|
20
|
-
baseline: number;
|
|
21
|
-
}
|
|
22
13
|
export interface SubagentThreadSnapshot {
|
|
23
14
|
agentId: string;
|
|
24
15
|
runId: string;
|
|
@@ -40,7 +31,6 @@ export interface SubagentThreadSnapshot {
|
|
|
40
31
|
deliveredAt?: number;
|
|
41
32
|
/** 1-based position in the scheduler queue while status is "queued". */
|
|
42
33
|
queuePosition?: number;
|
|
43
|
-
tokenCap?: SubagentTokenCap;
|
|
44
34
|
/** Present for write_worktree children: where the isolated checkout lives. */
|
|
45
35
|
worktree?: SubagentWorktree;
|
|
46
36
|
}
|
|
@@ -63,8 +53,11 @@ export interface SubagentThreadRecord {
|
|
|
63
53
|
createdAt: number;
|
|
64
54
|
updatedAt: number;
|
|
65
55
|
deliveredAt?: number;
|
|
66
|
-
tokenCap?: SubagentTokenCap;
|
|
67
56
|
worktree?: SubagentWorktree;
|
|
57
|
+
/** True when the child was asked for schema-validated structured output:
|
|
58
|
+
* a compact JSON handoff is complete by construction, so the token-floor
|
|
59
|
+
* restate turn must not fire on it. */
|
|
60
|
+
expectsStructuredOutput?: boolean;
|
|
68
61
|
/** True for agents spawned inside a run_workflow: kept out of list_agents /
|
|
69
62
|
* wait_agent and not persisted (design v2 appendix; option C). */
|
|
70
63
|
workflowInternal?: boolean;
|
|
@@ -9,7 +9,6 @@ export function isResumableReason(reason) {
|
|
|
9
9
|
case "completed":
|
|
10
10
|
case "failed_fatal":
|
|
11
11
|
case "blocked":
|
|
12
|
-
case "cancelled_budget":
|
|
13
12
|
return false;
|
|
14
13
|
}
|
|
15
14
|
}
|
|
@@ -33,7 +32,6 @@ export function snapshotSubagentThread(record) {
|
|
|
33
32
|
createdAt: record.createdAt,
|
|
34
33
|
updatedAt: record.updatedAt,
|
|
35
34
|
deliveredAt: record.deliveredAt,
|
|
36
|
-
tokenCap: record.tokenCap ? { ...record.tokenCap } : undefined,
|
|
37
35
|
worktree: record.worktree ? { ...record.worktree } : undefined,
|
|
38
36
|
};
|
|
39
37
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Persistence (design §7): final-state children are written to
|
|
8
8
|
* `<persistDir>/<agentId>.json` as snapshot + compacted message history, so a
|
|
9
9
|
* later process can resume them via send_input. The on-disk schema carries
|
|
10
|
-
* `finalReason` / `resumable` / `deliveredAt`
|
|
10
|
+
* `finalReason` / `resumable` / `deliveredAt` — the fields the
|
|
11
11
|
* reply protocol and delivery dedup depend on. Child transcripts never mix
|
|
12
12
|
* into the parent transcript.
|
|
13
13
|
*/
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Persistence (design §7): final-state children are written to
|
|
8
8
|
* `<persistDir>/<agentId>.json` as snapshot + compacted message history, so a
|
|
9
9
|
* later process can resume them via send_input. The on-disk schema carries
|
|
10
|
-
* `finalReason` / `resumable` / `deliveredAt`
|
|
10
|
+
* `finalReason` / `resumable` / `deliveredAt` — the fields the
|
|
11
11
|
* reply protocol and delivery dedup depend on. Child transcripts never mix
|
|
12
12
|
* into the parent transcript.
|
|
13
13
|
*/
|
|
@@ -84,7 +84,6 @@ export class SubagentStore {
|
|
|
84
84
|
createdAt: record.createdAt,
|
|
85
85
|
updatedAt: record.updatedAt,
|
|
86
86
|
deliveredAt: record.deliveredAt,
|
|
87
|
-
tokenCap: record.tokenCap,
|
|
88
87
|
messages: record.agent?.messages ?? record.messages,
|
|
89
88
|
};
|
|
90
89
|
writeFileSync(join(this.persistDir, `${record.agentId}.json`), JSON.stringify(payload));
|
|
@@ -135,7 +134,6 @@ export class SubagentStore {
|
|
|
135
134
|
createdAt: parsed.createdAt,
|
|
136
135
|
updatedAt: parsed.updatedAt,
|
|
137
136
|
deliveredAt: parsed.deliveredAt,
|
|
138
|
-
tokenCap: parsed.tokenCap,
|
|
139
137
|
abortController: new AbortController(),
|
|
140
138
|
waiters: new Set(),
|
|
141
139
|
messages: parsed.messages,
|
|
@@ -16,6 +16,8 @@ export interface WorkflowRunRecord {
|
|
|
16
16
|
snapshots: SubagentThreadSnapshot[];
|
|
17
17
|
logs: string[];
|
|
18
18
|
result?: WorkflowResult;
|
|
19
|
+
/** Where the full rendered result was persisted (unset if the write failed). */
|
|
20
|
+
resultPath?: string;
|
|
19
21
|
abortController: AbortController;
|
|
20
22
|
waiters: Set<() => void>;
|
|
21
23
|
promise?: Promise<void>;
|
|
@@ -30,8 +32,27 @@ export interface WorkflowRunSnapshot {
|
|
|
30
32
|
status: WorkflowRunStatus;
|
|
31
33
|
agentCount: number;
|
|
32
34
|
result?: WorkflowResult;
|
|
35
|
+
resultPath?: string;
|
|
33
36
|
logs: string[];
|
|
34
37
|
snapshots: SubagentThreadSnapshot[];
|
|
35
38
|
}
|
|
36
39
|
/** System-reminder injected at the next turn when a background workflow finishes. */
|
|
40
|
+
/**
|
|
41
|
+
* Failed members must surface in EVERY channel that renders a finished
|
|
42
|
+
* workflow (delivery notice and wait_workflow): agent() converts failures to
|
|
43
|
+
* null inside the script, so without this line a "completed" workflow could
|
|
44
|
+
* silently omit items whose agents never delivered.
|
|
45
|
+
*/
|
|
46
|
+
export declare function workflowMemberWarning(snapshot: WorkflowRunSnapshot): string | undefined;
|
|
47
|
+
/** Chars of the result shown inline; the full rendering is persisted to resultPath. */
|
|
48
|
+
export declare const WORKFLOW_RESULT_PREVIEW_LIMIT = 8000;
|
|
49
|
+
export declare function renderWorkflowResultValue(value: unknown): string;
|
|
50
|
+
/**
|
|
51
|
+
* Result block shared by every channel that hands a finished workflow to the
|
|
52
|
+
* parent (delivery notice and wait_workflow). Truncation must preserve line
|
|
53
|
+
* structure — the value is often pretty-printed JSON or markdown — and a cut
|
|
54
|
+
* preview must always point at the persisted full result plus the durable fix
|
|
55
|
+
* (distill inside the script), or the parent silently works from partial data.
|
|
56
|
+
*/
|
|
57
|
+
export declare function buildWorkflowResultBlock(snapshot: WorkflowRunSnapshot, limit?: number): string[];
|
|
37
58
|
export declare function buildWorkflowDeliveryNotice(snapshot: WorkflowRunSnapshot): string;
|