@astralyn/pi 0.81.1-1 → 0.82.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +53 -0
- package/README.md +690 -677
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +1 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/config-selector.d.ts.map +1 -1
- package/dist/cli/config-selector.js +1 -1
- package/dist/cli/config-selector.js.map +1 -1
- package/dist/cli/startup-ui.d.ts.map +1 -1
- package/dist/cli/startup-ui.js +1 -1
- package/dist/cli/startup-ui.js.map +1 -1
- package/dist/cli.js +0 -0
- package/dist/core/agent-session.d.ts +11 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +7 -8
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +11 -3
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +4 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +9 -1
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/model-config.d.ts +30 -0
- package/dist/core/model-config.d.ts.map +1 -1
- package/dist/core/model-config.js +6 -0
- package/dist/core/model-config.js.map +1 -1
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +2 -2
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +7 -0
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/model-runtime.d.ts +0 -1
- package/dist/core/model-runtime.d.ts.map +1 -1
- package/dist/core/model-runtime.js +3 -5
- package/dist/core/model-runtime.js.map +1 -1
- package/dist/core/remote-catalog-provider.d.ts +1 -1
- package/dist/core/remote-catalog-provider.d.ts.map +1 -1
- package/dist/core/remote-catalog-provider.js +5 -10
- package/dist/core/remote-catalog-provider.js.map +1 -1
- package/dist/core/settings-manager.d.ts +1 -1
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +1 -1
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/bash.d.ts +2 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +114 -24
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +1 -0
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +4 -3
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.js +5 -1
- package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
- package/dist/extensions/index.d.ts.map +1 -1
- package/dist/extensions/index.js +2 -0
- package/dist/extensions/index.js.map +1 -1
- package/dist/extensions/llama/provider.d.ts.map +1 -1
- package/dist/extensions/llama/provider.js +1 -2
- package/dist/extensions/llama/provider.js.map +1 -1
- package/dist/extensions/subagent/activity.d.ts +16 -0
- package/dist/extensions/subagent/activity.d.ts.map +1 -0
- package/dist/extensions/subagent/activity.js +150 -0
- package/dist/extensions/subagent/activity.js.map +1 -0
- package/dist/extensions/subagent/agents.d.ts +7 -0
- package/dist/extensions/subagent/agents.d.ts.map +1 -0
- package/dist/extensions/subagent/agents.js +218 -0
- package/dist/extensions/subagent/agents.js.map +1 -0
- package/dist/extensions/subagent/constants.d.ts +22 -0
- package/dist/extensions/subagent/constants.d.ts.map +1 -0
- package/dist/extensions/subagent/constants.js +32 -0
- package/dist/extensions/subagent/constants.js.map +1 -0
- package/dist/extensions/subagent/index.d.ts +3 -0
- package/dist/extensions/subagent/index.d.ts.map +1 -0
- package/dist/extensions/subagent/index.js +249 -0
- package/dist/extensions/subagent/index.js.map +1 -0
- package/dist/extensions/subagent/picker.d.ts +31 -0
- package/dist/extensions/subagent/picker.d.ts.map +1 -0
- package/dist/extensions/subagent/picker.js +116 -0
- package/dist/extensions/subagent/picker.js.map +1 -0
- package/dist/extensions/subagent/render.d.ts +8 -0
- package/dist/extensions/subagent/render.d.ts.map +1 -0
- package/dist/extensions/subagent/render.js +322 -0
- package/dist/extensions/subagent/render.js.map +1 -0
- package/dist/extensions/subagent/resolve.d.ts +16 -0
- package/dist/extensions/subagent/resolve.d.ts.map +1 -0
- package/dist/extensions/subagent/resolve.js +103 -0
- package/dist/extensions/subagent/resolve.js.map +1 -0
- package/dist/extensions/subagent/runner.d.ts +30 -0
- package/dist/extensions/subagent/runner.d.ts.map +1 -0
- package/dist/extensions/subagent/runner.js +355 -0
- package/dist/extensions/subagent/runner.js.map +1 -0
- package/dist/extensions/subagent/schema.d.ts +28 -0
- package/dist/extensions/subagent/schema.d.ts.map +1 -0
- package/dist/extensions/subagent/schema.js +33 -0
- package/dist/extensions/subagent/schema.js.map +1 -0
- package/dist/extensions/subagent/sdk-runner.d.ts +14 -0
- package/dist/extensions/subagent/sdk-runner.d.ts.map +1 -0
- package/dist/extensions/subagent/sdk-runner.js +210 -0
- package/dist/extensions/subagent/sdk-runner.js.map +1 -0
- package/dist/extensions/subagent/settings.d.ts +9 -0
- package/dist/extensions/subagent/settings.d.ts.map +1 -0
- package/dist/extensions/subagent/settings.js +131 -0
- package/dist/extensions/subagent/settings.js.map +1 -0
- package/dist/extensions/subagent/types.d.ts +99 -0
- package/dist/extensions/subagent/types.d.ts.map +1 -0
- package/dist/extensions/subagent/types.js +2 -0
- package/dist/extensions/subagent/types.js.map +1 -0
- package/dist/modes/interactive/components/extension-editor.d.ts +1 -2
- package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-editor.js +16 -46
- package/dist/modes/interactive/components/extension-editor.js.map +1 -1
- package/dist/modes/interactive/components/index.d.ts +1 -0
- package/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/index.js +1 -0
- package/dist/modes/interactive/components/index.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +2 -0
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +25 -4
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/tool-group.d.ts +16 -0
- package/dist/modes/interactive/components/tool-group.d.ts.map +1 -0
- package/dist/modes/interactive/components/tool-group.js +64 -0
- package/dist/modes/interactive/components/tool-group.js.map +1 -0
- package/dist/modes/interactive/external-editor.d.ts +12 -0
- package/dist/modes/interactive/external-editor.d.ts.map +1 -0
- package/dist/modes/interactive/external-editor.js +37 -0
- package/dist/modes/interactive/external-editor.js.map +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts +3 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +61 -62
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +1 -0
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/rpc-entry.js +0 -0
- package/dist/utils/clipboard.d.ts.map +1 -1
- package/dist/utils/clipboard.js +19 -8
- package/dist/utils/clipboard.js.map +1 -1
- package/dist/utils/version-check.d.ts.map +1 -1
- package/dist/utils/version-check.js +3 -1
- package/dist/utils/version-check.js.map +1 -1
- package/docs/compaction.md +401 -401
- package/docs/containerization.md +111 -111
- package/docs/custom-provider.md +766 -763
- package/docs/environment-variables.md +88 -0
- package/docs/extensions.md +2961 -2953
- package/docs/index.md +80 -77
- package/docs/models.md +6 -2
- package/docs/packages.md +228 -228
- package/docs/providers.md +8 -1
- package/docs/quickstart.md +165 -165
- package/docs/rpc.md +1576 -1559
- package/docs/sdk.md +1186 -1186
- package/docs/session-format.md +436 -436
- package/docs/settings.md +1 -1
- package/docs/termux.md +127 -127
- package/docs/tui.md +942 -942
- package/docs/usage.md +0 -13
- package/examples/extensions/README.md +213 -213
- package/examples/extensions/custom-compaction.ts +3 -0
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/doom-overlay/doom/build/doom.wasm +0 -0
- package/examples/extensions/doom-overlay/doom/build.sh +0 -0
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/handoff.ts +9 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/summarize.ts +3 -0
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/sdk/README.md +140 -140
- package/npm-shrinkwrap.json +16 -15
- package/package.json +10 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../src/extensions/subagent/constants.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,kBAAkB,GAAG,UAAU,CAAC;AAC7C,MAAM,CAAC,MAAM,mBAAmB,GAAG,UAAU,CAAC;AAC9C,MAAM,CAAC,MAAM,qBAAqB,GAAG,QAAQ,CAAC;AAC9C,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC;AACzC,MAAM,CAAC,MAAM,oBAAoB,GAAG,eAAe,CAAC;AAEpD,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,CAAC;AAC3B,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC;AAEjC,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,CAAU,CAAC;AACnG,0EAA0E;AAC1E,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAU,CAAC;AAC9E,oEAAoE;AACpE,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,CAAU,CAAC;AAEtE,MAAM,CAAC,MAAM,eAAe,GAAG;IAC9B,KAAK;IACL,SAAS;IACT,KAAK;IACL,QAAQ;IACR,MAAM;IACN,OAAO;IACP,KAAK;CACuC,CAAC;AAE9C,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,GAAG,IAAI,CAAC;AACxC,MAAM,CAAC,MAAM,cAAc,GAAG,EAAE,CAAC;AACjC,MAAM,CAAC,MAAM,mBAAmB,GAAG,EAAE,GAAG,IAAI,CAAC;AAC7C,MAAM,CAAC,MAAM,mBAAmB,GAAG,EAAE,GAAG,IAAI,CAAC;AAC7C,MAAM,CAAC,MAAM,0BAA0B,GAAG,EAAE,GAAG,IAAI,CAAC;AACpD,MAAM,CAAC,MAAM,qBAAqB,GAAG,EAAE,GAAG,IAAI,CAAC;AAC/C,MAAM,CAAC,MAAM,mBAAmB,GAAG,EAAE,GAAG,IAAI,CAAC;AAC7C,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,GAAG,IAAI,CAAC;AACzC,MAAM,CAAC,MAAM,oBAAoB,GAAG,GAAG,GAAG,IAAI,CAAC;AAC/C,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC","sourcesContent":["import type { ThinkingLevel } from \"@earendil-works/pi-agent-core\";\n\nexport const SUBAGENT_TOOL_NAME = \"subagent\";\nexport const SUBAGENT_TOOL_LABEL = \"Subagent\";\nexport const SUBAGENT_COMMAND_NAME = \"agents\";\nexport const SUBAGENT_CONFIG_VERSION = 1;\nexport const SUBAGENT_CONFIG_FILE = \"subagent.json\";\n\nexport const MAX_TASKS = 8;\nexport const MAX_CONCURRENCY = 3;\n\nexport const BUILTIN_TOOL_NAMES = [\"read\", \"bash\", \"edit\", \"write\", \"grep\", \"find\", \"ls\"] as const;\n// Matches the parent session's default toolset; bash covers search needs.\nexport const DEFAULT_AGENT_TOOLS = [\"read\", \"bash\", \"edit\", \"write\"] as const;\n// Read-only agents get the standalone search tools instead of bash.\nexport const EXPLORER_TOOLS = [\"read\", \"grep\", \"find\", \"ls\"] as const;\n\nexport const THINKING_LEVELS = [\n\t\"off\",\n\t\"minimal\",\n\t\"low\",\n\t\"medium\",\n\t\"high\",\n\t\"xhigh\",\n\t\"max\",\n] as const satisfies readonly ThinkingLevel[];\n\nexport const LIVE_TEXT_LIMIT = 8 * 1024;\nexport const ACTIVITY_LIMIT = 80;\nexport const ACTIVITY_TEXT_LIMIT = 24 * 1024;\nexport const SINGLE_OUTPUT_LIMIT = 32 * 1024;\nexport const PARALLEL_TASK_OUTPUT_LIMIT = 12 * 1024;\nexport const PARALLEL_OUTPUT_LIMIT = 48 * 1024;\nexport const CHAIN_HANDOFF_LIMIT = 16 * 1024;\nexport const ERROR_TEXT_LIMIT = 8 * 1024;\nexport const DETAILS_OUTPUT_LIMIT = 120 * 1024;\nexport const DETAILS_ACTIVITY_LIMIT = 4;\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/extensions/subagent/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAEX,YAAY,EAGZ,MAAM,gCAAgC,CAAC;AA8LxC,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAgGvD","sourcesContent":["import { join } from \"node:path\";\nimport type { ThinkingLevel } from \"@earendil-works/pi-agent-core\";\nimport type { Api, Model } from \"@earendil-works/pi-ai\";\nimport { getSupportedThinkingLevels } from \"@earendil-works/pi-ai/compat\";\nimport { getAgentDir } from \"../../config.ts\";\nimport type {\n\tAgentToolResult,\n\tExtensionAPI,\n\tExtensionCommandContext,\n\tExtensionContext,\n} from \"../../core/extensions/types.ts\";\nimport type { ModelRegistry } from \"../../core/model-registry.ts\";\nimport { ModelRuntime } from \"../../core/model-runtime.ts\";\nimport { discoverAgents, subagentToolDescription } from \"./agents.ts\";\nimport { SUBAGENT_COMMAND_NAME, SUBAGENT_TOOL_LABEL, SUBAGENT_TOOL_NAME, THINKING_LEVELS } from \"./constants.ts\";\nimport { type PickerItem, SearchPickerComponent } from \"./picker.ts\";\nimport { renderSubagentCall, renderSubagentResult } from \"./render.ts\";\nimport type { ParentModelContext } from \"./resolve.ts\";\nimport { ConcurrencyGate, runSubagentInvocation, statusSummary } from \"./runner.ts\";\nimport { SubagentParamsSchema } from \"./schema.ts\";\nimport { loadSubagentConfig, resetProfileOverrides, updateProfileOverride } from \"./settings.ts\";\nimport type { AgentDefinition, AgentDiscoveryResult, SubagentDetails, SubagentProfileOverride } from \"./types.ts\";\n\nfunction formatParentModel(model: { provider: string; id: string } | undefined): string {\n\treturn model ? `${model.provider}/${model.id}` : \"none\";\n}\n\n// Settings resolve in two layers: a saved override wins, otherwise the\n// profile inherits the parent session. Inherited rows show the value\n// currently in effect so the menu doubles as a status view.\nfunction effectiveSettings(\n\toverride: SubagentProfileOverride | undefined,\n\tctx: ExtensionCommandContext,\n\tparentThinking: string,\n): { model: string; thinking: string } {\n\treturn {\n\t\tmodel: override?.model ?? `inherit (${formatParentModel(ctx.model)})`,\n\t\tthinking: override?.thinking ?? `inherit (${parentThinking})`,\n\t};\n}\n\n// The thinking picker lists what the effective model actually supports;\n// if the saved override names a model that is unavailable right now,\n// every level shows and the runtime clamp decides.\nfunction effectiveModelFor(\n\tctx: ExtensionCommandContext,\n\toverride: SubagentProfileOverride | undefined,\n): Model<Api> | undefined {\n\tif (override?.model) {\n\t\tconst separator = override.model.indexOf(\"/\");\n\t\tif (separator <= 0) return undefined;\n\t\treturn ctx.modelRegistry.find(override.model.slice(0, separator), override.model.slice(separator + 1));\n\t}\n\treturn ctx.model;\n}\n\nasync function pickModel(\n\tctx: ExtensionCommandContext,\n\tprofileName: string,\n\toverride: SubagentProfileOverride | undefined,\n): Promise<void> {\n\tconst items: PickerItem<string | null>[] = [\n\t\t{\n\t\t\tvalue: null,\n\t\t\tlabel: \"inherit\",\n\t\t\tdetail: `Follow the parent session (${formatParentModel(ctx.model)})`,\n\t\t\tcurrent: !override?.model,\n\t\t},\n\t\t...ctx.modelRegistry.getAvailable().map((model) => ({\n\t\t\tvalue: `${model.provider}/${model.id}`,\n\t\t\tlabel: `${model.provider}/${model.id}`,\n\t\t\tdetail: model.name && model.name !== model.id ? model.name : undefined,\n\t\t\tcurrent: override?.model === `${model.provider}/${model.id}`,\n\t\t})),\n\t];\n\tconst selected = await ctx.ui.custom<string | null | undefined>(\n\t\t(_tui, theme, _keybindings, done) => new SearchPickerComponent(theme, `Model for ${profileName}`, items, done),\n\t);\n\tif (selected === undefined) return;\n\tawait updateProfileOverride(profileName, { model: selected ?? undefined }, getAgentDir());\n}\n\nasync function pickThinking(\n\tctx: ExtensionCommandContext,\n\tprofileName: string,\n\toverride: SubagentProfileOverride | undefined,\n\tparentThinking: string,\n): Promise<void> {\n\tconst model = effectiveModelFor(ctx, override);\n\tconst levels = model ? (getSupportedThinkingLevels(model) as ThinkingLevel[]) : [...THINKING_LEVELS];\n\tconst items: PickerItem<ThinkingLevel | null>[] = [\n\t\t{\n\t\t\tvalue: null,\n\t\t\tlabel: \"inherit\",\n\t\t\tdetail: `Follow the parent session (${parentThinking})`,\n\t\t\tcurrent: !override?.thinking,\n\t\t},\n\t\t...levels.map((level) => ({ value: level, label: level, current: override?.thinking === level })),\n\t];\n\tconst selected = await ctx.ui.custom<ThinkingLevel | null | undefined>(\n\t\t(_tui, theme, _keybindings, done) => new SearchPickerComponent(theme, `Thinking for ${profileName}`, items, done),\n\t);\n\tif (selected === undefined) return;\n\tawait updateProfileOverride(profileName, { thinking: selected ?? undefined }, getAgentDir());\n}\n\nasync function configureProfile(\n\tctx: ExtensionCommandContext,\n\tagent: AgentDefinition,\n\tparentThinking: string,\n): Promise<void> {\n\tconst agentDir = getAgentDir();\n\twhile (true) {\n\t\tconst config = await loadSubagentConfig(agentDir);\n\t\tconst override = config.profiles[agent.name];\n\t\tconst effective = effectiveSettings(override, ctx, parentThinking);\n\t\tconst action = await ctx.ui.select(`${agent.name} — ${agent.description}`, [\n\t\t\t`Model: ${effective.model}`,\n\t\t\t`Thinking: ${effective.thinking}`,\n\t\t\t\"Back\",\n\t\t]);\n\t\tif (!action || action === \"Back\") return;\n\t\tif (action.startsWith(\"Model:\")) {\n\t\t\tawait pickModel(ctx, agent.name, override);\n\t\t\tcontinue;\n\t\t}\n\t\tawait pickThinking(ctx, agent.name, override, parentThinking);\n\t}\n}\n\nasync function syncParentProviders(\n\truntime: ModelRuntime,\n\tregistry: ModelRegistry,\n\tsyncedIds: Set<string>,\n): Promise<void> {\n\tconst nextIds = new Set(registry.getRegisteredProviderIds());\n\tfor (const id of syncedIds) {\n\t\tif (!nextIds.has(id)) runtime.unregisterProvider(id);\n\t}\n\tfor (const id of nextIds) {\n\t\tconst native = registry.getRegisteredNativeProvider(id);\n\t\tif (native) runtime.registerNativeProvider(native);\n\t\telse {\n\t\t\tconst config = registry.getRegisteredProviderConfig(id);\n\t\t\tif (config) runtime.registerProvider(id, config);\n\t\t}\n\t\tconst auth = await registry.getProviderAuth(id);\n\t\tif (auth?.auth.apiKey) await runtime.setRuntimeApiKey(id, auth.auth.apiKey, { allowNetwork: false });\n\t}\n\tsyncedIds.clear();\n\tfor (const id of nextIds) syncedIds.add(id);\n}\n\nasync function showAgentsCommand(ctx: ExtensionCommandContext, parentThinking: string): Promise<void> {\n\tif (!ctx.hasUI) {\n\t\tctx.ui.notify(\"/agents requires an interactive UI.\", \"warning\");\n\t\treturn;\n\t}\n\tconst agentDir = getAgentDir();\n\tconst reset = \"Reset all overrides\";\n\tconst done = \"Done\";\n\twhile (true) {\n\t\tconst discovery = discoverAgents(ctx.cwd, { projectTrusted: ctx.isProjectTrusted(), agentDir });\n\t\tconst config = await loadSubagentConfig(agentDir);\n\t\tconst rows = discovery.agents.map((agent) => {\n\t\t\tconst effective = effectiveSettings(config.profiles[agent.name], ctx, parentThinking);\n\t\t\treturn `${agent.name} — model: ${effective.model} · thinking: ${effective.thinking}`;\n\t\t});\n\t\tconst issueCount = discovery.diagnostics.length;\n\t\tconst issues = issueCount ? [`Show ${issueCount} agent file issue${issueCount === 1 ? \"\" : \"s\"}`] : [];\n\t\tconst action = await ctx.ui.select(\"Subagent profiles — select one to configure\", [\n\t\t\t...rows,\n\t\t\t...issues,\n\t\t\treset,\n\t\t\tdone,\n\t\t]);\n\t\tif (!action || action === done) return;\n\t\tif (action === reset) {\n\t\t\tconst confirmed = await ctx.ui.confirm(\n\t\t\t\t\"Reset profile overrides?\",\n\t\t\t\t\"Clear every saved Subagent model and thinking override?\",\n\t\t\t);\n\t\t\tif (confirmed) {\n\t\t\t\tawait resetProfileOverrides(agentDir);\n\t\t\t\tctx.ui.notify(\"All profiles now inherit the parent session.\", \"info\");\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tif (issues.length > 0 && action === issues[0]) {\n\t\t\tctx.ui.notify(\n\t\t\t\tdiscovery.diagnostics.map((diagnostic) => `${diagnostic.path}: ${diagnostic.message}`).join(\"\\n\"),\n\t\t\t\t\"warning\",\n\t\t\t);\n\t\t\tcontinue;\n\t\t}\n\t\tconst agent = discovery.agents.find((candidate) => action.startsWith(`${candidate.name} — `));\n\t\tif (agent) await configureProfile(ctx, agent, parentThinking);\n\t}\n}\n\nexport default function subagent(pi: ExtensionAPI): void {\n\tconst gate = new ConcurrencyGate();\n\tconst activeAborters = new Set<() => Promise<void>>();\n\tconst syncedProviderIds = new Set<string>();\n\tlet modelRuntimePromise: Promise<ModelRuntime> | undefined;\n\n\tconst getModelRuntime = async (ctx: ExtensionContext): Promise<ModelRuntime> => {\n\t\tif (!modelRuntimePromise) {\n\t\t\tconst agentDir = getAgentDir();\n\t\t\tmodelRuntimePromise = ModelRuntime.create({\n\t\t\t\tauthPath: join(agentDir, \"auth.json\"),\n\t\t\t\tmodelsPath: join(agentDir, \"models.json\"),\n\t\t\t});\n\t\t}\n\t\tconst runtime = await modelRuntimePromise;\n\t\tawait syncParentProviders(runtime, ctx.modelRegistry, syncedProviderIds);\n\t\treturn runtime;\n\t};\n\n\tconst registerSubagentTool = (discovery: AgentDiscoveryResult): void => {\n\t\tpi.registerTool<typeof SubagentParamsSchema, SubagentDetails>({\n\t\t\tname: SUBAGENT_TOOL_NAME,\n\t\t\tlabel: SUBAGENT_TOOL_LABEL,\n\t\t\tdescription: subagentToolDescription(discovery),\n\t\t\tpromptSnippet: \"Delegate focused research, review, or implementation tasks to isolated subagents\",\n\t\t\tpromptGuidelines: [\n\t\t\t\t\"Use subagent when a bounded task benefits from isolated context, parallel investigation, or a sequential specialist handoff; choose a profile and write its briefing using the tool description.\",\n\t\t\t],\n\t\t\tparameters: SubagentParamsSchema,\n\t\t\tasync execute(_toolCallId, params, signal, onUpdate, ctx): Promise<AgentToolResult<SubagentDetails>> {\n\t\t\t\tconst discovery = discoverAgents(ctx.cwd, {\n\t\t\t\t\tprojectTrusted: ctx.isProjectTrusted(),\n\t\t\t\t\tagentDir: getAgentDir(),\n\t\t\t\t});\n\t\t\t\tconst runtime = await getModelRuntime(ctx);\n\t\t\t\tconst parent: ParentModelContext = {\n\t\t\t\t\tmodel: ctx.model,\n\t\t\t\t\tthinking: pi.getThinkingLevel(),\n\t\t\t\t\tmodelRegistry: ctx.modelRegistry,\n\t\t\t\t};\n\t\t\t\tconst execution = await runSubagentInvocation({\n\t\t\t\t\tparams,\n\t\t\t\t\tparentCwd: ctx.cwd,\n\t\t\t\t\tagents: discovery.agents,\n\t\t\t\t\tparent,\n\t\t\t\t\tmodelRuntime: runtime,\n\t\t\t\t\tagentDir: getAgentDir(),\n\t\t\t\t\tconfigAgentDir: getAgentDir(),\n\t\t\t\t\tprojectTrusted: ctx.isProjectTrusted(),\n\t\t\t\t\tsignal,\n\t\t\t\t\tgate,\n\t\t\t\t\tonUpdate: (details) => {\n\t\t\t\t\t\tonUpdate?.({ content: [{ type: \"text\", text: statusSummary(details) }], details });\n\t\t\t\t\t},\n\t\t\t\t\tregisterAbort: (abort) => {\n\t\t\t\t\t\tactiveAborters.add(abort);\n\t\t\t\t\t\treturn () => activeAborters.delete(abort);\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: execution.content }],\n\t\t\t\t\tdetails: execution.details,\n\t\t\t\t\tusage: execution.usage,\n\t\t\t\t};\n\t\t\t},\n\t\t\trenderCall(args, theme) {\n\t\t\t\treturn renderSubagentCall(args, theme);\n\t\t\t},\n\t\t\trenderResult(result, options, theme, context) {\n\t\t\t\treturn renderSubagentResult(result, options, theme, context.isError);\n\t\t\t},\n\t\t});\n\t};\n\n\tregisterSubagentTool(discoverAgents(process.cwd(), { projectTrusted: false, agentDir: getAgentDir() }));\n\tpi.on(\"session_start\", (_event, ctx) => {\n\t\tregisterSubagentTool(\n\t\t\tdiscoverAgents(ctx.cwd, { projectTrusted: ctx.isProjectTrusted(), agentDir: getAgentDir() }),\n\t\t);\n\t});\n\n\tpi.registerCommand(SUBAGENT_COMMAND_NAME, {\n\t\tdescription: \"Configure Subagent profiles, models, and thinking levels\",\n\t\thandler: async (_args, ctx) => showAgentsCommand(ctx, pi.getThinkingLevel()),\n\t});\n\n\tpi.on(\"tool_result\", async (event) => {\n\t\tif (event.toolName !== SUBAGENT_TOOL_NAME) return;\n\t\tconst details = event.details as SubagentDetails | undefined;\n\t\tif (details?.status === \"failed\" || details?.status === \"aborted\") return { isError: true };\n\t});\n\n\tpi.on(\"session_shutdown\", async () => {\n\t\tawait Promise.allSettled([...activeAborters].map((abort) => abort()));\n\t\tactiveAborters.clear();\n\t});\n}\n"]}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { getSupportedThinkingLevels } from "@earendil-works/pi-ai/compat";
|
|
3
|
+
import { getAgentDir } from "../../config.js";
|
|
4
|
+
import { ModelRuntime } from "../../core/model-runtime.js";
|
|
5
|
+
import { discoverAgents, subagentToolDescription } from "./agents.js";
|
|
6
|
+
import { SUBAGENT_COMMAND_NAME, SUBAGENT_TOOL_LABEL, SUBAGENT_TOOL_NAME, THINKING_LEVELS } from "./constants.js";
|
|
7
|
+
import { SearchPickerComponent } from "./picker.js";
|
|
8
|
+
import { renderSubagentCall, renderSubagentResult } from "./render.js";
|
|
9
|
+
import { ConcurrencyGate, runSubagentInvocation, statusSummary } from "./runner.js";
|
|
10
|
+
import { SubagentParamsSchema } from "./schema.js";
|
|
11
|
+
import { loadSubagentConfig, resetProfileOverrides, updateProfileOverride } from "./settings.js";
|
|
12
|
+
function formatParentModel(model) {
|
|
13
|
+
return model ? `${model.provider}/${model.id}` : "none";
|
|
14
|
+
}
|
|
15
|
+
// Settings resolve in two layers: a saved override wins, otherwise the
|
|
16
|
+
// profile inherits the parent session. Inherited rows show the value
|
|
17
|
+
// currently in effect so the menu doubles as a status view.
|
|
18
|
+
function effectiveSettings(override, ctx, parentThinking) {
|
|
19
|
+
return {
|
|
20
|
+
model: override?.model ?? `inherit (${formatParentModel(ctx.model)})`,
|
|
21
|
+
thinking: override?.thinking ?? `inherit (${parentThinking})`,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
// The thinking picker lists what the effective model actually supports;
|
|
25
|
+
// if the saved override names a model that is unavailable right now,
|
|
26
|
+
// every level shows and the runtime clamp decides.
|
|
27
|
+
function effectiveModelFor(ctx, override) {
|
|
28
|
+
if (override?.model) {
|
|
29
|
+
const separator = override.model.indexOf("/");
|
|
30
|
+
if (separator <= 0)
|
|
31
|
+
return undefined;
|
|
32
|
+
return ctx.modelRegistry.find(override.model.slice(0, separator), override.model.slice(separator + 1));
|
|
33
|
+
}
|
|
34
|
+
return ctx.model;
|
|
35
|
+
}
|
|
36
|
+
async function pickModel(ctx, profileName, override) {
|
|
37
|
+
const items = [
|
|
38
|
+
{
|
|
39
|
+
value: null,
|
|
40
|
+
label: "inherit",
|
|
41
|
+
detail: `Follow the parent session (${formatParentModel(ctx.model)})`,
|
|
42
|
+
current: !override?.model,
|
|
43
|
+
},
|
|
44
|
+
...ctx.modelRegistry.getAvailable().map((model) => ({
|
|
45
|
+
value: `${model.provider}/${model.id}`,
|
|
46
|
+
label: `${model.provider}/${model.id}`,
|
|
47
|
+
detail: model.name && model.name !== model.id ? model.name : undefined,
|
|
48
|
+
current: override?.model === `${model.provider}/${model.id}`,
|
|
49
|
+
})),
|
|
50
|
+
];
|
|
51
|
+
const selected = await ctx.ui.custom((_tui, theme, _keybindings, done) => new SearchPickerComponent(theme, `Model for ${profileName}`, items, done));
|
|
52
|
+
if (selected === undefined)
|
|
53
|
+
return;
|
|
54
|
+
await updateProfileOverride(profileName, { model: selected ?? undefined }, getAgentDir());
|
|
55
|
+
}
|
|
56
|
+
async function pickThinking(ctx, profileName, override, parentThinking) {
|
|
57
|
+
const model = effectiveModelFor(ctx, override);
|
|
58
|
+
const levels = model ? getSupportedThinkingLevels(model) : [...THINKING_LEVELS];
|
|
59
|
+
const items = [
|
|
60
|
+
{
|
|
61
|
+
value: null,
|
|
62
|
+
label: "inherit",
|
|
63
|
+
detail: `Follow the parent session (${parentThinking})`,
|
|
64
|
+
current: !override?.thinking,
|
|
65
|
+
},
|
|
66
|
+
...levels.map((level) => ({ value: level, label: level, current: override?.thinking === level })),
|
|
67
|
+
];
|
|
68
|
+
const selected = await ctx.ui.custom((_tui, theme, _keybindings, done) => new SearchPickerComponent(theme, `Thinking for ${profileName}`, items, done));
|
|
69
|
+
if (selected === undefined)
|
|
70
|
+
return;
|
|
71
|
+
await updateProfileOverride(profileName, { thinking: selected ?? undefined }, getAgentDir());
|
|
72
|
+
}
|
|
73
|
+
async function configureProfile(ctx, agent, parentThinking) {
|
|
74
|
+
const agentDir = getAgentDir();
|
|
75
|
+
while (true) {
|
|
76
|
+
const config = await loadSubagentConfig(agentDir);
|
|
77
|
+
const override = config.profiles[agent.name];
|
|
78
|
+
const effective = effectiveSettings(override, ctx, parentThinking);
|
|
79
|
+
const action = await ctx.ui.select(`${agent.name} — ${agent.description}`, [
|
|
80
|
+
`Model: ${effective.model}`,
|
|
81
|
+
`Thinking: ${effective.thinking}`,
|
|
82
|
+
"Back",
|
|
83
|
+
]);
|
|
84
|
+
if (!action || action === "Back")
|
|
85
|
+
return;
|
|
86
|
+
if (action.startsWith("Model:")) {
|
|
87
|
+
await pickModel(ctx, agent.name, override);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
await pickThinking(ctx, agent.name, override, parentThinking);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async function syncParentProviders(runtime, registry, syncedIds) {
|
|
94
|
+
const nextIds = new Set(registry.getRegisteredProviderIds());
|
|
95
|
+
for (const id of syncedIds) {
|
|
96
|
+
if (!nextIds.has(id))
|
|
97
|
+
runtime.unregisterProvider(id);
|
|
98
|
+
}
|
|
99
|
+
for (const id of nextIds) {
|
|
100
|
+
const native = registry.getRegisteredNativeProvider(id);
|
|
101
|
+
if (native)
|
|
102
|
+
runtime.registerNativeProvider(native);
|
|
103
|
+
else {
|
|
104
|
+
const config = registry.getRegisteredProviderConfig(id);
|
|
105
|
+
if (config)
|
|
106
|
+
runtime.registerProvider(id, config);
|
|
107
|
+
}
|
|
108
|
+
const auth = await registry.getProviderAuth(id);
|
|
109
|
+
if (auth?.auth.apiKey)
|
|
110
|
+
await runtime.setRuntimeApiKey(id, auth.auth.apiKey, { allowNetwork: false });
|
|
111
|
+
}
|
|
112
|
+
syncedIds.clear();
|
|
113
|
+
for (const id of nextIds)
|
|
114
|
+
syncedIds.add(id);
|
|
115
|
+
}
|
|
116
|
+
async function showAgentsCommand(ctx, parentThinking) {
|
|
117
|
+
if (!ctx.hasUI) {
|
|
118
|
+
ctx.ui.notify("/agents requires an interactive UI.", "warning");
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const agentDir = getAgentDir();
|
|
122
|
+
const reset = "Reset all overrides";
|
|
123
|
+
const done = "Done";
|
|
124
|
+
while (true) {
|
|
125
|
+
const discovery = discoverAgents(ctx.cwd, { projectTrusted: ctx.isProjectTrusted(), agentDir });
|
|
126
|
+
const config = await loadSubagentConfig(agentDir);
|
|
127
|
+
const rows = discovery.agents.map((agent) => {
|
|
128
|
+
const effective = effectiveSettings(config.profiles[agent.name], ctx, parentThinking);
|
|
129
|
+
return `${agent.name} — model: ${effective.model} · thinking: ${effective.thinking}`;
|
|
130
|
+
});
|
|
131
|
+
const issueCount = discovery.diagnostics.length;
|
|
132
|
+
const issues = issueCount ? [`Show ${issueCount} agent file issue${issueCount === 1 ? "" : "s"}`] : [];
|
|
133
|
+
const action = await ctx.ui.select("Subagent profiles — select one to configure", [
|
|
134
|
+
...rows,
|
|
135
|
+
...issues,
|
|
136
|
+
reset,
|
|
137
|
+
done,
|
|
138
|
+
]);
|
|
139
|
+
if (!action || action === done)
|
|
140
|
+
return;
|
|
141
|
+
if (action === reset) {
|
|
142
|
+
const confirmed = await ctx.ui.confirm("Reset profile overrides?", "Clear every saved Subagent model and thinking override?");
|
|
143
|
+
if (confirmed) {
|
|
144
|
+
await resetProfileOverrides(agentDir);
|
|
145
|
+
ctx.ui.notify("All profiles now inherit the parent session.", "info");
|
|
146
|
+
}
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (issues.length > 0 && action === issues[0]) {
|
|
150
|
+
ctx.ui.notify(discovery.diagnostics.map((diagnostic) => `${diagnostic.path}: ${diagnostic.message}`).join("\n"), "warning");
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
const agent = discovery.agents.find((candidate) => action.startsWith(`${candidate.name} — `));
|
|
154
|
+
if (agent)
|
|
155
|
+
await configureProfile(ctx, agent, parentThinking);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
export default function subagent(pi) {
|
|
159
|
+
const gate = new ConcurrencyGate();
|
|
160
|
+
const activeAborters = new Set();
|
|
161
|
+
const syncedProviderIds = new Set();
|
|
162
|
+
let modelRuntimePromise;
|
|
163
|
+
const getModelRuntime = async (ctx) => {
|
|
164
|
+
if (!modelRuntimePromise) {
|
|
165
|
+
const agentDir = getAgentDir();
|
|
166
|
+
modelRuntimePromise = ModelRuntime.create({
|
|
167
|
+
authPath: join(agentDir, "auth.json"),
|
|
168
|
+
modelsPath: join(agentDir, "models.json"),
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
const runtime = await modelRuntimePromise;
|
|
172
|
+
await syncParentProviders(runtime, ctx.modelRegistry, syncedProviderIds);
|
|
173
|
+
return runtime;
|
|
174
|
+
};
|
|
175
|
+
const registerSubagentTool = (discovery) => {
|
|
176
|
+
pi.registerTool({
|
|
177
|
+
name: SUBAGENT_TOOL_NAME,
|
|
178
|
+
label: SUBAGENT_TOOL_LABEL,
|
|
179
|
+
description: subagentToolDescription(discovery),
|
|
180
|
+
promptSnippet: "Delegate focused research, review, or implementation tasks to isolated subagents",
|
|
181
|
+
promptGuidelines: [
|
|
182
|
+
"Use subagent when a bounded task benefits from isolated context, parallel investigation, or a sequential specialist handoff; choose a profile and write its briefing using the tool description.",
|
|
183
|
+
],
|
|
184
|
+
parameters: SubagentParamsSchema,
|
|
185
|
+
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
186
|
+
const discovery = discoverAgents(ctx.cwd, {
|
|
187
|
+
projectTrusted: ctx.isProjectTrusted(),
|
|
188
|
+
agentDir: getAgentDir(),
|
|
189
|
+
});
|
|
190
|
+
const runtime = await getModelRuntime(ctx);
|
|
191
|
+
const parent = {
|
|
192
|
+
model: ctx.model,
|
|
193
|
+
thinking: pi.getThinkingLevel(),
|
|
194
|
+
modelRegistry: ctx.modelRegistry,
|
|
195
|
+
};
|
|
196
|
+
const execution = await runSubagentInvocation({
|
|
197
|
+
params,
|
|
198
|
+
parentCwd: ctx.cwd,
|
|
199
|
+
agents: discovery.agents,
|
|
200
|
+
parent,
|
|
201
|
+
modelRuntime: runtime,
|
|
202
|
+
agentDir: getAgentDir(),
|
|
203
|
+
configAgentDir: getAgentDir(),
|
|
204
|
+
projectTrusted: ctx.isProjectTrusted(),
|
|
205
|
+
signal,
|
|
206
|
+
gate,
|
|
207
|
+
onUpdate: (details) => {
|
|
208
|
+
onUpdate?.({ content: [{ type: "text", text: statusSummary(details) }], details });
|
|
209
|
+
},
|
|
210
|
+
registerAbort: (abort) => {
|
|
211
|
+
activeAborters.add(abort);
|
|
212
|
+
return () => activeAborters.delete(abort);
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
return {
|
|
216
|
+
content: [{ type: "text", text: execution.content }],
|
|
217
|
+
details: execution.details,
|
|
218
|
+
usage: execution.usage,
|
|
219
|
+
};
|
|
220
|
+
},
|
|
221
|
+
renderCall(args, theme) {
|
|
222
|
+
return renderSubagentCall(args, theme);
|
|
223
|
+
},
|
|
224
|
+
renderResult(result, options, theme, context) {
|
|
225
|
+
return renderSubagentResult(result, options, theme, context.isError);
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
};
|
|
229
|
+
registerSubagentTool(discoverAgents(process.cwd(), { projectTrusted: false, agentDir: getAgentDir() }));
|
|
230
|
+
pi.on("session_start", (_event, ctx) => {
|
|
231
|
+
registerSubagentTool(discoverAgents(ctx.cwd, { projectTrusted: ctx.isProjectTrusted(), agentDir: getAgentDir() }));
|
|
232
|
+
});
|
|
233
|
+
pi.registerCommand(SUBAGENT_COMMAND_NAME, {
|
|
234
|
+
description: "Configure Subagent profiles, models, and thinking levels",
|
|
235
|
+
handler: async (_args, ctx) => showAgentsCommand(ctx, pi.getThinkingLevel()),
|
|
236
|
+
});
|
|
237
|
+
pi.on("tool_result", async (event) => {
|
|
238
|
+
if (event.toolName !== SUBAGENT_TOOL_NAME)
|
|
239
|
+
return;
|
|
240
|
+
const details = event.details;
|
|
241
|
+
if (details?.status === "failed" || details?.status === "aborted")
|
|
242
|
+
return { isError: true };
|
|
243
|
+
});
|
|
244
|
+
pi.on("session_shutdown", async () => {
|
|
245
|
+
await Promise.allSettled([...activeAborters].map((abort) => abort()));
|
|
246
|
+
activeAborters.clear();
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/extensions/subagent/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { join } from \"node:path\";\nimport type { ThinkingLevel } from \"@earendil-works/pi-agent-core\";\nimport type { Api, Model } from \"@earendil-works/pi-ai\";\nimport { getSupportedThinkingLevels } from \"@earendil-works/pi-ai/compat\";\nimport { getAgentDir } from \"../../config.ts\";\nimport type {\n\tAgentToolResult,\n\tExtensionAPI,\n\tExtensionCommandContext,\n\tExtensionContext,\n} from \"../../core/extensions/types.ts\";\nimport type { ModelRegistry } from \"../../core/model-registry.ts\";\nimport { ModelRuntime } from \"../../core/model-runtime.ts\";\nimport { discoverAgents, subagentToolDescription } from \"./agents.ts\";\nimport { SUBAGENT_COMMAND_NAME, SUBAGENT_TOOL_LABEL, SUBAGENT_TOOL_NAME, THINKING_LEVELS } from \"./constants.ts\";\nimport { type PickerItem, SearchPickerComponent } from \"./picker.ts\";\nimport { renderSubagentCall, renderSubagentResult } from \"./render.ts\";\nimport type { ParentModelContext } from \"./resolve.ts\";\nimport { ConcurrencyGate, runSubagentInvocation, statusSummary } from \"./runner.ts\";\nimport { SubagentParamsSchema } from \"./schema.ts\";\nimport { loadSubagentConfig, resetProfileOverrides, updateProfileOverride } from \"./settings.ts\";\nimport type { AgentDefinition, AgentDiscoveryResult, SubagentDetails, SubagentProfileOverride } from \"./types.ts\";\n\nfunction formatParentModel(model: { provider: string; id: string } | undefined): string {\n\treturn model ? `${model.provider}/${model.id}` : \"none\";\n}\n\n// Settings resolve in two layers: a saved override wins, otherwise the\n// profile inherits the parent session. Inherited rows show the value\n// currently in effect so the menu doubles as a status view.\nfunction effectiveSettings(\n\toverride: SubagentProfileOverride | undefined,\n\tctx: ExtensionCommandContext,\n\tparentThinking: string,\n): { model: string; thinking: string } {\n\treturn {\n\t\tmodel: override?.model ?? `inherit (${formatParentModel(ctx.model)})`,\n\t\tthinking: override?.thinking ?? `inherit (${parentThinking})`,\n\t};\n}\n\n// The thinking picker lists what the effective model actually supports;\n// if the saved override names a model that is unavailable right now,\n// every level shows and the runtime clamp decides.\nfunction effectiveModelFor(\n\tctx: ExtensionCommandContext,\n\toverride: SubagentProfileOverride | undefined,\n): Model<Api> | undefined {\n\tif (override?.model) {\n\t\tconst separator = override.model.indexOf(\"/\");\n\t\tif (separator <= 0) return undefined;\n\t\treturn ctx.modelRegistry.find(override.model.slice(0, separator), override.model.slice(separator + 1));\n\t}\n\treturn ctx.model;\n}\n\nasync function pickModel(\n\tctx: ExtensionCommandContext,\n\tprofileName: string,\n\toverride: SubagentProfileOverride | undefined,\n): Promise<void> {\n\tconst items: PickerItem<string | null>[] = [\n\t\t{\n\t\t\tvalue: null,\n\t\t\tlabel: \"inherit\",\n\t\t\tdetail: `Follow the parent session (${formatParentModel(ctx.model)})`,\n\t\t\tcurrent: !override?.model,\n\t\t},\n\t\t...ctx.modelRegistry.getAvailable().map((model) => ({\n\t\t\tvalue: `${model.provider}/${model.id}`,\n\t\t\tlabel: `${model.provider}/${model.id}`,\n\t\t\tdetail: model.name && model.name !== model.id ? model.name : undefined,\n\t\t\tcurrent: override?.model === `${model.provider}/${model.id}`,\n\t\t})),\n\t];\n\tconst selected = await ctx.ui.custom<string | null | undefined>(\n\t\t(_tui, theme, _keybindings, done) => new SearchPickerComponent(theme, `Model for ${profileName}`, items, done),\n\t);\n\tif (selected === undefined) return;\n\tawait updateProfileOverride(profileName, { model: selected ?? undefined }, getAgentDir());\n}\n\nasync function pickThinking(\n\tctx: ExtensionCommandContext,\n\tprofileName: string,\n\toverride: SubagentProfileOverride | undefined,\n\tparentThinking: string,\n): Promise<void> {\n\tconst model = effectiveModelFor(ctx, override);\n\tconst levels = model ? (getSupportedThinkingLevels(model) as ThinkingLevel[]) : [...THINKING_LEVELS];\n\tconst items: PickerItem<ThinkingLevel | null>[] = [\n\t\t{\n\t\t\tvalue: null,\n\t\t\tlabel: \"inherit\",\n\t\t\tdetail: `Follow the parent session (${parentThinking})`,\n\t\t\tcurrent: !override?.thinking,\n\t\t},\n\t\t...levels.map((level) => ({ value: level, label: level, current: override?.thinking === level })),\n\t];\n\tconst selected = await ctx.ui.custom<ThinkingLevel | null | undefined>(\n\t\t(_tui, theme, _keybindings, done) => new SearchPickerComponent(theme, `Thinking for ${profileName}`, items, done),\n\t);\n\tif (selected === undefined) return;\n\tawait updateProfileOverride(profileName, { thinking: selected ?? undefined }, getAgentDir());\n}\n\nasync function configureProfile(\n\tctx: ExtensionCommandContext,\n\tagent: AgentDefinition,\n\tparentThinking: string,\n): Promise<void> {\n\tconst agentDir = getAgentDir();\n\twhile (true) {\n\t\tconst config = await loadSubagentConfig(agentDir);\n\t\tconst override = config.profiles[agent.name];\n\t\tconst effective = effectiveSettings(override, ctx, parentThinking);\n\t\tconst action = await ctx.ui.select(`${agent.name} — ${agent.description}`, [\n\t\t\t`Model: ${effective.model}`,\n\t\t\t`Thinking: ${effective.thinking}`,\n\t\t\t\"Back\",\n\t\t]);\n\t\tif (!action || action === \"Back\") return;\n\t\tif (action.startsWith(\"Model:\")) {\n\t\t\tawait pickModel(ctx, agent.name, override);\n\t\t\tcontinue;\n\t\t}\n\t\tawait pickThinking(ctx, agent.name, override, parentThinking);\n\t}\n}\n\nasync function syncParentProviders(\n\truntime: ModelRuntime,\n\tregistry: ModelRegistry,\n\tsyncedIds: Set<string>,\n): Promise<void> {\n\tconst nextIds = new Set(registry.getRegisteredProviderIds());\n\tfor (const id of syncedIds) {\n\t\tif (!nextIds.has(id)) runtime.unregisterProvider(id);\n\t}\n\tfor (const id of nextIds) {\n\t\tconst native = registry.getRegisteredNativeProvider(id);\n\t\tif (native) runtime.registerNativeProvider(native);\n\t\telse {\n\t\t\tconst config = registry.getRegisteredProviderConfig(id);\n\t\t\tif (config) runtime.registerProvider(id, config);\n\t\t}\n\t\tconst auth = await registry.getProviderAuth(id);\n\t\tif (auth?.auth.apiKey) await runtime.setRuntimeApiKey(id, auth.auth.apiKey, { allowNetwork: false });\n\t}\n\tsyncedIds.clear();\n\tfor (const id of nextIds) syncedIds.add(id);\n}\n\nasync function showAgentsCommand(ctx: ExtensionCommandContext, parentThinking: string): Promise<void> {\n\tif (!ctx.hasUI) {\n\t\tctx.ui.notify(\"/agents requires an interactive UI.\", \"warning\");\n\t\treturn;\n\t}\n\tconst agentDir = getAgentDir();\n\tconst reset = \"Reset all overrides\";\n\tconst done = \"Done\";\n\twhile (true) {\n\t\tconst discovery = discoverAgents(ctx.cwd, { projectTrusted: ctx.isProjectTrusted(), agentDir });\n\t\tconst config = await loadSubagentConfig(agentDir);\n\t\tconst rows = discovery.agents.map((agent) => {\n\t\t\tconst effective = effectiveSettings(config.profiles[agent.name], ctx, parentThinking);\n\t\t\treturn `${agent.name} — model: ${effective.model} · thinking: ${effective.thinking}`;\n\t\t});\n\t\tconst issueCount = discovery.diagnostics.length;\n\t\tconst issues = issueCount ? [`Show ${issueCount} agent file issue${issueCount === 1 ? \"\" : \"s\"}`] : [];\n\t\tconst action = await ctx.ui.select(\"Subagent profiles — select one to configure\", [\n\t\t\t...rows,\n\t\t\t...issues,\n\t\t\treset,\n\t\t\tdone,\n\t\t]);\n\t\tif (!action || action === done) return;\n\t\tif (action === reset) {\n\t\t\tconst confirmed = await ctx.ui.confirm(\n\t\t\t\t\"Reset profile overrides?\",\n\t\t\t\t\"Clear every saved Subagent model and thinking override?\",\n\t\t\t);\n\t\t\tif (confirmed) {\n\t\t\t\tawait resetProfileOverrides(agentDir);\n\t\t\t\tctx.ui.notify(\"All profiles now inherit the parent session.\", \"info\");\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tif (issues.length > 0 && action === issues[0]) {\n\t\t\tctx.ui.notify(\n\t\t\t\tdiscovery.diagnostics.map((diagnostic) => `${diagnostic.path}: ${diagnostic.message}`).join(\"\\n\"),\n\t\t\t\t\"warning\",\n\t\t\t);\n\t\t\tcontinue;\n\t\t}\n\t\tconst agent = discovery.agents.find((candidate) => action.startsWith(`${candidate.name} — `));\n\t\tif (agent) await configureProfile(ctx, agent, parentThinking);\n\t}\n}\n\nexport default function subagent(pi: ExtensionAPI): void {\n\tconst gate = new ConcurrencyGate();\n\tconst activeAborters = new Set<() => Promise<void>>();\n\tconst syncedProviderIds = new Set<string>();\n\tlet modelRuntimePromise: Promise<ModelRuntime> | undefined;\n\n\tconst getModelRuntime = async (ctx: ExtensionContext): Promise<ModelRuntime> => {\n\t\tif (!modelRuntimePromise) {\n\t\t\tconst agentDir = getAgentDir();\n\t\t\tmodelRuntimePromise = ModelRuntime.create({\n\t\t\t\tauthPath: join(agentDir, \"auth.json\"),\n\t\t\t\tmodelsPath: join(agentDir, \"models.json\"),\n\t\t\t});\n\t\t}\n\t\tconst runtime = await modelRuntimePromise;\n\t\tawait syncParentProviders(runtime, ctx.modelRegistry, syncedProviderIds);\n\t\treturn runtime;\n\t};\n\n\tconst registerSubagentTool = (discovery: AgentDiscoveryResult): void => {\n\t\tpi.registerTool<typeof SubagentParamsSchema, SubagentDetails>({\n\t\t\tname: SUBAGENT_TOOL_NAME,\n\t\t\tlabel: SUBAGENT_TOOL_LABEL,\n\t\t\tdescription: subagentToolDescription(discovery),\n\t\t\tpromptSnippet: \"Delegate focused research, review, or implementation tasks to isolated subagents\",\n\t\t\tpromptGuidelines: [\n\t\t\t\t\"Use subagent when a bounded task benefits from isolated context, parallel investigation, or a sequential specialist handoff; choose a profile and write its briefing using the tool description.\",\n\t\t\t],\n\t\t\tparameters: SubagentParamsSchema,\n\t\t\tasync execute(_toolCallId, params, signal, onUpdate, ctx): Promise<AgentToolResult<SubagentDetails>> {\n\t\t\t\tconst discovery = discoverAgents(ctx.cwd, {\n\t\t\t\t\tprojectTrusted: ctx.isProjectTrusted(),\n\t\t\t\t\tagentDir: getAgentDir(),\n\t\t\t\t});\n\t\t\t\tconst runtime = await getModelRuntime(ctx);\n\t\t\t\tconst parent: ParentModelContext = {\n\t\t\t\t\tmodel: ctx.model,\n\t\t\t\t\tthinking: pi.getThinkingLevel(),\n\t\t\t\t\tmodelRegistry: ctx.modelRegistry,\n\t\t\t\t};\n\t\t\t\tconst execution = await runSubagentInvocation({\n\t\t\t\t\tparams,\n\t\t\t\t\tparentCwd: ctx.cwd,\n\t\t\t\t\tagents: discovery.agents,\n\t\t\t\t\tparent,\n\t\t\t\t\tmodelRuntime: runtime,\n\t\t\t\t\tagentDir: getAgentDir(),\n\t\t\t\t\tconfigAgentDir: getAgentDir(),\n\t\t\t\t\tprojectTrusted: ctx.isProjectTrusted(),\n\t\t\t\t\tsignal,\n\t\t\t\t\tgate,\n\t\t\t\t\tonUpdate: (details) => {\n\t\t\t\t\t\tonUpdate?.({ content: [{ type: \"text\", text: statusSummary(details) }], details });\n\t\t\t\t\t},\n\t\t\t\t\tregisterAbort: (abort) => {\n\t\t\t\t\t\tactiveAborters.add(abort);\n\t\t\t\t\t\treturn () => activeAborters.delete(abort);\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: execution.content }],\n\t\t\t\t\tdetails: execution.details,\n\t\t\t\t\tusage: execution.usage,\n\t\t\t\t};\n\t\t\t},\n\t\t\trenderCall(args, theme) {\n\t\t\t\treturn renderSubagentCall(args, theme);\n\t\t\t},\n\t\t\trenderResult(result, options, theme, context) {\n\t\t\t\treturn renderSubagentResult(result, options, theme, context.isError);\n\t\t\t},\n\t\t});\n\t};\n\n\tregisterSubagentTool(discoverAgents(process.cwd(), { projectTrusted: false, agentDir: getAgentDir() }));\n\tpi.on(\"session_start\", (_event, ctx) => {\n\t\tregisterSubagentTool(\n\t\t\tdiscoverAgents(ctx.cwd, { projectTrusted: ctx.isProjectTrusted(), agentDir: getAgentDir() }),\n\t\t);\n\t});\n\n\tpi.registerCommand(SUBAGENT_COMMAND_NAME, {\n\t\tdescription: \"Configure Subagent profiles, models, and thinking levels\",\n\t\thandler: async (_args, ctx) => showAgentsCommand(ctx, pi.getThinkingLevel()),\n\t});\n\n\tpi.on(\"tool_result\", async (event) => {\n\t\tif (event.toolName !== SUBAGENT_TOOL_NAME) return;\n\t\tconst details = event.details as SubagentDetails | undefined;\n\t\tif (details?.status === \"failed\" || details?.status === \"aborted\") return { isError: true };\n\t});\n\n\tpi.on(\"session_shutdown\", async () => {\n\t\tawait Promise.allSettled([...activeAborters].map((abort) => abort()));\n\t\tactiveAborters.clear();\n\t});\n}\n"]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Container, type Focusable } from "@earendil-works/pi-tui";
|
|
2
|
+
import type { Theme } from "../../modes/interactive/theme/theme.ts";
|
|
3
|
+
export interface PickerItem<T> {
|
|
4
|
+
value: T;
|
|
5
|
+
label: string;
|
|
6
|
+
detail?: string;
|
|
7
|
+
current?: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Searchable selector matching the native /model experience: type to
|
|
11
|
+
* fuzzy-filter, arrows wrap around the list, enter confirms, escape
|
|
12
|
+
* cancels (resolves undefined). Mounted via ctx.ui.custom().
|
|
13
|
+
*/
|
|
14
|
+
export declare class SearchPickerComponent<T> extends Container implements Focusable {
|
|
15
|
+
private readonly theme;
|
|
16
|
+
private readonly items;
|
|
17
|
+
private readonly onDone;
|
|
18
|
+
private readonly searchInput;
|
|
19
|
+
private readonly listContainer;
|
|
20
|
+
private filtered;
|
|
21
|
+
private selectedIndex;
|
|
22
|
+
private _focused;
|
|
23
|
+
get focused(): boolean;
|
|
24
|
+
set focused(value: boolean);
|
|
25
|
+
constructor(theme: Theme, title: string, items: PickerItem<T>[], onDone: (value: T | undefined) => void);
|
|
26
|
+
private filter;
|
|
27
|
+
private confirm;
|
|
28
|
+
private updateList;
|
|
29
|
+
handleInput(keyData: string): void;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=picker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"picker.d.ts","sourceRoot":"","sources":["../../../src/extensions/subagent/picker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,SAAS,EAAoD,MAAM,wBAAwB,CAAC;AAGrH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,wCAAwC,CAAC;AAEpE,MAAM,WAAW,UAAU,CAAC,CAAC;IAC5B,KAAK,EAAE,CAAC,CAAC;IACT,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;CAClB;AAID;;;;GAIG;AACH,qBAAa,qBAAqB,CAAC,CAAC,CAAE,SAAQ,SAAU,YAAW,SAAS;IAC3E,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkB;IACxC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiC;IACxD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAQ;IACpC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAY;IAC1C,OAAO,CAAC,QAAQ,CAAkB;IAClC,OAAO,CAAC,aAAa,CAAK;IAE1B,OAAO,CAAC,QAAQ,CAAS;IACzB,IAAI,OAAO,IAAI,OAAO,CAErB;IACD,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAGzB;IAED,YAAY,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,SAAS,KAAK,IAAI,EA6BtG;IAED,OAAO,CAAC,MAAM;IAQd,OAAO,CAAC,OAAO;IAKf,OAAO,CAAC,UAAU;IAiClB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAkBjC;CACD","sourcesContent":["import { Container, type Focusable, fuzzyFilter, getKeybindings, Input, Spacer, Text } from \"@earendil-works/pi-tui\";\nimport { DynamicBorder } from \"../../modes/interactive/components/dynamic-border.ts\";\nimport { keyHint } from \"../../modes/interactive/components/keybinding-hints.ts\";\nimport type { Theme } from \"../../modes/interactive/theme/theme.ts\";\n\nexport interface PickerItem<T> {\n\tvalue: T;\n\tlabel: string;\n\tdetail?: string;\n\tcurrent?: boolean;\n}\n\nconst MAX_VISIBLE = 10;\n\n/**\n * Searchable selector matching the native /model experience: type to\n * fuzzy-filter, arrows wrap around the list, enter confirms, escape\n * cancels (resolves undefined). Mounted via ctx.ui.custom().\n */\nexport class SearchPickerComponent<T> extends Container implements Focusable {\n\tprivate readonly theme: Theme;\n\tprivate readonly items: PickerItem<T>[];\n\tprivate readonly onDone: (value: T | undefined) => void;\n\tprivate readonly searchInput: Input;\n\tprivate readonly listContainer: Container;\n\tprivate filtered: PickerItem<T>[];\n\tprivate selectedIndex = 0;\n\n\tprivate _focused = false;\n\tget focused(): boolean {\n\t\treturn this._focused;\n\t}\n\tset focused(value: boolean) {\n\t\tthis._focused = value;\n\t\tthis.searchInput.focused = value;\n\t}\n\n\tconstructor(theme: Theme, title: string, items: PickerItem<T>[], onDone: (value: T | undefined) => void) {\n\t\tsuper();\n\t\tthis.theme = theme;\n\t\tthis.items = items;\n\t\tthis.onDone = onDone;\n\t\tthis.filtered = items;\n\t\tconst currentIndex = items.findIndex((item) => item.current);\n\t\tif (currentIndex >= 0) this.selectedIndex = currentIndex;\n\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new Text(theme.fg(\"toolTitle\", theme.bold(title)), 0, 0));\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.searchInput = new Input();\n\t\tthis.searchInput.onSubmit = () => this.confirm();\n\t\tthis.addChild(this.searchInput);\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.listContainer = new Container();\n\t\tthis.addChild(this.listContainer);\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(\n\t\t\tnew Text(\n\t\t\t\t`${theme.fg(\"muted\", \"↑↓\")} ${theme.fg(\"dim\", \"navigate\")} ${keyHint(\"tui.select.confirm\", \"select\")} ${keyHint(\"tui.select.cancel\", \"cancel\")}`,\n\t\t\t\t0,\n\t\t\t\t0,\n\t\t\t),\n\t\t);\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.updateList();\n\t}\n\n\tprivate filter(query: string): void {\n\t\tthis.filtered = query\n\t\t\t? fuzzyFilter(this.items, query, (item) => `${item.label} ${item.detail ?? \"\"}`)\n\t\t\t: this.items;\n\t\tthis.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.filtered.length - 1));\n\t\tthis.updateList();\n\t}\n\n\tprivate confirm(): void {\n\t\tconst selected = this.filtered[this.selectedIndex];\n\t\tif (selected) this.onDone(selected.value);\n\t}\n\n\tprivate updateList(): void {\n\t\tthis.listContainer.clear();\n\t\tconst startIndex = Math.max(\n\t\t\t0,\n\t\t\tMath.min(this.selectedIndex - Math.floor(MAX_VISIBLE / 2), this.filtered.length - MAX_VISIBLE),\n\t\t);\n\t\tconst endIndex = Math.min(startIndex + MAX_VISIBLE, this.filtered.length);\n\t\tfor (let index = startIndex; index < endIndex; index++) {\n\t\t\tconst item = this.filtered[index];\n\t\t\tif (!item) continue;\n\t\t\tconst isSelected = index === this.selectedIndex;\n\t\t\tconst marker = item.current ? this.theme.fg(\"success\", \" ✓\") : \"\";\n\t\t\tconst line = isSelected\n\t\t\t\t? `${this.theme.fg(\"accent\", \"→ \")}${this.theme.fg(\"accent\", item.label)}${marker}`\n\t\t\t\t: ` ${item.label}${marker}`;\n\t\t\tthis.listContainer.addChild(new Text(line, 0, 0));\n\t\t}\n\t\tif (startIndex > 0 || endIndex < this.filtered.length) {\n\t\t\tthis.listContainer.addChild(\n\t\t\t\tnew Text(this.theme.fg(\"muted\", ` (${this.selectedIndex + 1}/${this.filtered.length})`), 0, 0),\n\t\t\t);\n\t\t}\n\t\tif (this.filtered.length === 0) {\n\t\t\tthis.listContainer.addChild(new Text(this.theme.fg(\"muted\", \" No matches\"), 0, 0));\n\t\t\treturn;\n\t\t}\n\t\tconst detail = this.filtered[this.selectedIndex]?.detail;\n\t\tif (detail) {\n\t\t\tthis.listContainer.addChild(new Spacer(1));\n\t\t\tthis.listContainer.addChild(new Text(this.theme.fg(\"muted\", ` ${detail}`), 0, 0));\n\t\t}\n\t}\n\n\thandleInput(keyData: string): void {\n\t\tconst kb = getKeybindings();\n\t\tif (kb.matches(keyData, \"tui.select.up\")) {\n\t\t\tif (this.filtered.length === 0) return;\n\t\t\tthis.selectedIndex = this.selectedIndex === 0 ? this.filtered.length - 1 : this.selectedIndex - 1;\n\t\t\tthis.updateList();\n\t\t} else if (kb.matches(keyData, \"tui.select.down\")) {\n\t\t\tif (this.filtered.length === 0) return;\n\t\t\tthis.selectedIndex = this.selectedIndex === this.filtered.length - 1 ? 0 : this.selectedIndex + 1;\n\t\t\tthis.updateList();\n\t\t} else if (kb.matches(keyData, \"tui.select.confirm\")) {\n\t\t\tthis.confirm();\n\t\t} else if (kb.matches(keyData, \"tui.select.cancel\")) {\n\t\t\tthis.onDone(undefined);\n\t\t} else {\n\t\t\tthis.searchInput.handleInput(keyData);\n\t\t\tthis.filter(this.searchInput.getValue());\n\t\t}\n\t}\n}\n"]}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { Container, fuzzyFilter, getKeybindings, Input, Spacer, Text } from "@earendil-works/pi-tui";
|
|
2
|
+
import { DynamicBorder } from "../../modes/interactive/components/dynamic-border.js";
|
|
3
|
+
import { keyHint } from "../../modes/interactive/components/keybinding-hints.js";
|
|
4
|
+
const MAX_VISIBLE = 10;
|
|
5
|
+
/**
|
|
6
|
+
* Searchable selector matching the native /model experience: type to
|
|
7
|
+
* fuzzy-filter, arrows wrap around the list, enter confirms, escape
|
|
8
|
+
* cancels (resolves undefined). Mounted via ctx.ui.custom().
|
|
9
|
+
*/
|
|
10
|
+
export class SearchPickerComponent extends Container {
|
|
11
|
+
theme;
|
|
12
|
+
items;
|
|
13
|
+
onDone;
|
|
14
|
+
searchInput;
|
|
15
|
+
listContainer;
|
|
16
|
+
filtered;
|
|
17
|
+
selectedIndex = 0;
|
|
18
|
+
_focused = false;
|
|
19
|
+
get focused() {
|
|
20
|
+
return this._focused;
|
|
21
|
+
}
|
|
22
|
+
set focused(value) {
|
|
23
|
+
this._focused = value;
|
|
24
|
+
this.searchInput.focused = value;
|
|
25
|
+
}
|
|
26
|
+
constructor(theme, title, items, onDone) {
|
|
27
|
+
super();
|
|
28
|
+
this.theme = theme;
|
|
29
|
+
this.items = items;
|
|
30
|
+
this.onDone = onDone;
|
|
31
|
+
this.filtered = items;
|
|
32
|
+
const currentIndex = items.findIndex((item) => item.current);
|
|
33
|
+
if (currentIndex >= 0)
|
|
34
|
+
this.selectedIndex = currentIndex;
|
|
35
|
+
this.addChild(new DynamicBorder());
|
|
36
|
+
this.addChild(new Spacer(1));
|
|
37
|
+
this.addChild(new Text(theme.fg("toolTitle", theme.bold(title)), 0, 0));
|
|
38
|
+
this.addChild(new Spacer(1));
|
|
39
|
+
this.searchInput = new Input();
|
|
40
|
+
this.searchInput.onSubmit = () => this.confirm();
|
|
41
|
+
this.addChild(this.searchInput);
|
|
42
|
+
this.addChild(new Spacer(1));
|
|
43
|
+
this.listContainer = new Container();
|
|
44
|
+
this.addChild(this.listContainer);
|
|
45
|
+
this.addChild(new Spacer(1));
|
|
46
|
+
this.addChild(new Text(`${theme.fg("muted", "↑↓")} ${theme.fg("dim", "navigate")} ${keyHint("tui.select.confirm", "select")} ${keyHint("tui.select.cancel", "cancel")}`, 0, 0));
|
|
47
|
+
this.addChild(new DynamicBorder());
|
|
48
|
+
this.updateList();
|
|
49
|
+
}
|
|
50
|
+
filter(query) {
|
|
51
|
+
this.filtered = query
|
|
52
|
+
? fuzzyFilter(this.items, query, (item) => `${item.label} ${item.detail ?? ""}`)
|
|
53
|
+
: this.items;
|
|
54
|
+
this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.filtered.length - 1));
|
|
55
|
+
this.updateList();
|
|
56
|
+
}
|
|
57
|
+
confirm() {
|
|
58
|
+
const selected = this.filtered[this.selectedIndex];
|
|
59
|
+
if (selected)
|
|
60
|
+
this.onDone(selected.value);
|
|
61
|
+
}
|
|
62
|
+
updateList() {
|
|
63
|
+
this.listContainer.clear();
|
|
64
|
+
const startIndex = Math.max(0, Math.min(this.selectedIndex - Math.floor(MAX_VISIBLE / 2), this.filtered.length - MAX_VISIBLE));
|
|
65
|
+
const endIndex = Math.min(startIndex + MAX_VISIBLE, this.filtered.length);
|
|
66
|
+
for (let index = startIndex; index < endIndex; index++) {
|
|
67
|
+
const item = this.filtered[index];
|
|
68
|
+
if (!item)
|
|
69
|
+
continue;
|
|
70
|
+
const isSelected = index === this.selectedIndex;
|
|
71
|
+
const marker = item.current ? this.theme.fg("success", " ✓") : "";
|
|
72
|
+
const line = isSelected
|
|
73
|
+
? `${this.theme.fg("accent", "→ ")}${this.theme.fg("accent", item.label)}${marker}`
|
|
74
|
+
: ` ${item.label}${marker}`;
|
|
75
|
+
this.listContainer.addChild(new Text(line, 0, 0));
|
|
76
|
+
}
|
|
77
|
+
if (startIndex > 0 || endIndex < this.filtered.length) {
|
|
78
|
+
this.listContainer.addChild(new Text(this.theme.fg("muted", ` (${this.selectedIndex + 1}/${this.filtered.length})`), 0, 0));
|
|
79
|
+
}
|
|
80
|
+
if (this.filtered.length === 0) {
|
|
81
|
+
this.listContainer.addChild(new Text(this.theme.fg("muted", " No matches"), 0, 0));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const detail = this.filtered[this.selectedIndex]?.detail;
|
|
85
|
+
if (detail) {
|
|
86
|
+
this.listContainer.addChild(new Spacer(1));
|
|
87
|
+
this.listContainer.addChild(new Text(this.theme.fg("muted", ` ${detail}`), 0, 0));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
handleInput(keyData) {
|
|
91
|
+
const kb = getKeybindings();
|
|
92
|
+
if (kb.matches(keyData, "tui.select.up")) {
|
|
93
|
+
if (this.filtered.length === 0)
|
|
94
|
+
return;
|
|
95
|
+
this.selectedIndex = this.selectedIndex === 0 ? this.filtered.length - 1 : this.selectedIndex - 1;
|
|
96
|
+
this.updateList();
|
|
97
|
+
}
|
|
98
|
+
else if (kb.matches(keyData, "tui.select.down")) {
|
|
99
|
+
if (this.filtered.length === 0)
|
|
100
|
+
return;
|
|
101
|
+
this.selectedIndex = this.selectedIndex === this.filtered.length - 1 ? 0 : this.selectedIndex + 1;
|
|
102
|
+
this.updateList();
|
|
103
|
+
}
|
|
104
|
+
else if (kb.matches(keyData, "tui.select.confirm")) {
|
|
105
|
+
this.confirm();
|
|
106
|
+
}
|
|
107
|
+
else if (kb.matches(keyData, "tui.select.cancel")) {
|
|
108
|
+
this.onDone(undefined);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
this.searchInput.handleInput(keyData);
|
|
112
|
+
this.filter(this.searchInput.getValue());
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=picker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"picker.js","sourceRoot":"","sources":["../../../src/extensions/subagent/picker.ts"],"names":[],"mappings":"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","sourcesContent":["import { Container, type Focusable, fuzzyFilter, getKeybindings, Input, Spacer, Text } from \"@earendil-works/pi-tui\";\nimport { DynamicBorder } from \"../../modes/interactive/components/dynamic-border.ts\";\nimport { keyHint } from \"../../modes/interactive/components/keybinding-hints.ts\";\nimport type { Theme } from \"../../modes/interactive/theme/theme.ts\";\n\nexport interface PickerItem<T> {\n\tvalue: T;\n\tlabel: string;\n\tdetail?: string;\n\tcurrent?: boolean;\n}\n\nconst MAX_VISIBLE = 10;\n\n/**\n * Searchable selector matching the native /model experience: type to\n * fuzzy-filter, arrows wrap around the list, enter confirms, escape\n * cancels (resolves undefined). Mounted via ctx.ui.custom().\n */\nexport class SearchPickerComponent<T> extends Container implements Focusable {\n\tprivate readonly theme: Theme;\n\tprivate readonly items: PickerItem<T>[];\n\tprivate readonly onDone: (value: T | undefined) => void;\n\tprivate readonly searchInput: Input;\n\tprivate readonly listContainer: Container;\n\tprivate filtered: PickerItem<T>[];\n\tprivate selectedIndex = 0;\n\n\tprivate _focused = false;\n\tget focused(): boolean {\n\t\treturn this._focused;\n\t}\n\tset focused(value: boolean) {\n\t\tthis._focused = value;\n\t\tthis.searchInput.focused = value;\n\t}\n\n\tconstructor(theme: Theme, title: string, items: PickerItem<T>[], onDone: (value: T | undefined) => void) {\n\t\tsuper();\n\t\tthis.theme = theme;\n\t\tthis.items = items;\n\t\tthis.onDone = onDone;\n\t\tthis.filtered = items;\n\t\tconst currentIndex = items.findIndex((item) => item.current);\n\t\tif (currentIndex >= 0) this.selectedIndex = currentIndex;\n\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new Text(theme.fg(\"toolTitle\", theme.bold(title)), 0, 0));\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.searchInput = new Input();\n\t\tthis.searchInput.onSubmit = () => this.confirm();\n\t\tthis.addChild(this.searchInput);\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.listContainer = new Container();\n\t\tthis.addChild(this.listContainer);\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(\n\t\t\tnew Text(\n\t\t\t\t`${theme.fg(\"muted\", \"↑↓\")} ${theme.fg(\"dim\", \"navigate\")} ${keyHint(\"tui.select.confirm\", \"select\")} ${keyHint(\"tui.select.cancel\", \"cancel\")}`,\n\t\t\t\t0,\n\t\t\t\t0,\n\t\t\t),\n\t\t);\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.updateList();\n\t}\n\n\tprivate filter(query: string): void {\n\t\tthis.filtered = query\n\t\t\t? fuzzyFilter(this.items, query, (item) => `${item.label} ${item.detail ?? \"\"}`)\n\t\t\t: this.items;\n\t\tthis.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.filtered.length - 1));\n\t\tthis.updateList();\n\t}\n\n\tprivate confirm(): void {\n\t\tconst selected = this.filtered[this.selectedIndex];\n\t\tif (selected) this.onDone(selected.value);\n\t}\n\n\tprivate updateList(): void {\n\t\tthis.listContainer.clear();\n\t\tconst startIndex = Math.max(\n\t\t\t0,\n\t\t\tMath.min(this.selectedIndex - Math.floor(MAX_VISIBLE / 2), this.filtered.length - MAX_VISIBLE),\n\t\t);\n\t\tconst endIndex = Math.min(startIndex + MAX_VISIBLE, this.filtered.length);\n\t\tfor (let index = startIndex; index < endIndex; index++) {\n\t\t\tconst item = this.filtered[index];\n\t\t\tif (!item) continue;\n\t\t\tconst isSelected = index === this.selectedIndex;\n\t\t\tconst marker = item.current ? this.theme.fg(\"success\", \" ✓\") : \"\";\n\t\t\tconst line = isSelected\n\t\t\t\t? `${this.theme.fg(\"accent\", \"→ \")}${this.theme.fg(\"accent\", item.label)}${marker}`\n\t\t\t\t: ` ${item.label}${marker}`;\n\t\t\tthis.listContainer.addChild(new Text(line, 0, 0));\n\t\t}\n\t\tif (startIndex > 0 || endIndex < this.filtered.length) {\n\t\t\tthis.listContainer.addChild(\n\t\t\t\tnew Text(this.theme.fg(\"muted\", ` (${this.selectedIndex + 1}/${this.filtered.length})`), 0, 0),\n\t\t\t);\n\t\t}\n\t\tif (this.filtered.length === 0) {\n\t\t\tthis.listContainer.addChild(new Text(this.theme.fg(\"muted\", \" No matches\"), 0, 0));\n\t\t\treturn;\n\t\t}\n\t\tconst detail = this.filtered[this.selectedIndex]?.detail;\n\t\tif (detail) {\n\t\t\tthis.listContainer.addChild(new Spacer(1));\n\t\t\tthis.listContainer.addChild(new Text(this.theme.fg(\"muted\", ` ${detail}`), 0, 0));\n\t\t}\n\t}\n\n\thandleInput(keyData: string): void {\n\t\tconst kb = getKeybindings();\n\t\tif (kb.matches(keyData, \"tui.select.up\")) {\n\t\t\tif (this.filtered.length === 0) return;\n\t\t\tthis.selectedIndex = this.selectedIndex === 0 ? this.filtered.length - 1 : this.selectedIndex - 1;\n\t\t\tthis.updateList();\n\t\t} else if (kb.matches(keyData, \"tui.select.down\")) {\n\t\t\tif (this.filtered.length === 0) return;\n\t\t\tthis.selectedIndex = this.selectedIndex === this.filtered.length - 1 ? 0 : this.selectedIndex + 1;\n\t\t\tthis.updateList();\n\t\t} else if (kb.matches(keyData, \"tui.select.confirm\")) {\n\t\t\tthis.confirm();\n\t\t} else if (kb.matches(keyData, \"tui.select.cancel\")) {\n\t\t\tthis.onDone(undefined);\n\t\t} else {\n\t\t\tthis.searchInput.handleInput(keyData);\n\t\t\tthis.filter(this.searchInput.getValue());\n\t\t}\n\t}\n}\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type Component } from "@earendil-works/pi-tui";
|
|
2
|
+
import type { AgentToolResult, ToolRenderResultOptions } from "../../core/extensions/types.ts";
|
|
3
|
+
import { type Theme } from "../../modes/interactive/theme/theme.ts";
|
|
4
|
+
import type { SubagentParams } from "./schema.ts";
|
|
5
|
+
import type { SubagentDetails } from "./types.ts";
|
|
6
|
+
export declare function renderSubagentCall(args: SubagentParams, theme: Theme): Component;
|
|
7
|
+
export declare function renderSubagentResult(result: AgentToolResult<SubagentDetails>, options: ToolRenderResultOptions, theme: Theme, isError: boolean): Component;
|
|
8
|
+
//# sourceMappingURL=render.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../src/extensions/subagent/render.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAqC,MAAM,wBAAwB,CAAC;AAC3F,OAAO,KAAK,EAAE,eAAe,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAE/F,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,wCAAwC,CAAC;AAEtF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAyC,MAAM,YAAY,CAAC;AA8PzF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,KAAK,GAAG,SAAS,CAgBhF;AAED,wBAAgB,oBAAoB,CACnC,MAAM,EAAE,eAAe,CAAC,eAAe,CAAC,EACxC,OAAO,EAAE,uBAAuB,EAChC,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,OAAO,GACd,SAAS,CAyCX","sourcesContent":["import { type Component, Container, Markdown, Spacer, Text } from \"@earendil-works/pi-tui\";\nimport type { AgentToolResult, ToolRenderResultOptions } from \"../../core/extensions/types.ts\";\nimport { keyHint } from \"../../modes/interactive/components/keybinding-hints.ts\";\nimport { getMarkdownTheme, type Theme } from \"../../modes/interactive/theme/theme.ts\";\nimport { statusSummary } from \"./runner.ts\";\nimport type { SubagentParams } from \"./schema.ts\";\nimport type { SubagentDetails, SubagentRunDetails, SubagentRunStatus } from \"./types.ts\";\n\nconst SINGLE_EXCERPT_LIMIT = 200;\nconst RUN_LINE_EXCERPT_LIMIT = 64;\nconst LIVE_TAIL_LIMIT = 100;\nconst COLLAPSED_RUN_LIMIT = 4;\n\nconst ACTIVITY_GROUPS = [\n\t{ toolName: \"read\", verb: \"read\", singular: \"file\", plural: \"files\" },\n\t{ toolName: \"grep\", verb: \"searched\", singular: \"pattern\", plural: \"patterns\" },\n\t{ toolName: \"find\", verb: \"searched\", singular: \"path pattern\", plural: \"path patterns\" },\n\t{ toolName: \"ls\", verb: \"listed\", singular: \"directory\", plural: \"directories\" },\n\t{ toolName: \"bash\", verb: \"ran\", singular: \"command\", plural: \"commands\" },\n\t{ toolName: \"edit\", verb: \"edited\", singular: \"file\", plural: \"files\" },\n\t{ toolName: \"write\", verb: \"wrote\", singular: \"file\", plural: \"files\" },\n] as const;\n\nfunction singleAgentName(args: { agent?: string | null }): string {\n\treturn args.agent ?? \"general\";\n}\n\nfunction truncate(text: string, limit: number): string {\n\treturn text.length <= limit ? text : `${text.slice(0, Math.max(0, limit - 1))}…`;\n}\n\n// Transcript excerpts render inside plain Text components, so markdown\n// punctuation would show up literally; strip the common inline syntax.\nfunction stripInlineMarkdown(text: string): string {\n\treturn text\n\t\t.replace(/```[a-zA-Z0-9-]*/gu, \"\")\n\t\t.replace(/`([^`]*)`/gu, \"$1\")\n\t\t.replace(/\\*\\*([^*]+)\\*\\*/gu, \"$1\")\n\t\t.replace(/__([^_]+)__/gu, \"$1\")\n\t\t.replace(/\\[([^\\]]+)\\]\\([^)]*\\)/gu, \"$1\")\n\t\t.replace(/^#{1,6}\\s+/gmu, \"\");\n}\n\n// Markdown tables and horizontal rules turn into symbol soup when\n// flattened onto one line; drop those lines before inline stripping.\nfunction stripBlockMarkdown(text: string): string {\n\treturn text\n\t\t.split(\"\\n\")\n\t\t.filter((line) => {\n\t\t\tconst trimmed = line.trim();\n\t\t\tif (trimmed.startsWith(\"|\")) return false;\n\t\t\treturn !(trimmed.length >= 3 && /^[-—─═=*_|:\\s]+$/u.test(trimmed));\n\t\t})\n\t\t.join(\"\\n\");\n}\n\nfunction excerpt(text: string, limit: number): string {\n\t// finalOutput/liveText carry model-facing truncation notices from\n\t// boundText/tailText; in the transcript an ellipsis is enough.\n\tconst cleaned = text.replace(/\\[(?:Output truncated(?:: \\d+ bytes omitted\\.)?|Earlier output omitted\\.)\\]/gu, \"…\");\n\treturn truncate(stripInlineMarkdown(stripBlockMarkdown(cleaned)).replace(/\\s+/gu, \" \").trim(), limit);\n}\n\nfunction liveTail(run: SubagentRunDetails): string | undefined {\n\tconst lines = run.liveText\n\t\t.split(\"\\n\")\n\t\t.map((line) => line.trim())\n\t\t.filter(Boolean);\n\tconst last = lines.at(-1);\n\treturn last ? excerpt(last, LIVE_TAIL_LIMIT) : undefined;\n}\n\nfunction statusMarker(status: SubagentRunStatus, theme: Theme): string {\n\tswitch (status) {\n\t\tcase \"completed\":\n\t\t\treturn theme.fg(\"success\", \"✓\");\n\t\tcase \"failed\":\n\t\t\treturn theme.fg(\"error\", \"×\");\n\t\tcase \"aborted\":\n\t\t\treturn theme.fg(\"warning\", \"■\");\n\t\tcase \"running\":\n\t\t\treturn theme.fg(\"accent\", \"●\");\n\t\tdefault:\n\t\t\treturn theme.fg(\"muted\", \"○\");\n\t}\n}\n\nfunction runTitle(run: SubagentRunDetails, theme: Theme, mode: SubagentDetails[\"mode\"]): string {\n\tconst step = mode === \"chain\" && run.step ? theme.fg(\"muted\", `${run.step}. `) : \"\";\n\treturn `${statusMarker(run.status, theme)} ${step}${theme.fg(\"accent\", run.agent)}${theme.fg(\"dim\", ` · ${truncate(run.description, 48)}`)}`;\n}\n\nfunction runProgressText(run: SubagentRunDetails): string {\n\tconst items = [`${run.usage.toolUses} tool use${run.usage.toolUses === 1 ? \"\" : \"s\"}`];\n\tif (run.usage.totalTokens) items.push(`${formatTokens(run.usage.totalTokens)} tokens`);\n\treturn items.join(\" · \");\n}\n\nfunction runLine(run: SubagentRunDetails, theme: Theme, mode: SubagentDetails[\"mode\"]): string {\n\tlet line = runTitle(run, theme, mode);\n\tconst detail =\n\t\trun.status === \"running\"\n\t\t\t? run.currentActivity\n\t\t\t: run.status === \"failed\" || run.status === \"aborted\"\n\t\t\t\t? run.error && excerpt(run.error, RUN_LINE_EXCERPT_LIMIT)\n\t\t\t\t: run.finalOutput && excerpt(run.finalOutput, RUN_LINE_EXCERPT_LIMIT);\n\tif (detail) line += theme.fg(run.status === \"failed\" ? \"error\" : \"dim\", ` — ${detail}`);\n\tif (run.status === \"running\") line += theme.fg(\"dim\", ` · ${runProgressText(run)}`);\n\tconst settled = run.status !== \"running\" && run.status !== \"queued\";\n\tif (settled && run.startedAt !== undefined && run.endedAt !== undefined) {\n\t\tline += theme.fg(\"dim\", ` · ${formatDuration(Math.max(0, (run.endedAt - run.startedAt) / 1000))}`);\n\t}\n\treturn line;\n}\n\n// Keep active work visible while a batch is in flight: completed runs\n// may otherwise crowd a still-running task out of the collapsed window.\nfunction selectCollapsedRuns(runs: SubagentRunDetails[], isPartial: boolean): SubagentRunDetails[] {\n\tif (runs.length <= COLLAPSED_RUN_LIMIT) return runs;\n\tif (!isPartial) return runs.slice(0, COLLAPSED_RUN_LIMIT);\n\tconst active = runs.filter((run) => run.status === \"running\" || run.status === \"queued\");\n\tconst settled = runs.filter((run) => run.status !== \"running\" && run.status !== \"queued\");\n\treturn [...active, ...settled].slice(0, COLLAPSED_RUN_LIMIT);\n}\n\nfunction singleCollapsedLines(details: SubagentDetails, theme: Theme): string[] {\n\tconst run = details.runs[0];\n\tif (!run) return [theme.fg(\"muted\", \"Initializing…\")];\n\tif (run.status === \"running\" || run.status === \"queued\") {\n\t\tconst isInitializing =\n\t\t\trun.status === \"running\" && run.usage.turns === 0 && run.usage.toolUses === 0 && run.liveText.length === 0;\n\t\tconst lines = [\n\t\t\t`${statusMarker(run.status, theme)} ${theme.fg(\"dim\", run.currentActivity ?? (run.status === \"queued\" ? \"queued\" : isInitializing ? \"Initializing…\" : \"Thinking…\"))}`,\n\t\t];\n\t\tconst tail = liveTail(run);\n\t\tif (tail) lines.push(theme.fg(\"dim\", tail));\n\t\treturn lines;\n\t}\n\tif (run.error) return [theme.fg(\"error\", excerpt(run.error, SINGLE_EXCERPT_LIMIT))];\n\tif (run.finalOutput) return [theme.fg(\"toolOutput\", excerpt(run.finalOutput, SINGLE_EXCERPT_LIMIT))];\n\treturn [theme.fg(\"muted\", run.status)];\n}\n\nfunction activitySummaryText(details: SubagentDetails): string {\n\tconst toolCounts = new Map<string, number>();\n\tlet activityCount = 0;\n\tfor (const run of details.runs) {\n\t\tfor (const activity of run.activities) {\n\t\t\tactivityCount++;\n\t\t\ttoolCounts.set(activity.toolName, (toolCounts.get(activity.toolName) ?? 0) + 1);\n\t\t}\n\t}\n\tconst items: string[] = [];\n\tlet groupedCount = 0;\n\tfor (const group of ACTIVITY_GROUPS) {\n\t\tconst count = toolCounts.get(group.toolName) ?? 0;\n\t\tif (count === 0) continue;\n\t\tgroupedCount += count;\n\t\titems.push(`${group.verb} ${count} ${count === 1 ? group.singular : group.plural}`);\n\t}\n\tconst otherCount = activityCount - groupedCount;\n\tif (otherCount > 0) items.push(`used ${otherCount} other tool${otherCount === 1 ? \"\" : \"s\"}`);\n\tif (items.length === 0) return \"\";\n\tconst earlierCount = Math.max(0, details.usage.toolUses - activityCount);\n\tif (earlierCount > 0) items.push(`${earlierCount} earlier tool use${earlierCount === 1 ? \"\" : \"s\"}`);\n\treturn `${items[0]![0]!.toUpperCase()}${items[0]!.slice(1)}${items\n\t\t.slice(1)\n\t\t.map((item) => ` · ${item}`)\n\t\t.join(\"\")}`;\n}\n\nfunction usageText(details: SubagentDetails, includeToolUses = true): string {\n\tconst usage = details.usage;\n\tconst items: string[] = [];\n\tif (includeToolUses && usage.toolUses) items.push(`${usage.toolUses} tool use${usage.toolUses === 1 ? \"\" : \"s\"}`);\n\tif (usage.turns) items.push(`${usage.turns} turn${usage.turns === 1 ? \"\" : \"s\"}`);\n\tif (usage.output) items.push(`↓${formatTokens(usage.output)}`);\n\tif (usage.cost) items.push(`$${usage.cost.toFixed(3)}`);\n\tconst duration = details.endedAt ? Math.max(0, (details.endedAt - details.startedAt) / 1000) : undefined;\n\tif (duration !== undefined) items.push(formatDuration(duration));\n\treturn items.join(\" · \");\n}\n\nfunction formatTokens(value: number): string {\n\tif (value < 1_000) return String(value);\n\tif (value < 1_000_000) return `${(value / 1_000).toFixed(value < 10_000 ? 1 : 0)}k`;\n\treturn `${(value / 1_000_000).toFixed(1)}M`;\n}\n\nfunction formatDuration(seconds: number): string {\n\tif (seconds >= 90) return `${Math.floor(seconds / 60)}m ${Math.round(seconds % 60)}s`;\n\treturn `${seconds.toFixed(seconds < 10 ? 1 : 0)}s`;\n}\n\n// A single run's title and usage would duplicate the call header and\n// the call-level usage line, so `single` drops both and folds the\n// duration into the metadata line instead.\nfunction renderRunDetails(\n\trun: SubagentRunDetails,\n\ttheme: Theme,\n\tmode: SubagentDetails[\"mode\"],\n\tsingle: boolean,\n): Component {\n\tconst container = new Container();\n\tconst duration =\n\t\trun.startedAt !== undefined && run.endedAt !== undefined\n\t\t\t? formatDuration(Math.max(0, (run.endedAt - run.startedAt) / 1000))\n\t\t\t: undefined;\n\tif (!single) {\n\t\tlet title = runTitle(run, theme, mode);\n\t\tif (run.status === \"running\") title += theme.fg(\"dim\", ` · ${runProgressText(run)}`);\n\t\telse if (duration) title += theme.fg(\"dim\", ` · ${duration}`);\n\t\tcontainer.addChild(new Text(title, 0, 0));\n\t}\n\tcontainer.addChild(new Text(theme.fg(\"muted\", \"Task\"), 0, 0));\n\tcontainer.addChild(new Text(theme.fg(\"dim\", run.prompt), 0, 0));\n\tif (run.activities.length > 0) {\n\t\tcontainer.addChild(new Spacer(1));\n\t\tcontainer.addChild(new Text(theme.fg(\"muted\", \"Activity\"), 0, 0));\n\t\tfor (const activity of run.activities) {\n\t\t\tconst marker =\n\t\t\t\tactivity.status === \"succeeded\"\n\t\t\t\t\t? theme.fg(\"success\", \"✓\")\n\t\t\t\t\t: activity.status === \"failed\"\n\t\t\t\t\t\t? theme.fg(\"error\", \"×\")\n\t\t\t\t\t\t: theme.fg(\"accent\", \"●\");\n\t\t\tlet text = `${marker} ${theme.fg(\"toolOutput\", excerpt(activity.summary, 96))}`;\n\t\t\tif (activity.resultSummary) text += ` ${theme.fg(\"dim\", `· ${excerpt(activity.resultSummary, 64)}`)}`;\n\t\t\tcontainer.addChild(new Text(text, 0, 0));\n\t\t}\n\t}\n\tif (run.error) {\n\t\tcontainer.addChild(new Spacer(1));\n\t\tcontainer.addChild(new Text(theme.fg(\"error\", `Error: ${run.error}`), 0, 0));\n\t}\n\tif (run.finalOutput) {\n\t\tcontainer.addChild(new Spacer(1));\n\t\tcontainer.addChild(new Text(theme.fg(\"muted\", \"Response\"), 0, 0));\n\t\tcontainer.addChild(new Markdown(run.finalOutput, 0, 0, getMarkdownTheme()));\n\t}\n\tconst metadata = [\n\t\t`${run.model} · ${run.thinking}`,\n\t\trun.usage.toolUses ? `${run.usage.toolUses} tool use${run.usage.toolUses === 1 ? \"\" : \"s\"}` : undefined,\n\t\trun.usage.turns ? `${run.usage.turns} turn${run.usage.turns === 1 ? \"\" : \"s\"}` : undefined,\n\t\trun.usage.output ? `↓${formatTokens(run.usage.output)}` : undefined,\n\t\trun.usage.cost ? `$${run.usage.cost.toFixed(3)}` : undefined,\n\t\tsingle ? duration : undefined,\n\t]\n\t\t.filter((item): item is string => Boolean(item))\n\t\t.join(\" · \");\n\tif (metadata) {\n\t\tcontainer.addChild(new Spacer(1));\n\t\tcontainer.addChild(new Text(theme.fg(\"dim\", metadata), 0, 0));\n\t}\n\treturn container;\n}\n\n// The call header stays a single line: run rows in the result area take\n// over within the first update and carry richer per-task state, so a\n// task list here would render everything twice.\nexport function renderSubagentCall(args: SubagentParams, theme: Theme): Component {\n\tlet text = theme.fg(\"toolTitle\", theme.bold(\"Subagent \"));\n\tconst modes = [args.prompt != null && \"prompt\", args.tasks != null && \"tasks\", args.chain != null && \"chain\"].filter(\n\t\t(mode): mode is string => Boolean(mode),\n\t);\n\tif (modes.length > 1) {\n\t\ttext += theme.fg(\"error\", `invalid · ${modes.join(\" + \")}`);\n\t} else if (args.tasks) {\n\t\ttext += theme.fg(\"accent\", `parallel · ${args.tasks.length} tasks`);\n\t} else if (args.chain) {\n\t\ttext += theme.fg(\"accent\", `chain · ${args.chain.length} steps`);\n\t} else {\n\t\ttext += theme.fg(\"accent\", singleAgentName(args));\n\t\tif (args.description) text += theme.fg(\"dim\", ` · ${truncate(args.description, 72)}`);\n\t}\n\treturn new Text(text, 0, 0);\n}\n\nexport function renderSubagentResult(\n\tresult: AgentToolResult<SubagentDetails>,\n\toptions: ToolRenderResultOptions,\n\ttheme: Theme,\n\tisError: boolean,\n): Component {\n\tconst details = result.details;\n\tif (!details) {\n\t\tconst text = result.content.find((part) => part.type === \"text\");\n\t\treturn new Text(text?.type === \"text\" ? text.text : \"(no output)\", 0, 0);\n\t}\n\tif (!options.expanded) {\n\t\tconst lines: string[] = [];\n\t\tif (details.mode === \"single\") {\n\t\t\tlines.push(...singleCollapsedLines(details, theme));\n\t\t} else {\n\t\t\tlines.push(theme.fg(isError ? \"error\" : options.isPartial ? \"accent\" : \"muted\", statusSummary(details)));\n\t\t\tconst shown = selectCollapsedRuns(details.runs, Boolean(options.isPartial));\n\t\t\tfor (const run of shown) lines.push(runLine(run, theme, details.mode));\n\t\t\tconst hidden = details.runs.length - shown.length;\n\t\t\tif (hidden > 0) lines.push(theme.fg(\"muted\", `+${hidden} more`));\n\t\t}\n\t\tconst activitySummary = activitySummaryText(details);\n\t\tif (activitySummary) lines.push(theme.fg(\"dim\", activitySummary));\n\t\tconst usage = usageText(details, !activitySummary);\n\t\tif (usage) lines.push(theme.fg(\"dim\", usage));\n\t\tif (!options.isPartial) lines.push(theme.fg(\"muted\", `(${keyHint(\"app.tools.expand\", \"to expand\")})`));\n\t\treturn new Text(lines.join(\"\\n\"), 0, 0);\n\t}\n\tconst container = new Container();\n\tconst single = details.mode === \"single\" && details.runs.length === 1;\n\tif (!single) {\n\t\tcontainer.addChild(new Text(theme.fg(isError ? \"error\" : \"toolTitle\", statusSummary(details)), 0, 0));\n\t}\n\tdetails.runs.forEach((run, index) => {\n\t\tif (!single || index > 0) container.addChild(new Spacer(1));\n\t\tcontainer.addChild(renderRunDetails(run, theme, details.mode, single));\n\t});\n\tif (!single) {\n\t\tconst usage = usageText(details);\n\t\tif (usage) {\n\t\t\tcontainer.addChild(new Spacer(1));\n\t\t\tcontainer.addChild(new Text(theme.fg(\"dim\", usage), 0, 0));\n\t\t}\n\t}\n\treturn container;\n}\n"]}
|