@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
|
@@ -2,6 +2,9 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { discoverAgentProfiles, findAgentProfile } from "../agent/profiles.js";
|
|
4
4
|
import { parseThinkingLevel } from "../agent/categories.js";
|
|
5
|
+
import { THINKING_LEVELS } from "../types.js";
|
|
6
|
+
import { buildWorkflowResultBlock, workflowMemberWarning } from "../agent/workflow/control.js";
|
|
7
|
+
import { precompileWorkflowScript } from "../agent/workflow/runtime.js";
|
|
5
8
|
import { formatSubagentRoute } from "../agent/subagent-route-format.js";
|
|
6
9
|
/**
|
|
7
10
|
* Session-scoped trust decisions for project profiles, keyed by file path +
|
|
@@ -102,6 +105,7 @@ export function createSpawnAgentTool(options = {}, sharedTrust) {
|
|
|
102
105
|
"Start a child subagent in the background and return its agent_id plus random nickname.",
|
|
103
106
|
"The child has an independent thread; call wait_agent later to collect its result.",
|
|
104
107
|
"Proactively delegate multi-file investigations whose intermediate steps would be noise in the main conversation.",
|
|
108
|
+
"Best for helpers whose results you want to read and react to individually. For a uniform sweep over many items, or when member results should be aggregated before returning, weigh run_workflow instead. If the user asked for a workflow, an orchestration, or an agent team, that is run_workflow, not this tool.",
|
|
105
109
|
"Do the work yourself when it takes only a couple of tool calls or needs conversation context — unless the user explicitly asks for a subagent, in which case spawn one.",
|
|
106
110
|
"The child starts with zero context: write the task as a self-contained work order — state the goal, include known file paths or commands, and never make it rediscover knowledge you already hold.",
|
|
107
111
|
"After spawning, do not duplicate the same delegated work locally; either wait for the child or do clearly non-overlapping work.",
|
|
@@ -121,7 +125,7 @@ export function createSpawnAgentTool(options = {}, sharedTrust) {
|
|
|
121
125
|
agent: { type: "string", description: "Alias for agent_type." },
|
|
122
126
|
category: { type: "string", description: "Optional semantic category for model/thinking routing, such as quick, deep, explore, review, frontend, or writing." },
|
|
123
127
|
model: { type: "string", description: "Optional per-call model for this child, overriding category and profile. Bare name (e.g. claude-haiku-4-5) uses the parent provider; provider:model (e.g. anthropic:claude-opus-4-1) selects cross-provider." },
|
|
124
|
-
effort: { type: "string", enum: [
|
|
128
|
+
effort: { type: "string", enum: [...THINKING_LEVELS], description: "Optional per-call thinking level for this child, overriding category and profile." },
|
|
125
129
|
message: { type: "string", description: "Initial task for the subagent." },
|
|
126
130
|
task: { type: "string", description: "Alias for message." },
|
|
127
131
|
fork_context: { type: "boolean", description: "When true, copy recent parent conversation into the child thread." },
|
|
@@ -288,6 +292,7 @@ export function createSendInputTool() {
|
|
|
288
292
|
export function createCloseAgentTool() {
|
|
289
293
|
return {
|
|
290
294
|
name: "close_agent",
|
|
295
|
+
deferred: true,
|
|
291
296
|
readOnly: true,
|
|
292
297
|
effect: "read",
|
|
293
298
|
description: "Close a spawned subagent only when the delegated task is cancelled, stale, or no longer needed. Running children are cancelled before closing; do not close a child just because you started doing the same delegated work locally.",
|
|
@@ -324,6 +329,7 @@ export function createCloseAgentTool() {
|
|
|
324
329
|
export function createListAgentsTool() {
|
|
325
330
|
return {
|
|
326
331
|
name: "list_agents",
|
|
332
|
+
deferred: true,
|
|
327
333
|
readOnly: true,
|
|
328
334
|
effect: "read",
|
|
329
335
|
description: "List this session's subagents with their current status. Use it to recall which children exist before spawning duplicates or narrating progress.",
|
|
@@ -377,266 +383,31 @@ export function createListAgentsTool() {
|
|
|
377
383
|
},
|
|
378
384
|
};
|
|
379
385
|
}
|
|
380
|
-
|
|
381
|
-
export const AGENT_TEAM_MIN_ITEMS = 2;
|
|
382
|
-
export const AGENT_TEAM_MAX_ITEMS = 32;
|
|
383
|
-
export function createAgentTeamTool(options = {}, sharedTrust) {
|
|
386
|
+
export function createRunWorkflowTool(options = {}, sharedTrust) {
|
|
384
387
|
const trust = sharedTrust ?? new ProjectProfileTrust(options.approval);
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
`Use ${AGENT_TEAM_MIN_ITEMS}-${AGENT_TEAM_MAX_ITEMS} items. agent_team must be the ONLY tool call in your response; run other tools or further teams after it returns.`,
|
|
395
|
-
"Scoping rule: split items so members never overlap or conflict with each other.",
|
|
396
|
-
].join(" "),
|
|
397
|
-
parameters: {
|
|
398
|
-
type: "object",
|
|
399
|
-
properties: {
|
|
400
|
-
description: { type: "string", description: "Short (3-5 word) description of the team, shown in the UI." },
|
|
401
|
-
agent_type: { type: "string", description: "Subagent profile for every member. Defaults to default." },
|
|
402
|
-
category: { type: "string", description: "Optional semantic category for model/thinking routing." },
|
|
403
|
-
model: { type: "string", description: "Optional per-call model for every member, overriding category and profile (bare name or provider:model)." },
|
|
404
|
-
effort: { type: "string", enum: ["off", "minimal", "low", "medium", "high", "xhigh", "max"], description: "Optional per-call thinking level for every member." },
|
|
405
|
-
prompt_template: { type: "string", description: "Task template applied to each item. Must contain the literal placeholder {{item}}." },
|
|
406
|
-
items: {
|
|
407
|
-
type: "array",
|
|
408
|
-
description: `Items to fan out over (${AGENT_TEAM_MIN_ITEMS}-${AGENT_TEAM_MAX_ITEMS} unique strings); each becomes one subagent.`,
|
|
409
|
-
items: { type: "string" },
|
|
410
|
-
},
|
|
411
|
-
},
|
|
412
|
-
required: ["description", "prompt_template", "items"],
|
|
413
|
-
additionalProperties: false,
|
|
414
|
-
},
|
|
415
|
-
async execute(args, ctx) {
|
|
416
|
-
if (!ctx.agent?.runAgentTeam) {
|
|
417
|
-
return toolRuntimeMissing("agent_team");
|
|
418
|
-
}
|
|
419
|
-
const template = stringArg(args.prompt_template);
|
|
420
|
-
if (!template) {
|
|
421
|
-
return { content: "Error: agent_team requires prompt_template.", isError: true };
|
|
422
|
-
}
|
|
423
|
-
if (!template.includes("{{item}}")) {
|
|
424
|
-
return {
|
|
425
|
-
content: "Error: prompt_template must contain the literal placeholder {{item}} — it is replaced with each item. Example: \"Review {{item}} for risks.\"",
|
|
426
|
-
isError: true,
|
|
427
|
-
};
|
|
428
|
-
}
|
|
429
|
-
const rawItems = Array.isArray(args.items)
|
|
430
|
-
? args.items.filter((item) => typeof item === "string" && !!item.trim()).map((item) => item.trim())
|
|
431
|
-
: [];
|
|
432
|
-
const items = [...new Set(rawItems)];
|
|
433
|
-
if (items.length < AGENT_TEAM_MIN_ITEMS) {
|
|
434
|
-
return {
|
|
435
|
-
content: `Error: agent_team needs at least ${AGENT_TEAM_MIN_ITEMS} unique items after deduplication (got ${items.length}). For a single task use spawn_agent instead.`,
|
|
436
|
-
isError: true,
|
|
437
|
-
};
|
|
438
|
-
}
|
|
439
|
-
if (items.length > AGENT_TEAM_MAX_ITEMS) {
|
|
440
|
-
return {
|
|
441
|
-
content: `Error: agent_team accepts at most ${AGENT_TEAM_MAX_ITEMS} items (got ${items.length}). Split the work into sequential teams.`,
|
|
442
|
-
isError: true,
|
|
443
|
-
};
|
|
444
|
-
}
|
|
445
|
-
const profileName = stringArg(args.agent_type) ?? "default";
|
|
446
|
-
const resolved = resolveProfile(ctx.cwd, profileName, "both");
|
|
447
|
-
if ("error" in resolved)
|
|
448
|
-
return resolved.error;
|
|
449
|
-
const modeBlock = unsupportedProfile(resolved.profile);
|
|
450
|
-
if (modeBlock)
|
|
451
|
-
return modeBlock;
|
|
452
|
-
const trustBlock = await trust.ensureTrusted(resolved.profile);
|
|
453
|
-
if (trustBlock)
|
|
454
|
-
return trustBlock;
|
|
455
|
-
const effort = parseEffortArg(args.effort);
|
|
456
|
-
if ("error" in effort)
|
|
457
|
-
return effort.error;
|
|
458
|
-
try {
|
|
459
|
-
const snapshots = await ctx.agent.runAgentTeam(ctx.cwd, {
|
|
460
|
-
profile: resolved.profile,
|
|
461
|
-
category: stringArg(args.category),
|
|
462
|
-
model: stringArg(args.model),
|
|
463
|
-
effort: effort.value,
|
|
464
|
-
promptTemplate: template,
|
|
465
|
-
items,
|
|
466
|
-
parentToolCallId: ctx.toolCall?.id ?? snapshotFallbackId(),
|
|
467
|
-
emitUpdate: ctx.emitUpdate,
|
|
468
|
-
abortSignal: ctx.abortSignal,
|
|
469
|
-
});
|
|
470
|
-
const counts = teamStatusCounts(snapshots);
|
|
471
|
-
const lines = [
|
|
472
|
-
`agent_team "${stringArg(args.description) ?? "team"}": ${snapshots.length} members — ${counts}`,
|
|
473
|
-
"Failed or cancelled members can be resumed individually with send_input (see per-member guidance below).",
|
|
474
|
-
"",
|
|
475
|
-
...snapshots.flatMap((snapshot, index) => [
|
|
476
|
-
`### item ${index + 1}: ${truncateText(items[index] ?? "", 100)}`,
|
|
477
|
-
...formatSnapshot(snapshot),
|
|
478
|
-
"",
|
|
479
|
-
]),
|
|
480
|
-
];
|
|
481
|
-
return {
|
|
482
|
-
content: lines.join("\n").trim(),
|
|
483
|
-
status: snapshots.every((snapshot) => snapshot.status === "completed")
|
|
484
|
-
? "success"
|
|
485
|
-
: snapshots.some((snapshot) => snapshot.status === "completed")
|
|
486
|
-
? "partial"
|
|
487
|
-
: "blocked",
|
|
488
|
-
isError: snapshots.length > 0 && snapshots.every((snapshot) => snapshot.status !== "completed"),
|
|
489
|
-
metadata: {
|
|
490
|
-
kind: "subagent",
|
|
491
|
-
mode: "team",
|
|
492
|
-
subagents: snapshots.map(snapshotToMetadata),
|
|
493
|
-
},
|
|
494
|
-
};
|
|
495
|
-
}
|
|
496
|
-
catch (error) {
|
|
497
|
-
return toolError("agent_team", error);
|
|
498
|
-
}
|
|
499
|
-
},
|
|
388
|
+
// Single-flight: the interactive UI holds ONE pending approval at a time,
|
|
389
|
+
// so concurrent trust prompts from a parallel() fan-out would overwrite
|
|
390
|
+
// each other's resolver and hang the workflow. Serializing here also lets
|
|
391
|
+
// the first approval satisfy the rest of the same profile via the cache.
|
|
392
|
+
let trustChain = Promise.resolve();
|
|
393
|
+
const ensureProfileTrustedSerially = (profile) => {
|
|
394
|
+
const next = trustChain.then(() => trust.ensureTrusted(profile));
|
|
395
|
+
trustChain = next.then(() => undefined, () => undefined);
|
|
396
|
+
return next;
|
|
500
397
|
};
|
|
501
|
-
}
|
|
502
|
-
/** Specs bound for one agent_batch call (design v2 §1.3). */
|
|
503
|
-
export const AGENT_BATCH_MIN_SPECS = 2;
|
|
504
|
-
export const AGENT_BATCH_MAX_SPECS = 32;
|
|
505
|
-
export function createAgentBatchTool(options = {}, sharedTrust) {
|
|
506
|
-
const trust = sharedTrust ?? new ProjectProfileTrust(options.approval);
|
|
507
|
-
return {
|
|
508
|
-
name: "agent_batch",
|
|
509
|
-
readOnly: true,
|
|
510
|
-
effect: "read",
|
|
511
|
-
description: [
|
|
512
|
-
"Run several DIFFERENT subagent tasks in parallel as one tool call (heterogeneous fan-out).",
|
|
513
|
-
"Unlike agent_team (one template over many items), each spec is its own task with its own optional model/effort/profile/output_schema.",
|
|
514
|
-
"Use it to run, e.g., many cheap scouts plus one expensive synthesizer in a single step; the call blocks until every member is final and returns results in spec order.",
|
|
515
|
-
`Provide ${AGENT_BATCH_MIN_SPECS}-${AGENT_BATCH_MAX_SPECS} specs. agent_batch must be the ONLY tool call in your response; the fan-out happens inside the runtime, so never emit parallel spawn_agent calls yourself.`,
|
|
516
|
-
"Scoping rule: split specs so members never overlap or conflict.",
|
|
517
|
-
].join(" "),
|
|
518
|
-
parameters: {
|
|
519
|
-
type: "object",
|
|
520
|
-
properties: {
|
|
521
|
-
description: { type: "string", description: "Short (3-5 word) description of the batch, shown in the UI." },
|
|
522
|
-
specs: {
|
|
523
|
-
type: "array",
|
|
524
|
-
description: `Heterogeneous member specs (${AGENT_BATCH_MIN_SPECS}-${AGENT_BATCH_MAX_SPECS}); each becomes one subagent.`,
|
|
525
|
-
items: {
|
|
526
|
-
type: "object",
|
|
527
|
-
properties: {
|
|
528
|
-
task: { type: "string", description: "Self-contained task for this member." },
|
|
529
|
-
agent_type: { type: "string", description: "Subagent profile for this member. Defaults to default." },
|
|
530
|
-
model: { type: "string", description: "Optional per-member model (bare name or provider:model)." },
|
|
531
|
-
effort: { type: "string", enum: ["off", "minimal", "low", "medium", "high", "xhigh", "max"], description: "Optional per-member thinking level." },
|
|
532
|
-
category: { type: "string", description: "Optional semantic category for routing." },
|
|
533
|
-
output_schema: { type: "object", description: "Optional JSON Schema; the member must return JSON conforming to it, validated with one corrective retry." },
|
|
534
|
-
},
|
|
535
|
-
required: ["task"],
|
|
536
|
-
additionalProperties: false,
|
|
537
|
-
},
|
|
538
|
-
},
|
|
539
|
-
},
|
|
540
|
-
required: ["description", "specs"],
|
|
541
|
-
additionalProperties: false,
|
|
542
|
-
},
|
|
543
|
-
async execute(args, ctx) {
|
|
544
|
-
if (!ctx.agent?.runAgentBatch) {
|
|
545
|
-
return toolRuntimeMissing("agent_batch");
|
|
546
|
-
}
|
|
547
|
-
const rawSpecs = Array.isArray(args.specs) ? args.specs : [];
|
|
548
|
-
if (rawSpecs.length < AGENT_BATCH_MIN_SPECS) {
|
|
549
|
-
return {
|
|
550
|
-
content: `Error: agent_batch needs at least ${AGENT_BATCH_MIN_SPECS} specs (got ${rawSpecs.length}). For a single task use spawn_agent instead.`,
|
|
551
|
-
isError: true,
|
|
552
|
-
};
|
|
553
|
-
}
|
|
554
|
-
if (rawSpecs.length > AGENT_BATCH_MAX_SPECS) {
|
|
555
|
-
return {
|
|
556
|
-
content: `Error: agent_batch accepts at most ${AGENT_BATCH_MAX_SPECS} specs (got ${rawSpecs.length}). Split into sequential batches.`,
|
|
557
|
-
isError: true,
|
|
558
|
-
};
|
|
559
|
-
}
|
|
560
|
-
const specs = [];
|
|
561
|
-
for (let index = 0; index < rawSpecs.length; index++) {
|
|
562
|
-
const raw = (rawSpecs[index] ?? {});
|
|
563
|
-
const task = stringArg(raw.task);
|
|
564
|
-
if (!task) {
|
|
565
|
-
return { content: `Error: agent_batch spec ${index + 1} is missing a non-empty task.`, isError: true };
|
|
566
|
-
}
|
|
567
|
-
const profileName = stringArg(raw.agent_type) ?? stringArg(raw.agent) ?? "default";
|
|
568
|
-
const resolved = resolveProfile(ctx.cwd, profileName, "both");
|
|
569
|
-
if ("error" in resolved)
|
|
570
|
-
return resolved.error;
|
|
571
|
-
const modeBlock = unsupportedProfile(resolved.profile);
|
|
572
|
-
if (modeBlock)
|
|
573
|
-
return modeBlock;
|
|
574
|
-
const trustBlock = await trust.ensureTrusted(resolved.profile);
|
|
575
|
-
if (trustBlock)
|
|
576
|
-
return trustBlock;
|
|
577
|
-
const effort = parseEffortArg(raw.effort);
|
|
578
|
-
if ("error" in effort)
|
|
579
|
-
return effort.error;
|
|
580
|
-
const outputSchema = raw.output_schema && typeof raw.output_schema === "object" ? raw.output_schema : undefined;
|
|
581
|
-
specs.push({
|
|
582
|
-
task,
|
|
583
|
-
profile: resolved.profile,
|
|
584
|
-
category: stringArg(raw.category),
|
|
585
|
-
model: stringArg(raw.model),
|
|
586
|
-
effort: effort.value,
|
|
587
|
-
outputSchema,
|
|
588
|
-
});
|
|
589
|
-
}
|
|
590
|
-
try {
|
|
591
|
-
const snapshots = await ctx.agent.runAgentBatch(ctx.cwd, {
|
|
592
|
-
specs,
|
|
593
|
-
parentToolCallId: ctx.toolCall?.id ?? snapshotFallbackId(),
|
|
594
|
-
emitUpdate: ctx.emitUpdate,
|
|
595
|
-
abortSignal: ctx.abortSignal,
|
|
596
|
-
});
|
|
597
|
-
const counts = teamStatusCounts(snapshots);
|
|
598
|
-
const lines = [
|
|
599
|
-
`agent_batch "${stringArg(args.description) ?? "batch"}": ${snapshots.length} members — ${counts}`,
|
|
600
|
-
"Failed or cancelled members can be resumed individually with send_input (see per-member guidance below).",
|
|
601
|
-
"",
|
|
602
|
-
...snapshots.flatMap((snapshot, index) => [
|
|
603
|
-
`### member ${index + 1}: ${truncateText(specs[index]?.task ?? "", 100)}`,
|
|
604
|
-
...formatSnapshot(snapshot),
|
|
605
|
-
"",
|
|
606
|
-
]),
|
|
607
|
-
];
|
|
608
|
-
return {
|
|
609
|
-
content: lines.join("\n").trim(),
|
|
610
|
-
status: snapshots.every((snapshot) => snapshot.status === "completed")
|
|
611
|
-
? "success"
|
|
612
|
-
: snapshots.some((snapshot) => snapshot.status === "completed")
|
|
613
|
-
? "partial"
|
|
614
|
-
: "blocked",
|
|
615
|
-
isError: snapshots.length > 0 && snapshots.every((snapshot) => snapshot.status !== "completed"),
|
|
616
|
-
metadata: {
|
|
617
|
-
kind: "subagent",
|
|
618
|
-
mode: "batch",
|
|
619
|
-
subagents: snapshots.map(snapshotToMetadata),
|
|
620
|
-
},
|
|
621
|
-
};
|
|
622
|
-
}
|
|
623
|
-
catch (error) {
|
|
624
|
-
return toolError("agent_batch", error);
|
|
625
|
-
}
|
|
626
|
-
},
|
|
627
|
-
};
|
|
628
|
-
}
|
|
629
|
-
export function createRunWorkflowTool(options = {}) {
|
|
630
|
-
void options;
|
|
631
398
|
return {
|
|
632
399
|
name: "run_workflow",
|
|
633
400
|
readOnly: true,
|
|
634
401
|
effect: "read",
|
|
635
402
|
description: [
|
|
636
403
|
"Run an LLM-authored JavaScript orchestration script (dynamic workflow) that coordinates many subagents with deterministic control flow.",
|
|
637
|
-
"
|
|
638
|
-
"
|
|
639
|
-
"
|
|
404
|
+
"This is THE tool whenever the user asks for a workflow, an orchestration, or an agent team — even a small one; do not substitute parallel spawn_agent calls for an explicit request.",
|
|
405
|
+
"Also use it for tasks that need loops, conditional fan-out, or staged pipelines over dozens of subagents whose intermediate steps should stay out of this conversation — e.g. a codebase-wide audit, a migration, or cross-checked research.",
|
|
406
|
+
"The script's only capability is agent(prompt, opts?) — each call spawns a sandboxed readonly subagent; opts may set {model, effort, agentType, category, schema, label}. Give each agent a short unique label. Also available: parallel(thunks), pipeline(items, ...stages), phase(title), log(msg), the global args, and budget {total, spent(), remaining()}.",
|
|
407
|
+
"End the script with `return <value>`; that value (only) comes back to you. Return distilled conclusions, not raw agent transcripts — reduce inside the script (plain JS filtering or a final synthesis agent); a return value past ~8000 chars reaches you truncated (full copy on disk). The script has no filesystem/shell/network/clock/random access. run_workflow must be the ONLY tool call in your response; it blocks until the workflow finishes.",
|
|
408
|
+
"A failed or blocked agent() resolves to null inside parallel/pipeline (a bare await agent() throws instead): check for null slots and return which items failed alongside the results — never silently drop them. Keep a final synthesis/aggregation step inside try/catch (or parallel) so its failure cannot discard every completed member's work.",
|
|
409
|
+
"Write plain JavaScript only — no TypeScript syntax, no import/require, and no Date or Math.random (they throw; pass timestamps in via args).",
|
|
410
|
+
"parallel() takes an array of FUNCTIONS, not promises: await parallel(items.map(item => () => agent(...))). Passing promises throws a TypeError.",
|
|
640
411
|
"Example: `export const meta = { name: 'audit', description: 'auth audit' };\\nconst files = args;\\nconst findings = await parallel(files.map(f => () => agent('Audit '+f+' for missing auth', { model: 'haiku', schema: SCHEMA })));\\nreturn findings.filter(Boolean);`",
|
|
641
412
|
].join(" "),
|
|
642
413
|
parameters: {
|
|
@@ -657,6 +428,16 @@ export function createRunWorkflowTool(options = {}) {
|
|
|
657
428
|
if (!script) {
|
|
658
429
|
return { content: "Error: run_workflow requires a non-empty script.", isError: true };
|
|
659
430
|
}
|
|
431
|
+
// Submit-time syntax probe (same QuickJS engine, compile-only): a broken
|
|
432
|
+
// script fails HERE as the immediate tool result instead of one turn
|
|
433
|
+
// later through the background delivery path.
|
|
434
|
+
const precheck = await precompileWorkflowScript(script);
|
|
435
|
+
if (!precheck.ok) {
|
|
436
|
+
return {
|
|
437
|
+
content: `run_workflow rejected before launch: ${precheck.error}. Fix the script and re-issue run_workflow.`,
|
|
438
|
+
isError: true,
|
|
439
|
+
};
|
|
440
|
+
}
|
|
660
441
|
try {
|
|
661
442
|
const { runId, title } = ctx.agent.startWorkflow(ctx.cwd, {
|
|
662
443
|
script,
|
|
@@ -664,6 +445,9 @@ export function createRunWorkflowTool(options = {}) {
|
|
|
664
445
|
title: stringArg(args.title),
|
|
665
446
|
parentToolCallId: ctx.toolCall?.id ?? snapshotFallbackId(),
|
|
666
447
|
abortSignal: ctx.abortSignal,
|
|
448
|
+
// Project-local profiles named via agent(..., {agentType}) pass the
|
|
449
|
+
// same first-use trust gate as spawn_agent (Codex review on #58).
|
|
450
|
+
ensureProfileTrusted: ensureProfileTrustedSerially,
|
|
667
451
|
});
|
|
668
452
|
return {
|
|
669
453
|
content: [
|
|
@@ -729,17 +513,14 @@ export function createWaitWorkflowTool() {
|
|
|
729
513
|
metadata: { kind: "subagent", mode: "workflow", subagents: snapshot.snapshots.map(snapshotToMetadata) },
|
|
730
514
|
};
|
|
731
515
|
}
|
|
732
|
-
const
|
|
733
|
-
? snapshot.result.value
|
|
734
|
-
: JSON.stringify(snapshot.result.value, null, 2);
|
|
516
|
+
const memberWarning = workflowMemberWarning(snapshot);
|
|
735
517
|
return {
|
|
736
518
|
content: [
|
|
737
519
|
`workflow "${snapshot.title}" (${runId}) completed (${snapshot.agentCount} agents).`,
|
|
520
|
+
...(memberWarning ? [memberWarning] : []),
|
|
738
521
|
...(snapshot.logs.length > 0 ? ["", "Log:", ...snapshot.logs.slice(-20)] : []),
|
|
739
522
|
"",
|
|
740
|
-
|
|
741
|
-
truncateText(rendered, 8000),
|
|
742
|
-
"--- end workflow result ---",
|
|
523
|
+
...buildWorkflowResultBlock(snapshot),
|
|
743
524
|
].join("\n"),
|
|
744
525
|
status: "success",
|
|
745
526
|
metadata: { kind: "subagent", mode: "workflow", subagents: snapshot.snapshots.map(snapshotToMetadata) },
|
|
@@ -747,13 +528,6 @@ export function createWaitWorkflowTool() {
|
|
|
747
528
|
},
|
|
748
529
|
};
|
|
749
530
|
}
|
|
750
|
-
function teamStatusCounts(snapshots) {
|
|
751
|
-
const counts = new Map();
|
|
752
|
-
for (const snapshot of snapshots) {
|
|
753
|
-
counts.set(snapshot.status, (counts.get(snapshot.status) ?? 0) + 1);
|
|
754
|
-
}
|
|
755
|
-
return [...counts.entries()].map(([status, count]) => `${status} ${count}`).join(" / ");
|
|
756
|
-
}
|
|
757
531
|
export function createAgentLifecycleTools(options = {}) {
|
|
758
532
|
const trust = new ProjectProfileTrust(options.approval);
|
|
759
533
|
return [
|
|
@@ -762,9 +536,7 @@ export function createAgentLifecycleTools(options = {}) {
|
|
|
762
536
|
createSendInputTool(),
|
|
763
537
|
createCloseAgentTool(),
|
|
764
538
|
createListAgentsTool(),
|
|
765
|
-
|
|
766
|
-
createAgentBatchTool(options, trust),
|
|
767
|
-
createRunWorkflowTool(options),
|
|
539
|
+
createRunWorkflowTool(options, trust),
|
|
768
540
|
createWaitWorkflowTool(),
|
|
769
541
|
];
|
|
770
542
|
}
|
|
@@ -907,8 +679,6 @@ function finalGuidanceLines(snapshot) {
|
|
|
907
679
|
case "cancelled_user":
|
|
908
680
|
case "cancelled_parent_abort":
|
|
909
681
|
return ["", resumeLine(snapshot.agentId)];
|
|
910
|
-
case "cancelled_budget":
|
|
911
|
-
return ["", "budget exhausted — do not resume this child; integrate what it already reported and narrow the task if more is needed"];
|
|
912
682
|
case "blocked":
|
|
913
683
|
return ["", "blocked: re-spawn with an adjusted profile or approval setting — resuming as-is would hit the same block"];
|
|
914
684
|
case "failed_fatal":
|
|
@@ -972,7 +742,7 @@ function parseEffortArg(value) {
|
|
|
972
742
|
if (!parsed) {
|
|
973
743
|
return {
|
|
974
744
|
error: {
|
|
975
|
-
content: `Error: effort must be one of
|
|
745
|
+
content: `Error: effort must be one of ${THINKING_LEVELS.join(", ")} (got ${JSON.stringify(value)}).`,
|
|
976
746
|
isError: true,
|
|
977
747
|
},
|
|
978
748
|
};
|
package/dist/tools/bash.js
CHANGED
|
@@ -16,7 +16,7 @@ export function createBashTool(cwd, approval, _fileState) {
|
|
|
16
16
|
name: "bash",
|
|
17
17
|
effect: "unknown",
|
|
18
18
|
requiresApproval: true,
|
|
19
|
-
description: "Execute a bounded bash command in the working directory. Use timeout for long-running commands. For persistent dev servers or watchers such as npm run dev, next dev, vite, or webpack --watch, use
|
|
19
|
+
description: "Execute a bounded bash command in the working directory. Use timeout for long-running commands. For persistent dev servers or watchers such as npm run dev, next dev, vite, or webpack --watch, use the managed server tools instead of backgrounding a bash command (deferred: load them first via tool_search with query 'select:start_server,server_status,server_logs,stop_server').",
|
|
20
20
|
parameters: {
|
|
21
21
|
type: "object",
|
|
22
22
|
properties: {
|
|
@@ -76,6 +76,8 @@ export class WorktreeApprovalController {
|
|
|
76
76
|
return { action: "approve" };
|
|
77
77
|
case "agent_profile":
|
|
78
78
|
return { action: "reject", feedback: "Subagents cannot approve agent profiles." };
|
|
79
|
+
case "external_tool":
|
|
80
|
+
return { action: "reject", feedback: "Subagents cannot approve external runtime tools." };
|
|
79
81
|
}
|
|
80
82
|
}
|
|
81
83
|
checkRules() {
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ export { createTodoTool, type TodoStore } from "./todo.js";
|
|
|
19
19
|
export { createExitPlanModeTool, type PlanController } from "./exit-plan-mode.js";
|
|
20
20
|
export { createToolSearchTool, type ToolSearchController } from "./tool-search.js";
|
|
21
21
|
export { createQuestionTool } from "./question.js";
|
|
22
|
-
export {
|
|
22
|
+
export { createMemoryTool } from "./memory.js";
|
|
23
23
|
import type { ToolRegistryEntry } from "../types.js";
|
|
24
24
|
import type { ApprovalController } from "../approval/types.js";
|
|
25
25
|
import type { SkillRegistry } from "../skills/registry.js";
|
|
@@ -45,7 +45,7 @@ export interface CreateAllToolsOptions {
|
|
|
45
45
|
* files before mutating them so /rewind can restore.
|
|
46
46
|
*/
|
|
47
47
|
checkpoints?: () => CheckpointStore | undefined;
|
|
48
|
-
/** Shared goal state; when present, registers the
|
|
48
|
+
/** Shared goal state; when present, registers the update_goal tool. */
|
|
49
49
|
goalStore?: GoalStore;
|
|
50
50
|
}
|
|
51
51
|
export declare function createAllTools(cwd: string, skillRegistry?: SkillRegistry, options?: CreateAllToolsOptions): ToolRegistryEntry[];
|
package/dist/tools/index.js
CHANGED
|
@@ -19,7 +19,7 @@ export { createTodoTool } from "./todo.js";
|
|
|
19
19
|
export { createExitPlanModeTool } from "./exit-plan-mode.js";
|
|
20
20
|
export { createToolSearchTool } from "./tool-search.js";
|
|
21
21
|
export { createQuestionTool } from "./question.js";
|
|
22
|
-
export {
|
|
22
|
+
export { createMemoryTool } from "./memory.js";
|
|
23
23
|
import { createBashTool } from "./bash.js";
|
|
24
24
|
import { createManagedServerTools } from "./server.js";
|
|
25
25
|
import { createEditTool } from "./edit.js";
|
|
@@ -38,7 +38,7 @@ import { createWebFetchTool } from "./web-fetch.js";
|
|
|
38
38
|
import { createWebSearchTool } from "./web-search.js";
|
|
39
39
|
import { createWriteTool } from "./write.js";
|
|
40
40
|
import { createQuestionTool } from "./question.js";
|
|
41
|
-
import {
|
|
41
|
+
import { createMemoryTool } from "./memory.js";
|
|
42
42
|
import { FileStateTracker } from "./file-state.js";
|
|
43
43
|
import { createGoalTools } from "../goal/tools.js";
|
|
44
44
|
export function createAllTools(cwd, skillRegistry, options = {}) {
|
|
@@ -56,14 +56,16 @@ export function createAllTools(cwd, skillRegistry, options = {}) {
|
|
|
56
56
|
createLspTool(cwd, lsp, approval),
|
|
57
57
|
createWebSearchTool(),
|
|
58
58
|
createWebFetchTool(approval),
|
|
59
|
-
|
|
60
|
-
createMemoryReadSummaryTool(cwd),
|
|
59
|
+
createMemoryTool(cwd),
|
|
61
60
|
...createAgentLifecycleTools({ cwd, approval }),
|
|
61
|
+
// Always registered: any host can carry deferred tools (MCP entries mark
|
|
62
|
+
// themselves deferred), so every host needs the unlock path. Falls back
|
|
63
|
+
// to the executing agent when no controller is wired.
|
|
64
|
+
createToolSearchTool(options.toolSearchController),
|
|
62
65
|
...(options.questionController ? [createQuestionTool(options.questionController)] : []),
|
|
63
66
|
...(skillRegistry ? [createSkillSearchTool(skillRegistry), createSkillTool(skillRegistry)] : []),
|
|
64
67
|
...(options.todoStore ? [createTodoTool(options.todoStore)] : []),
|
|
65
68
|
...(options.planController ? [createExitPlanModeTool(options.planController)] : []),
|
|
66
|
-
...(options.toolSearchController ? [createToolSearchTool(options.toolSearchController)] : []),
|
|
67
69
|
...(options.goalStore ? createGoalTools(options.goalStore) : []),
|
|
68
70
|
];
|
|
69
71
|
}
|
package/dist/tools/lsp.js
CHANGED
package/dist/tools/memory.d.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
import type { ToolRegistryEntry } from "../types.js";
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Single entry point to persistent memory: without a query it reads the
|
|
4
|
+
* concise memory summary, with a query it searches memory. One schema in the
|
|
5
|
+
* per-turn tool list instead of two (the read/search split cost context on
|
|
6
|
+
* every turn while both modes share this file's backing code).
|
|
7
|
+
*/
|
|
8
|
+
export declare function createMemoryTool(cwd: string): ToolRegistryEntry;
|
package/dist/tools/memory.js
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import { readMemorySummary, searchMemory } from "../memory/index.js";
|
|
2
2
|
const MEMORY_SCOPE_ENUM = ["project", "global", "all"];
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Single entry point to persistent memory: without a query it reads the
|
|
5
|
+
* concise memory summary, with a query it searches memory. One schema in the
|
|
6
|
+
* per-turn tool list instead of two (the read/search split cost context on
|
|
7
|
+
* every turn while both modes share this file's backing code).
|
|
8
|
+
*/
|
|
9
|
+
export function createMemoryTool(cwd) {
|
|
4
10
|
return {
|
|
5
|
-
name: "
|
|
6
|
-
description: "
|
|
11
|
+
name: "memory",
|
|
12
|
+
description: "Read or search persistent Bubble memory (prior project facts, user preferences, workflows, decisions, gotchas). " +
|
|
13
|
+
"Without query, returns the concise memory summary (scope defaults to project). " +
|
|
14
|
+
"With query, searches memory entries (scope defaults to all, project memory first).",
|
|
7
15
|
readOnly: true,
|
|
8
16
|
effect: "read",
|
|
9
17
|
parameters: {
|
|
@@ -11,25 +19,37 @@ export function createMemorySearchTool(cwd) {
|
|
|
11
19
|
properties: {
|
|
12
20
|
query: {
|
|
13
21
|
type: "string",
|
|
14
|
-
description: "Search query. Use concrete terms such as file names, feature names, commands, or error text.",
|
|
22
|
+
description: "Search query. Use concrete terms such as file names, feature names, commands, or error text. Omit to read the memory summary instead.",
|
|
15
23
|
},
|
|
16
24
|
scope: {
|
|
17
25
|
type: "string",
|
|
18
26
|
enum: MEMORY_SCOPE_ENUM,
|
|
19
|
-
description: "Memory scope
|
|
27
|
+
description: "Memory scope. Defaults to project when reading the summary, all when searching.",
|
|
20
28
|
},
|
|
21
29
|
limit: {
|
|
22
30
|
type: "number",
|
|
23
|
-
description: "Maximum number of results to return. Defaults to 12.",
|
|
31
|
+
description: "Maximum number of search results to return. Defaults to 12. Ignored for summary reads.",
|
|
24
32
|
},
|
|
25
33
|
},
|
|
26
|
-
required: ["query"],
|
|
27
34
|
additionalProperties: false,
|
|
28
35
|
},
|
|
29
36
|
async execute(args) {
|
|
30
37
|
const query = typeof args.query === "string" ? args.query.trim() : "";
|
|
31
38
|
if (!query) {
|
|
32
|
-
|
|
39
|
+
const scope = parseScope(args.scope, "project");
|
|
40
|
+
const summaries = readMemorySummary(cwd, scope);
|
|
41
|
+
if (summaries.length === 0) {
|
|
42
|
+
return { content: `No ${scope} memory summary is available.`, status: "no_match" };
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
content: summaries.map((summary) => [
|
|
46
|
+
`# ${summary.scope} memory summary`,
|
|
47
|
+
`Path: ${summary.path}`,
|
|
48
|
+
"",
|
|
49
|
+
summary.content,
|
|
50
|
+
].join("\n")).join("\n\n---\n\n"),
|
|
51
|
+
metadata: { kind: "read", matches: summaries.length },
|
|
52
|
+
};
|
|
33
53
|
}
|
|
34
54
|
const scope = parseScope(args.scope);
|
|
35
55
|
const limit = typeof args.limit === "number" && Number.isFinite(args.limit)
|
|
@@ -52,41 +72,6 @@ export function createMemorySearchTool(cwd) {
|
|
|
52
72
|
},
|
|
53
73
|
};
|
|
54
74
|
}
|
|
55
|
-
export function createMemoryReadSummaryTool(cwd) {
|
|
56
|
-
return {
|
|
57
|
-
name: "memory_read_summary",
|
|
58
|
-
description: "Read the concise persistent memory summary for the current project, global scope, or both.",
|
|
59
|
-
readOnly: true,
|
|
60
|
-
effect: "read",
|
|
61
|
-
parameters: {
|
|
62
|
-
type: "object",
|
|
63
|
-
properties: {
|
|
64
|
-
scope: {
|
|
65
|
-
type: "string",
|
|
66
|
-
enum: MEMORY_SCOPE_ENUM,
|
|
67
|
-
description: "Memory scope to read. Defaults to project.",
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
additionalProperties: false,
|
|
71
|
-
},
|
|
72
|
-
async execute(args) {
|
|
73
|
-
const scope = parseScope(args.scope, "project");
|
|
74
|
-
const summaries = readMemorySummary(cwd, scope);
|
|
75
|
-
if (summaries.length === 0) {
|
|
76
|
-
return { content: `No ${scope} memory summary is available.`, status: "no_match" };
|
|
77
|
-
}
|
|
78
|
-
return {
|
|
79
|
-
content: summaries.map((summary) => [
|
|
80
|
-
`# ${summary.scope} memory summary`,
|
|
81
|
-
`Path: ${summary.path}`,
|
|
82
|
-
"",
|
|
83
|
-
summary.content,
|
|
84
|
-
].join("\n")).join("\n\n---\n\n"),
|
|
85
|
-
metadata: { kind: "read", matches: summaries.length },
|
|
86
|
-
};
|
|
87
|
-
},
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
75
|
function parseScope(value, fallback = "all") {
|
|
91
76
|
return value === "project" || value === "global" || value === "all" ? value : fallback;
|
|
92
77
|
}
|