agency-lang 0.8.0 → 0.9.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/dist/lib/agents/agency-agent/agent.agency +381 -1604
- package/dist/lib/agents/agency-agent/agent.js +2880 -8317
- package/dist/lib/agents/agency-agent/images/images.js +6 -1
- package/dist/lib/agents/agency-agent/lib/args.agency +88 -0
- package/dist/lib/agents/agency-agent/lib/args.js +386 -0
- package/dist/lib/agents/agency-agent/lib/attachments.js +40 -4
- package/dist/lib/agents/agency-agent/lib/capabilities.agency +22 -20
- package/dist/lib/agents/agency-agent/lib/capabilities.js +115 -31
- package/dist/lib/agents/agency-agent/lib/capabilitiesUi.agency +330 -0
- package/dist/lib/agents/agency-agent/lib/capabilitiesUi.js +2314 -0
- package/dist/lib/agents/agency-agent/lib/commandPalette.agency +38 -0
- package/dist/lib/agents/agency-agent/lib/commandPalette.js +494 -0
- package/dist/lib/agents/agency-agent/lib/config.js +8 -2
- package/dist/lib/agents/agency-agent/lib/header.agency +82 -0
- package/dist/lib/agents/agency-agent/lib/header.js +1024 -0
- package/dist/lib/agents/agency-agent/lib/images.agency +62 -0
- package/dist/lib/agents/agency-agent/lib/images.js +645 -0
- package/dist/lib/agents/agency-agent/lib/mcp.agency +103 -0
- package/dist/lib/agents/agency-agent/lib/mcp.js +1152 -0
- package/dist/lib/agents/agency-agent/lib/modelFilters.js +8 -2
- package/dist/lib/agents/agency-agent/lib/models.agency +1 -2
- package/dist/lib/agents/agency-agent/lib/models.js +42 -21
- package/dist/lib/agents/agency-agent/lib/policy.agency +111 -0
- package/dist/lib/agents/agency-agent/lib/policy.js +971 -0
- package/dist/lib/agents/agency-agent/lib/resolution.js +56 -12
- package/dist/lib/agents/agency-agent/lib/search.agency +21 -3
- package/dist/lib/agents/agency-agent/lib/search.js +285 -22
- package/dist/lib/agents/agency-agent/lib/session.agency +331 -0
- package/dist/lib/agents/agency-agent/lib/session.js +1884 -0
- package/dist/lib/agents/agency-agent/lib/settings.js +76 -20
- package/dist/lib/agents/agency-agent/lib/slots.agency +0 -1
- package/dist/lib/agents/agency-agent/lib/slots.js +15 -8
- package/dist/lib/agents/agency-agent/lib/utils/renderLLMCallResponse.agency +27 -0
- package/dist/lib/agents/agency-agent/lib/utils/renderLLMCallResponse.js +353 -0
- package/dist/lib/agents/agency-agent/lib/utils.agency +42 -0
- package/dist/lib/agents/agency-agent/lib/utils.js +419 -9
- package/dist/lib/agents/agency-agent/prompts/codeSample.js +25 -1
- package/dist/lib/agents/agency-agent/prompts/main-large.md +166 -0
- package/dist/lib/agents/agency-agent/prompts/main-small.md +26 -0
- package/dist/lib/agents/agency-agent/shared.agency +0 -3
- package/dist/lib/agents/agency-agent/shared.js +124 -32
- package/dist/lib/agents/agency-agent/subagents/code.agency +153 -51
- package/dist/lib/agents/agency-agent/subagents/code.js +680 -174
- package/dist/lib/agents/agency-agent/subagents/explorer.agency +3 -2
- package/dist/lib/agents/agency-agent/subagents/explorer.js +27 -22
- package/dist/lib/agents/agency-agent/subagents/oracle.agency +3 -2
- package/dist/lib/agents/agency-agent/subagents/oracle.js +27 -22
- package/dist/lib/agents/agency-agent/subagents/research.agency +14 -25
- package/dist/lib/agents/agency-agent/subagents/research.js +36 -90
- package/dist/lib/agents/agency-agent/subagents/review.agency +60 -4
- package/dist/lib/agents/agency-agent/subagents/review.js +658 -25
- package/dist/lib/agents/agency-agent/subagents/verify.agency +23 -77
- package/dist/lib/agents/agency-agent/subagents/verify.js +267 -185
- package/dist/lib/agents/agency-agent/tests/agentDir.js +19 -1
- package/dist/lib/agents/agency-agent/tests/agentTurn.agency +31 -4
- package/dist/lib/agents/agency-agent/tests/agentTurn.js +480 -22
- package/dist/lib/agents/agency-agent/tests/agentTurn.test.json +30 -0
- package/dist/lib/agents/agency-agent/tests/attachments.agency +6 -1
- package/dist/lib/agents/agency-agent/tests/attachments.js +288 -207
- package/dist/lib/agents/agency-agent/tests/attachmentsTurn.agency +5 -10
- package/dist/lib/agents/agency-agent/tests/attachmentsTurn.js +60 -141
- package/dist/lib/agents/agency-agent/tests/attachmentsTurn.test.json +0 -16
- package/dist/lib/agents/agency-agent/tests/capabilities.js +84 -2
- package/dist/lib/agents/agency-agent/tests/capabilities.test.json +1 -1
- package/dist/lib/agents/agency-agent/tests/execPolicy.js +8 -2
- package/dist/lib/agents/agency-agent/tests/gitPolicy.js +8 -2
- package/dist/lib/agents/agency-agent/tests/imageTool.agency +1 -1
- package/dist/lib/agents/agency-agent/tests/imageTool.js +62 -4
- package/dist/lib/agents/agency-agent/tests/mcpGating.js +18 -2
- package/dist/lib/agents/agency-agent/tests/memoryWiring.js +82 -1
- package/dist/lib/agents/agency-agent/tests/modelFilters.js +13 -1
- package/dist/lib/agents/agency-agent/tests/models.js +12 -2
- package/dist/lib/agents/agency-agent/tests/oneShotRounds.js +6 -1
- package/dist/lib/agents/agency-agent/tests/resolution.js +54 -4
- package/dist/lib/agents/agency-agent/tests/search.js +37 -5
- package/dist/lib/agents/agency-agent/tests/settings.agency +5 -1
- package/dist/lib/agents/agency-agent/tests/settings.js +126 -49
- package/dist/lib/agents/agency-agent/tests/settings.test.json +2 -2
- package/dist/lib/agents/agency-agent/tests/slots.js +6 -1
- package/dist/lib/agents/agency-agent/tests/toolWiring.js +19 -2
- package/dist/lib/agents/agency-agent/tests/turnFailure.agency +8 -3
- package/dist/lib/agents/agency-agent/tests/turnFailure.js +19 -4
- package/dist/lib/agents/eval/goalJudge.js +9 -1
- package/dist/lib/agents/eval/judge.js +7 -1
- package/dist/lib/agents/eval/judgePairwise.js +7 -1
- package/dist/lib/agents/optimize/gepaReflect.js +7 -1
- package/dist/lib/agents/optimize/mutatePrompt.js +7 -1
- package/dist/lib/agents/policy/agent.js +92 -16
- package/dist/lib/agents/review/agent.js +64 -5
- package/dist/lib/backends/agencyGenerator.d.ts +12 -0
- package/dist/lib/backends/agencyGenerator.finalizeBlock.test.d.ts +1 -0
- package/dist/lib/backends/agencyGenerator.finalizeBlock.test.js +36 -0
- package/dist/lib/backends/agencyGenerator.guardBlock.test.d.ts +1 -0
- package/dist/lib/backends/agencyGenerator.guardBlock.test.js +33 -0
- package/dist/lib/backends/agencyGenerator.js +35 -1
- package/dist/lib/backends/agencyGenerator.test.js +52 -0
- package/dist/lib/backends/draftSchemaCodegen.test.d.ts +1 -0
- package/dist/lib/backends/draftSchemaCodegen.test.js +38 -0
- package/dist/lib/backends/finalizeBinderCodegen.test.d.ts +1 -0
- package/dist/lib/backends/finalizeBinderCodegen.test.js +26 -0
- package/dist/lib/backends/functionContainsDestructiveBlock.d.ts +22 -0
- package/dist/lib/backends/functionContainsDestructiveBlock.js +30 -0
- package/dist/lib/backends/functionContainsDestructiveBlock.test.d.ts +1 -0
- package/dist/lib/backends/functionContainsDestructiveBlock.test.js +31 -0
- package/dist/lib/backends/typescriptBuilder/destructiveTracking.d.ts +70 -0
- package/dist/lib/backends/typescriptBuilder/destructiveTracking.js +123 -0
- package/dist/lib/backends/typescriptBuilder/destructiveTracking.test.d.ts +1 -0
- package/dist/lib/backends/typescriptBuilder/destructiveTracking.test.js +111 -0
- package/dist/lib/backends/typescriptBuilder/finalizeCodegen.d.ts +104 -0
- package/dist/lib/backends/typescriptBuilder/finalizeCodegen.js +146 -0
- package/dist/lib/backends/typescriptBuilder/nameClassifier.d.ts +2 -11
- package/dist/lib/backends/typescriptBuilder/nameClassifier.js +7 -27
- package/dist/lib/backends/typescriptBuilder/scopeManager.d.ts +9 -0
- package/dist/lib/backends/typescriptBuilder/scopeManager.js +30 -0
- package/dist/lib/backends/typescriptBuilder/stepPathTracker.js +2 -1
- package/dist/lib/backends/typescriptBuilder.d.ts +43 -12
- package/dist/lib/backends/typescriptBuilder.integration.test.js +4 -2
- package/dist/lib/backends/typescriptBuilder.js +258 -98
- package/dist/lib/cli/agent.d.ts +4 -1
- package/dist/lib/cli/agent.js +2 -2
- package/dist/lib/cli/budget.d.ts +19 -0
- package/dist/lib/cli/budget.js +44 -0
- package/dist/lib/cli/budget.test.d.ts +1 -0
- package/dist/lib/cli/budget.test.js +46 -0
- package/dist/lib/cli/budgetRun.spawn.test.d.ts +1 -0
- package/dist/lib/cli/budgetRun.spawn.test.js +109 -0
- package/dist/lib/cli/commands.d.ts +33 -0
- package/dist/lib/cli/commands.js +79 -2
- package/dist/lib/cli/commands.test.d.ts +1 -0
- package/dist/lib/cli/commands.test.js +115 -0
- package/dist/lib/cli/diagnosticsDocs.d.ts +8 -0
- package/dist/lib/cli/diagnosticsDocs.js +79 -0
- package/dist/lib/cli/diagnosticsDocs.test.d.ts +1 -0
- package/dist/lib/cli/diagnosticsDocs.test.js +61 -0
- package/dist/lib/cli/doc.js +5 -0
- package/dist/lib/cli/doctor.js +2 -2
- package/dist/lib/cli/doctor.test.js +3 -3
- package/dist/lib/cli/explain.d.ts +9 -0
- package/dist/lib/cli/explain.js +53 -0
- package/dist/lib/cli/explain.test.d.ts +1 -0
- package/dist/lib/cli/explain.test.js +44 -0
- package/dist/lib/cli/runBundledAgent.d.ts +4 -1
- package/dist/lib/cli/runBundledAgent.js +23 -7
- package/dist/lib/cli/runBundledAgent.test.js +40 -8
- package/dist/lib/cli/serve.js +16 -28
- package/dist/lib/compilationUnit.js +6 -3
- package/dist/lib/compiler/buildSession.js +7 -1
- package/dist/lib/config.d.ts +4 -1
- package/dist/lib/config.js +17 -2
- package/dist/lib/config.test.js +7 -1
- package/dist/lib/constants.d.ts +9 -0
- package/dist/lib/constants.js +9 -0
- package/dist/lib/ir/prettyPrint.js +11 -1
- package/dist/lib/lowering/patternLowering.js +39 -0
- package/dist/lib/lsp/builtinHover.js +4 -3
- package/dist/lib/lsp/inlayHint.js +2 -1
- package/dist/lib/lsp/typeResolution.js +2 -1
- package/dist/lib/parsers/destructiveBlock.test.d.ts +1 -0
- package/dist/lib/parsers/destructiveBlock.test.js +36 -0
- package/dist/lib/parsers/finalizeBinder.test.d.ts +1 -0
- package/dist/lib/parsers/finalizeBinder.test.js +59 -0
- package/dist/lib/parsers/finalizeBlock.test.d.ts +1 -0
- package/dist/lib/parsers/finalizeBlock.test.js +37 -0
- package/dist/lib/parsers/guardBlock.test.d.ts +1 -0
- package/dist/lib/parsers/guardBlock.test.js +81 -0
- package/dist/lib/parsers/parsers.d.ts +26 -0
- package/dist/lib/parsers/parsers.js +71 -4
- package/dist/lib/preprocessors/guardDesugar.d.ts +24 -0
- package/dist/lib/preprocessors/guardDesugar.js +69 -0
- package/dist/lib/preprocessors/guardDesugar.test.d.ts +1 -0
- package/dist/lib/preprocessors/guardDesugar.test.js +48 -0
- package/dist/lib/preprocessors/liftCallbacks.js +3 -0
- package/dist/lib/preprocessors/parallelDesugar.js +17 -1
- package/dist/lib/preprocessors/parallelDesugar.test.js +24 -0
- package/dist/lib/preprocessors/prunePreludeShadows.d.ts +0 -20
- package/dist/lib/preprocessors/prunePreludeShadows.js +11 -0
- package/dist/lib/preprocessors/typescriptPreprocessor.js +11 -0
- package/dist/lib/runtime/__tests__/testHelpers.js +1 -1
- package/dist/lib/runtime/abortedResult.d.ts +100 -0
- package/dist/lib/runtime/abortedResult.js +217 -0
- package/dist/lib/runtime/abortedResult.test.d.ts +1 -0
- package/dist/lib/runtime/abortedResult.test.js +295 -0
- package/dist/lib/runtime/agencyFunction.js +18 -2
- package/dist/lib/runtime/agencyFunction.test.js +39 -0
- package/dist/lib/runtime/asyncContext.d.ts +1 -1
- package/dist/lib/runtime/asyncContext.js +11 -2
- package/dist/lib/runtime/blockNames.d.ts +11 -0
- package/dist/lib/runtime/blockNames.js +21 -0
- package/dist/lib/runtime/blockNames.test.d.ts +1 -0
- package/dist/lib/runtime/blockNames.test.js +18 -0
- package/dist/lib/runtime/budgetExit.d.ts +20 -0
- package/dist/lib/runtime/budgetExit.js +35 -0
- package/dist/lib/runtime/budgetExit.test.d.ts +1 -0
- package/dist/lib/runtime/budgetExit.test.js +59 -0
- package/dist/lib/runtime/call.js +77 -0
- package/dist/lib/runtime/configOverrides.test.js +1 -1
- package/dist/lib/runtime/deterministicClient.d.ts +9 -1
- package/dist/lib/runtime/deterministicClient.js +23 -1
- package/dist/lib/runtime/effectMerge.d.ts +31 -0
- package/dist/lib/runtime/effectMerge.js +93 -0
- package/dist/lib/runtime/effectMerge.test.d.ts +1 -0
- package/dist/lib/runtime/effectMerge.test.js +91 -0
- package/dist/lib/runtime/errors.d.ts +8 -0
- package/dist/lib/runtime/errors.js +10 -0
- package/dist/lib/runtime/guard.d.ts +259 -32
- package/dist/lib/runtime/guard.js +353 -54
- package/dist/lib/runtime/guard.test.js +207 -23
- package/dist/lib/runtime/guardScope.d.ts +74 -0
- package/dist/lib/runtime/guardScope.js +140 -0
- package/dist/lib/runtime/guardScope.test.d.ts +1 -0
- package/dist/lib/runtime/guardScope.test.js +228 -0
- package/dist/lib/runtime/guardTripInterrupt.d.ts +61 -0
- package/dist/lib/runtime/guardTripInterrupt.js +273 -0
- package/dist/lib/runtime/index.d.ts +4 -1
- package/dist/lib/runtime/index.js +4 -1
- package/dist/lib/runtime/interrupts.d.ts +14 -8
- package/dist/lib/runtime/interrupts.js +81 -25
- package/dist/lib/runtime/interrupts.test.js +58 -11
- package/dist/lib/runtime/intrinsicToolSchema.test.d.ts +1 -0
- package/dist/lib/runtime/intrinsicToolSchema.test.js +188 -0
- package/dist/lib/runtime/intrinsicTools.d.ts +88 -0
- package/dist/lib/runtime/intrinsicTools.js +82 -0
- package/dist/lib/runtime/ipc.js +8 -2
- package/dist/lib/runtime/nn.d.ts +13 -0
- package/dist/lib/runtime/nn.js +15 -0
- package/dist/lib/runtime/nn.test.d.ts +1 -0
- package/dist/lib/runtime/nn.test.js +20 -0
- package/dist/lib/runtime/node.js +7 -0
- package/dist/lib/runtime/prompt.d.ts +29 -0
- package/dist/lib/runtime/prompt.js +437 -209
- package/dist/lib/runtime/promptLabels.test.d.ts +1 -0
- package/dist/lib/runtime/promptLabels.test.js +159 -0
- package/dist/lib/runtime/promptRunner.d.ts +7 -2
- package/dist/lib/runtime/result.d.ts +6 -4
- package/dist/lib/runtime/result.js +37 -2
- package/dist/lib/runtime/result.test.js +58 -0
- package/dist/lib/runtime/revivers/functionRefReviver.js +27 -0
- package/dist/lib/runtime/revivers/functionRefReviver.test.js +41 -0
- package/dist/lib/runtime/rootBudget.d.ts +13 -0
- package/dist/lib/runtime/rootBudget.js +52 -0
- package/dist/lib/runtime/rootBudget.test.d.ts +1 -0
- package/dist/lib/runtime/rootBudget.test.js +70 -0
- package/dist/lib/runtime/runBatch.js +169 -9
- package/dist/lib/runtime/runPolicyHandler.d.ts +1 -1
- package/dist/lib/runtime/runPolicyHandler.js +5 -1
- package/dist/lib/runtime/runner.d.ts +9 -0
- package/dist/lib/runtime/runner.js +77 -8
- package/dist/lib/runtime/runner.test.js +19 -3
- package/dist/lib/runtime/saveDraftTool.d.ts +13 -0
- package/dist/lib/runtime/saveDraftTool.js +70 -0
- package/dist/lib/runtime/saveDraftTool.test.d.ts +1 -0
- package/dist/lib/runtime/saveDraftTool.test.js +114 -0
- package/dist/lib/runtime/state/context.d.ts +21 -3
- package/dist/lib/runtime/state/context.js +21 -3
- package/dist/lib/runtime/state/messageThread.d.ts +45 -2
- package/dist/lib/runtime/state/messageThread.js +84 -5
- package/dist/lib/runtime/state/messageThread.test.js +142 -0
- package/dist/lib/runtime/state/stateStack.d.ts +175 -6
- package/dist/lib/runtime/state/stateStack.js +293 -11
- package/dist/lib/runtime/state/stateStack.test.js +75 -1
- package/dist/lib/runtime/types.d.ts +19 -1
- package/dist/lib/runtime/utils.js +19 -3
- package/dist/lib/runtime/utils.test.js +42 -0
- package/dist/lib/serve/createServeHandler.d.ts +34 -0
- package/dist/lib/serve/createServeHandler.js +43 -0
- package/dist/lib/serve/createServeHandler.test.d.ts +1 -0
- package/dist/lib/serve/createServeHandler.test.js +115 -0
- package/dist/lib/serve/http/adapter.d.ts +8 -7
- package/dist/lib/serve/http/adapter.js +6 -2
- package/dist/lib/serve/http/adapter.test.js +7 -3
- package/dist/lib/serve/http/functionFrame.integration.test.js +0 -1
- package/dist/lib/serve/metadata.d.ts +22 -0
- package/dist/lib/serve/metadata.js +33 -0
- package/dist/lib/serve/metadata.test.d.ts +1 -0
- package/dist/lib/serve/metadata.test.js +46 -0
- package/dist/lib/serve/public.d.ts +8 -0
- package/dist/lib/serve/public.js +4 -0
- package/dist/lib/serve/public.test.d.ts +1 -0
- package/dist/lib/serve/public.test.js +16 -0
- package/dist/lib/statelogClient.d.ts +25 -4
- package/dist/lib/statelogClient.js +21 -1
- package/dist/lib/stdlib/appleNotes.d.ts +111 -0
- package/dist/lib/stdlib/appleNotes.js +314 -0
- package/dist/lib/stdlib/builtins.d.ts +7 -0
- package/dist/lib/stdlib/builtins.js +32 -3
- package/dist/lib/stdlib/cli.js +10 -8
- package/dist/lib/stdlib/git.d.ts +4 -0
- package/dist/lib/stdlib/git.js +24 -1
- package/dist/lib/stdlib/http.js +8 -0
- package/dist/lib/stdlib/markdown.d.ts +8 -0
- package/dist/lib/stdlib/markdown.js +238 -0
- package/dist/lib/stdlib/skills.d.ts +1 -1
- package/dist/lib/stdlib/thread.d.ts +15 -7
- package/dist/lib/stdlib/thread.js +71 -27
- package/dist/lib/stdlib/wikipedia.js +17 -3
- package/dist/lib/symbolTable.d.ts +19 -0
- package/dist/lib/symbolTable.importModule.test.d.ts +1 -0
- package/dist/lib/symbolTable.importModule.test.js +54 -0
- package/dist/lib/symbolTable.js +59 -5
- package/dist/lib/templates/applescript/notes/accountWalk.d.ts +4 -0
- package/dist/lib/templates/applescript/notes/accountWalk.js +18 -0
- package/dist/lib/templates/applescript/notes/appendBody.d.ts +6 -0
- package/dist/lib/templates/applescript/notes/appendBody.js +15 -0
- package/dist/lib/templates/applescript/notes/appendScoped.d.ts +6 -0
- package/dist/lib/templates/applescript/notes/appendScoped.js +12 -0
- package/dist/lib/templates/applescript/notes/appendUnscoped.d.ts +6 -0
- package/dist/lib/templates/applescript/notes/appendUnscoped.js +12 -0
- package/dist/lib/templates/applescript/notes/create.d.ts +6 -0
- package/dist/lib/templates/applescript/notes/create.js +20 -0
- package/dist/lib/templates/applescript/notes/deleteScoped.d.ts +4 -0
- package/dist/lib/templates/applescript/notes/deleteScoped.js +13 -0
- package/dist/lib/templates/applescript/notes/deleteUnscoped.d.ts +4 -0
- package/dist/lib/templates/applescript/notes/deleteUnscoped.js +13 -0
- package/dist/lib/templates/applescript/notes/folderExists.d.ts +4 -0
- package/dist/lib/templates/applescript/notes/folderExists.js +11 -0
- package/dist/lib/templates/applescript/notes/listAll.d.ts +6 -0
- package/dist/lib/templates/applescript/notes/listAll.js +16 -0
- package/dist/lib/templates/applescript/notes/listFolders.d.ts +4 -0
- package/dist/lib/templates/applescript/notes/listFolders.js +20 -0
- package/dist/lib/templates/applescript/notes/listInFolder.d.ts +6 -0
- package/dist/lib/templates/applescript/notes/listInFolder.js +16 -0
- package/dist/lib/templates/applescript/notes/noteRow.d.ts +6 -0
- package/dist/lib/templates/applescript/notes/noteRow.js +13 -0
- package/dist/lib/templates/applescript/notes/preflight.d.ts +6 -0
- package/dist/lib/templates/applescript/notes/preflight.js +16 -0
- package/dist/lib/templates/applescript/notes/readScoped.d.ts +4 -0
- package/dist/lib/templates/applescript/notes/readScoped.js +13 -0
- package/dist/lib/templates/applescript/notes/readUnscoped.d.ts +4 -0
- package/dist/lib/templates/applescript/notes/readUnscoped.js +13 -0
- package/dist/lib/templates/applescript/notes/searchAll.d.ts +6 -0
- package/dist/lib/templates/applescript/notes/searchAll.js +16 -0
- package/dist/lib/templates/applescript/notes/searchInFolder.d.ts +6 -0
- package/dist/lib/templates/applescript/notes/searchInFolder.js +16 -0
- package/dist/lib/templates/applescript/notes/withTimeout.d.ts +7 -0
- package/dist/lib/templates/applescript/notes/withTimeout.js +13 -0
- package/dist/lib/templates/backends/agency/template.d.ts +1 -1
- package/dist/lib/templates/backends/agency/template.js +1 -1
- package/dist/lib/templates/backends/typescriptGenerator/blockSetup.d.ts +3 -1
- package/dist/lib/templates/backends/typescriptGenerator/blockSetup.js +10 -1
- package/dist/lib/templates/backends/typescriptGenerator/finalizeClosure.d.ts +10 -0
- package/dist/lib/templates/backends/typescriptGenerator/finalizeClosure.js +14 -0
- package/dist/lib/templates/backends/typescriptGenerator/functionCatchFailure.d.ts +2 -1
- package/dist/lib/templates/backends/typescriptGenerator/functionCatchFailure.js +6 -1
- package/dist/lib/templates/backends/typescriptGenerator/imports.d.ts +1 -1
- package/dist/lib/templates/backends/typescriptGenerator/imports.js +5 -2
- package/dist/lib/templates/backends/typescriptGenerator/withHandlerWrapper.d.ts +9 -0
- package/dist/lib/templates/backends/typescriptGenerator/withHandlerWrapper.js +14 -0
- package/dist/lib/templates/runtime/guardTripMessage.d.ts +9 -0
- package/dist/lib/templates/runtime/guardTripMessage.js +9 -0
- package/dist/lib/typeChecker/anySentinelRetired.test.d.ts +1 -0
- package/dist/lib/typeChecker/anySentinelRetired.test.js +53 -0
- package/dist/lib/typeChecker/assignability.d.ts +3 -3
- package/dist/lib/typeChecker/assignability.js +10 -9
- package/dist/lib/typeChecker/assignability.test.js +14 -1
- package/dist/lib/typeChecker/builtins.js +30 -20
- package/dist/lib/typeChecker/checker.d.ts +1 -1
- package/dist/lib/typeChecker/checker.js +91 -28
- package/dist/lib/typeChecker/diagnosticExplanations.d.ts +23 -0
- package/dist/lib/typeChecker/diagnosticExplanations.js +377 -0
- package/dist/lib/typeChecker/diagnosticExplanations.test.d.ts +1 -0
- package/dist/lib/typeChecker/diagnosticExplanations.test.js +52 -0
- package/dist/lib/typeChecker/diagnostics.d.ts +100 -5
- package/dist/lib/typeChecker/diagnostics.js +83 -5
- package/dist/lib/typeChecker/diagnostics.test.js +11 -1
- package/dist/lib/typeChecker/effectPayloadCheck.js +2 -1
- package/dist/lib/typeChecker/effectSets.js +2 -1
- package/dist/lib/typeChecker/finalize.test.d.ts +1 -0
- package/dist/lib/typeChecker/finalize.test.js +312 -0
- package/dist/lib/typeChecker/finalizeBinder.d.ts +21 -0
- package/dist/lib/typeChecker/finalizeBinder.js +70 -0
- package/dist/lib/typeChecker/finalizeBinder.test.d.ts +1 -0
- package/dist/lib/typeChecker/finalizeBinder.test.js +132 -0
- package/dist/lib/typeChecker/finalizeChecks.d.ts +16 -0
- package/dist/lib/typeChecker/finalizeChecks.js +161 -0
- package/dist/lib/typeChecker/fixtureTypeCheck.integration.test.js +3 -0
- package/dist/lib/typeChecker/flow.js +14 -12
- package/dist/lib/typeChecker/flow.test.js +12 -11
- package/dist/lib/typeChecker/flowBuilder.js +34 -2
- package/dist/lib/typeChecker/formatErrors.test.js +25 -1
- package/dist/lib/typeChecker/functionTypeRaises.js +2 -1
- package/dist/lib/typeChecker/functionValueEffects.js +2 -1
- package/dist/lib/typeChecker/guardConstruct.test.d.ts +1 -0
- package/dist/lib/typeChecker/guardConstruct.test.js +143 -0
- package/dist/lib/typeChecker/index.d.ts +9 -1
- package/dist/lib/typeChecker/index.js +57 -7
- package/dist/lib/typeChecker/inference.d.ts +1 -1
- package/dist/lib/typeChecker/inference.js +9 -12
- package/dist/lib/typeChecker/interruptAnalysis.js +8 -3
- package/dist/lib/typeChecker/matchExprTypes.js +7 -8
- package/dist/lib/typeChecker/missingImportDiagnostic.d.ts +12 -0
- package/dist/lib/typeChecker/missingImportDiagnostic.js +69 -0
- package/dist/lib/typeChecker/missingImportDiagnostic.test.d.ts +1 -0
- package/dist/lib/typeChecker/missingImportDiagnostic.test.js +183 -0
- package/dist/lib/typeChecker/narrowing.js +2 -1
- package/dist/lib/typeChecker/reservedNameDeclaration.test.js +21 -1
- package/dist/lib/typeChecker/resolveCall.js +26 -21
- package/dist/lib/typeChecker/saveDraft.test.d.ts +1 -0
- package/dist/lib/typeChecker/saveDraft.test.js +139 -0
- package/dist/lib/typeChecker/scope.d.ts +5 -1
- package/dist/lib/typeChecker/scope.js +6 -0
- package/dist/lib/typeChecker/scope.test.js +9 -8
- package/dist/lib/typeChecker/scopes.js +16 -11
- package/dist/lib/typeChecker/synthesizer.d.ts +2 -2
- package/dist/lib/typeChecker/synthesizer.js +100 -68
- package/dist/lib/typeChecker/typeCases.d.ts +1 -1
- package/dist/lib/typeChecker/typeCases.js +3 -2
- package/dist/lib/typeChecker/typeCases.test.js +2 -1
- package/dist/lib/typeChecker/types.d.ts +10 -10
- package/dist/lib/typeChecker/utils.d.ts +7 -2
- package/dist/lib/typeChecker/utils.js +6 -4
- package/dist/lib/types/finalizeBlock.d.ts +19 -0
- package/dist/lib/types/finalizeBlock.js +1 -0
- package/dist/lib/types/guardBlock.d.ts +25 -0
- package/dist/lib/types/guardBlock.js +1 -0
- package/dist/lib/types/markDestructiveRan.d.ts +8 -0
- package/dist/lib/types/markDestructiveRan.js +1 -0
- package/dist/lib/types/parallelBlock.d.ts +5 -0
- package/dist/lib/types.d.ts +7 -1
- package/dist/lib/types.js +3 -0
- package/dist/lib/utils/bodySlots.js +27 -0
- package/dist/lib/utils/bodySlots.test.d.ts +1 -0
- package/dist/lib/utils/bodySlots.test.js +17 -0
- package/dist/lib/utils/node.js +3 -0
- package/dist/scripts/agency.js +117 -59
- package/dist/scripts/generateDiagnosticsDocs.d.ts +1 -0
- package/dist/scripts/generateDiagnosticsDocs.js +28 -0
- package/package.json +8 -2
- package/stdlib/agency/eval.js +33 -6
- package/stdlib/agency/local.js +28 -13
- package/stdlib/agency.agency +143 -30
- package/stdlib/agency.js +1413 -197
- package/stdlib/agent.agency +1 -296
- package/stdlib/agent.js +13 -1160
- package/stdlib/agents/agency.agency +88 -0
- package/stdlib/agents/agency.js +1415 -0
- package/stdlib/agents/coding.agency +67 -0
- package/stdlib/agents/coding.js +633 -0
- package/stdlib/agents/expert.agency +197 -0
- package/stdlib/agents/expert.js +1828 -0
- package/stdlib/agents/planner.agency +176 -0
- package/stdlib/agents/planner.js +2561 -0
- package/stdlib/agents/research.agency +64 -0
- package/stdlib/agents/research.js +744 -0
- package/stdlib/agents/shared.agency +21 -0
- package/stdlib/agents/shared.js +516 -0
- package/stdlib/args.js +8 -2
- package/stdlib/array.js +17 -13
- package/stdlib/auth/keyring.agency +8 -4
- package/stdlib/auth/keyring.js +16 -6
- package/stdlib/auth/oauth.agency +4 -2
- package/stdlib/auth/oauth.js +14 -6
- package/stdlib/calendar.agency +27 -21
- package/stdlib/calendar.js +20 -8
- package/stdlib/capabilities.agency +9 -0
- package/stdlib/capabilities.js +12 -1
- package/stdlib/clipboard.agency +4 -2
- package/stdlib/clipboard.js +17 -4
- package/stdlib/concurrency.js +8 -2
- package/stdlib/data/finance/dbnomics.js +51 -10
- package/stdlib/data/finance/edgar.js +75 -16
- package/stdlib/data/finance/fred.js +75 -14
- package/stdlib/data/finance/gdelt.js +39 -6
- package/stdlib/data/people/littlesis.js +90 -24
- package/stdlib/data/tech/hackernews.js +107 -30
- package/stdlib/data/tech/yc.js +79 -22
- package/stdlib/data/usaspending.js +73 -23
- package/stdlib/data/wikidata.js +108 -22
- package/stdlib/date.js +22 -16
- package/stdlib/docs/cli/agent.md +7 -0
- package/stdlib/docs/cli/run.md +2 -0
- package/stdlib/docs/cli/typecheck.md +3 -1
- package/stdlib/docs/diagnostics/checking.md +154 -0
- package/stdlib/docs/diagnostics/effects.md +167 -0
- package/stdlib/docs/diagnostics/index.md +139 -0
- package/stdlib/docs/diagnostics/match.md +25 -0
- package/stdlib/docs/diagnostics/names.md +113 -0
- package/stdlib/docs/diagnostics/static-init.md +65 -0
- package/stdlib/docs/diagnostics/tools.md +398 -0
- package/stdlib/docs/diagnostics/types-aliases.md +125 -0
- package/stdlib/docs/guide/developer-tools.md +3 -3
- package/stdlib/docs/guide/guards.md +141 -44
- package/stdlib/docs/guide/llm-part-2.md +20 -3
- package/stdlib/docs/guide/partial-results.md +166 -0
- package/stdlib/docs/guide/ts-helpers.md +2 -0
- package/stdlib/fs.agency +24 -12
- package/stdlib/fs.js +80 -52
- package/stdlib/git.agency +109 -24
- package/stdlib/git.js +346 -29
- package/stdlib/http.js +10 -4
- package/stdlib/image.js +8 -2
- package/stdlib/index.agency +67 -4
- package/stdlib/index.js +437 -31
- package/stdlib/llm.js +41 -10
- package/stdlib/markdown.agency +26 -0
- package/stdlib/markdown.js +140 -7
- package/stdlib/math.js +12 -6
- package/stdlib/mcp.js +12 -6
- package/stdlib/memory.agency +14 -12
- package/stdlib/memory.js +64 -23
- package/stdlib/messaging/email.agency +53 -47
- package/stdlib/messaging/email.js +17 -5
- package/stdlib/messaging/imessage.agency +7 -5
- package/stdlib/messaging/imessage.js +11 -3
- package/stdlib/messaging/sms.agency +10 -8
- package/stdlib/messaging/sms.js +11 -3
- package/stdlib/notes/apple.agency +360 -0
- package/stdlib/notes/apple.js +1948 -0
- package/stdlib/object.js +27 -11
- package/stdlib/path.js +14 -8
- package/stdlib/policy.agency +19 -9
- package/stdlib/policy.js +173 -38
- package/stdlib/shell.agency +36 -9
- package/stdlib/shell.js +56 -34
- package/stdlib/skills.agency +65 -24
- package/stdlib/skills.js +479 -47
- package/stdlib/speech.js +15 -4
- package/stdlib/statelog.js +23 -8
- package/stdlib/strategy.js +42 -7
- package/stdlib/syntax.js +12 -6
- package/stdlib/system.js +28 -13
- package/stdlib/tag.js +13 -7
- package/stdlib/thread.agency +20 -69
- package/stdlib/thread.js +155 -132
- package/stdlib/ui/chart.js +18 -7
- package/stdlib/ui/cli.js +27 -8
- package/stdlib/ui/layout.js +71 -20
- package/stdlib/ui/table.js +19 -8
- package/stdlib/ui.js +445 -63
- package/stdlib/validation.js +18 -12
- package/stdlib/weather.js +10 -4
- package/stdlib/web/browser.js +8 -2
- package/stdlib/web/search.js +9 -3
- package/stdlib/wikipedia.js +10 -4
- package/dist/lib/agents/agency-agent/prompts/system.md +0 -4
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
printLocalCatalog,
|
|
4
|
-
localModelsSupported,
|
|
5
|
-
listModelNames,
|
|
6
|
-
} from "std::agency/local"
|
|
1
|
+
import { localModelsSupported, listModelNames } from "std::agency/local"
|
|
7
2
|
import { parseArgs } from "std::args"
|
|
8
3
|
import { map } from "std::array"
|
|
9
4
|
import { today } from "std::date"
|
|
@@ -11,7 +6,6 @@ import { generateImage } from "std::image"
|
|
|
11
6
|
import { applyAgentCwd, setAgentCwd } from "std::index"
|
|
12
7
|
import { listHostedModels, hostedModelInfo, setLlmOptions } from "std::llm"
|
|
13
8
|
import { setMemoryId, disableMemory } from "std::memory"
|
|
14
|
-
import { keys } from "std::object"
|
|
15
9
|
import { basename, dirname } from "std::path"
|
|
16
10
|
import {
|
|
17
11
|
ScopedRuleFields,
|
|
@@ -24,7 +18,6 @@ import {
|
|
|
24
18
|
builtinPolicyNames,
|
|
25
19
|
BUILTIN_POLICIES,
|
|
26
20
|
} from "std::policy"
|
|
27
|
-
import { loadMcpToolsWithStatus, mergeMcpServers, isMcpAvailable, readProjectMcpConfig } from "std::mcp"
|
|
28
21
|
import { exists } from "std::shell"
|
|
29
22
|
import { commandsDir, expandSlash } from "std::skills"
|
|
30
23
|
import { highlight } from "std::syntax"
|
|
@@ -45,19 +38,33 @@ import {
|
|
|
45
38
|
clearScreen,
|
|
46
39
|
clearHistory,
|
|
47
40
|
} from "std::ui/cli"
|
|
48
|
-
import { box, render } from "std::ui/layout"
|
|
49
41
|
|
|
50
|
-
import {
|
|
42
|
+
import { parseAgentArgs } from "./lib/args.agency"
|
|
51
43
|
import { detectAttachments, modalityFilter } from "./lib/attachments.agency"
|
|
44
|
+
import { showCapabilities, editCapabilities } from "./lib/capabilitiesUi.agency"
|
|
45
|
+
import { mergedPalette, projectCommands } from "./lib/commandPalette.agency"
|
|
52
46
|
import {
|
|
53
47
|
POLICY_PATH,
|
|
54
48
|
SESSION_POLICY_PATH,
|
|
55
49
|
HISTORY_PATH,
|
|
56
50
|
ALWAYS_FIELDS,
|
|
57
51
|
} from "./lib/config.agency"
|
|
52
|
+
import { getHeader } from "./lib/header.agency"
|
|
53
|
+
import { generateImageFile } from "./lib/images.agency"
|
|
54
|
+
import { getMcpTools, maybeLoadMcp, mcpSlashCommand } from "./lib/mcp.agency"
|
|
58
55
|
import { filterHostedModels, ModelFilters } from "./lib/modelFilters.agency"
|
|
59
|
-
import {
|
|
60
|
-
|
|
56
|
+
import {
|
|
57
|
+
parseModelFlag,
|
|
58
|
+
parseModelSpec,
|
|
59
|
+
knownProviders,
|
|
60
|
+
missingProviderEnvVar,
|
|
61
|
+
Resolved,
|
|
62
|
+
} from "./lib/models.agency"
|
|
63
|
+
import {
|
|
64
|
+
givePolicyChoice,
|
|
65
|
+
printPolicyHelp,
|
|
66
|
+
getPolicyForAgent,
|
|
67
|
+
} from "./lib/policy.agency"
|
|
61
68
|
import {
|
|
62
69
|
buildLayers,
|
|
63
70
|
resolveAll,
|
|
@@ -72,21 +79,29 @@ import {
|
|
|
72
79
|
availableBackendItems,
|
|
73
80
|
} from "./lib/search.agency"
|
|
74
81
|
import {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
82
|
+
Session,
|
|
83
|
+
switchModel,
|
|
84
|
+
reactToSlotChange,
|
|
85
|
+
modelChoiceItems,
|
|
86
|
+
parseModelsFilters,
|
|
87
|
+
markMemoryEmbeddingsPinned,
|
|
88
|
+
pickModelsForAgent,
|
|
89
|
+
} from "./lib/session.agency"
|
|
81
90
|
import {
|
|
82
|
-
Settings,
|
|
83
91
|
getCapabilitySettings,
|
|
84
92
|
getModelSettings,
|
|
85
|
-
getMcpServers,
|
|
86
93
|
loadSettings,
|
|
87
94
|
saveSettings,
|
|
88
95
|
} from "./lib/settings.agency"
|
|
89
|
-
import {
|
|
96
|
+
import { SLOTS } from "./lib/slots.agency"
|
|
97
|
+
import {
|
|
98
|
+
truncate,
|
|
99
|
+
formatArgs,
|
|
100
|
+
formatToolResponse,
|
|
101
|
+
retryReasonLabel,
|
|
102
|
+
formatTurnFailure,
|
|
103
|
+
} from "./lib/utils.agency"
|
|
104
|
+
import { renderLLMCallResponse } from "./lib/utils/renderLLMCallResponse.agency"
|
|
90
105
|
import {
|
|
91
106
|
configureModels,
|
|
92
107
|
configureLocalModel,
|
|
@@ -106,7 +121,6 @@ import {
|
|
|
106
121
|
overrideFromResolved,
|
|
107
122
|
embeddingsOverride,
|
|
108
123
|
providerEnvMissing,
|
|
109
|
-
demoteProvider,
|
|
110
124
|
} from "./shared.agency"
|
|
111
125
|
import { codeAgent, setCodeOneShot } from "./subagents/code.agency"
|
|
112
126
|
import { explorerAgent } from "./subagents/explorer.agency"
|
|
@@ -126,14 +140,18 @@ import { reviewAgent } from "./subagents/review.agency"
|
|
|
126
140
|
/*
|
|
127
141
|
* Agency Agent — helps users create and modify Agency code.
|
|
128
142
|
*
|
|
129
|
-
* Multi-thread architecture: the
|
|
130
|
-
*
|
|
143
|
+
* Multi-thread architecture: the coordinator (`mainAgent`) runs the
|
|
144
|
+
* user's message with the specialists registered as tools. Each
|
|
145
|
+
* specialist has its own thread / system prompt / tool set, and the
|
|
146
|
+
* coordinator's LLM delegates by calling one as a tool and getting its
|
|
147
|
+
* reply back (call/return — not a flat hop/handoff loop):
|
|
131
148
|
* - `code.agency` — writes, edits, typechecks, runs code
|
|
132
149
|
* - `research.agency` — looks up docs, fetches URLs, browses web
|
|
150
|
+
* - `review.agency` — parse/typecheck feedback on Agency code
|
|
151
|
+
* - `oracle.agency` — read-only critique on a stronger model
|
|
152
|
+
* - `explorer.agency` — read-only broad codebase/docs synthesis
|
|
133
153
|
*
|
|
134
154
|
* The standard library does the heavy lifting:
|
|
135
|
-
* - `std::agent.route` runs the per-turn hop loop, owns the
|
|
136
|
-
* handoff signal, and force-answers when the hop limit is hit.
|
|
137
155
|
* - `std::policy.cliPolicyHandler` provides the interactive
|
|
138
156
|
* approve/reject/always menu and persists "always" decisions to
|
|
139
157
|
* `~/.agency-agent/policy.json`.
|
|
@@ -146,7 +164,7 @@ import { reviewAgent } from "./subagents/review.agency"
|
|
|
146
164
|
* `mainAgent` and printing the reply into the scroll output.
|
|
147
165
|
* 2. Per-effect ALWAYS_FIELDS map for the policy handler's
|
|
148
166
|
* "approve-always-here" option.
|
|
149
|
-
* 3. Wire the
|
|
167
|
+
* 3. Wire the specialists into `mainAgentTools`.
|
|
150
168
|
*
|
|
151
169
|
* Other features:
|
|
152
170
|
* - Doc-as-skills: bundles `docs/site/guide` etc. (copied to
|
|
@@ -159,28 +177,29 @@ import { reviewAgent } from "./subagents/review.agency"
|
|
|
159
177
|
* editing `.agency` files.
|
|
160
178
|
*/
|
|
161
179
|
|
|
162
|
-
// Verbose tool-call tracing. Enabled either by `AGENT_DEBUG=1` (legacy)
|
|
163
|
-
// or by passing `--debug` / `--verbose` on the command line (set in main()).
|
|
164
180
|
let AGENT_DEBUG: boolean = env("AGENT_DEBUG") == "1"
|
|
165
181
|
let VERBOSE: boolean = false
|
|
166
182
|
|
|
167
|
-
// Whether the agent is running in interactive (TTY) mode.
|
|
168
|
-
// before any tool calls happen, so the callbacks below can suppress
|
|
169
|
-
// scroll-output writes when stdout is destined for a Unix pipe.
|
|
183
|
+
// Whether the agent is running in interactive (TTY) mode.
|
|
170
184
|
let _isInteractive: boolean = true
|
|
171
185
|
|
|
172
|
-
// The `--policy` argument (a built-in name or a policy-file path),
|
|
173
|
-
//
|
|
174
|
-
// consumed there to override the saved policy for this run only.
|
|
186
|
+
// The `--policy` argument (a built-in name or a policy-file path),
|
|
187
|
+
// or "" when absent
|
|
175
188
|
let _policyArg: string = ""
|
|
176
189
|
|
|
177
|
-
// Live tool-call tracing. Always shown in the interactive REPL
|
|
178
|
-
// one-shot mode it's suppressed
|
|
179
|
-
//
|
|
190
|
+
// Live tool-call tracing. Always shown in the interactive REPL, but in
|
|
191
|
+
// one-shot mode it's suppressed unless the user opts in
|
|
192
|
+
// with `--verbose` / `--debug` (AGENT_DEBUG). A function, not a static
|
|
193
|
+
// const: it reads globals that `main()` sets after module init, so the
|
|
194
|
+
// value must be computed per call.
|
|
180
195
|
def _showTraces(): boolean {
|
|
181
196
|
return _isInteractive || VERBOSE || AGENT_DEBUG
|
|
182
197
|
}
|
|
183
198
|
|
|
199
|
+
/**************************
|
|
200
|
+
**** CALLBACKS / HOOKS ****
|
|
201
|
+
***************************/
|
|
202
|
+
|
|
184
203
|
callback("onToolCallStart") as data {
|
|
185
204
|
if (_showTraces()) {
|
|
186
205
|
pushMessage(color.yellow("⏺ ${data.toolName}(${formatArgs(data.args)})"))
|
|
@@ -206,6 +225,21 @@ callback("onLLMCallStart") as data {
|
|
|
206
225
|
}
|
|
207
226
|
|
|
208
227
|
callback("onLLMCallEnd") as data {
|
|
228
|
+
if (data.result.hostedToolResults) {
|
|
229
|
+
for (result in data.result.hostedToolResults) {
|
|
230
|
+
match(result.tool) {
|
|
231
|
+
"web_search" => {
|
|
232
|
+
const queries = map(result.queries, \q -> "\"${q}\"").join(", ")
|
|
233
|
+
// there's also result.sources and result.citations
|
|
234
|
+
pushMessage(
|
|
235
|
+
color.yellow(
|
|
236
|
+
"⏺ web_search(provider: '${result.provider}', queries: [${queries}])",
|
|
237
|
+
),
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
209
243
|
if (AGENT_DEBUG || VERBOSE) {
|
|
210
244
|
pushMessage(
|
|
211
245
|
color.green.dim("💬 [${data.model}] ${renderLLMCallResponse(data.result)}"),
|
|
@@ -213,42 +247,6 @@ callback("onLLMCallEnd") as data {
|
|
|
213
247
|
}
|
|
214
248
|
}
|
|
215
249
|
|
|
216
|
-
// Map a transient LLM retry reason (an `LLMRetryReason` from the runtime
|
|
217
|
-
// retry layer) to a short, human-facing label. `connectionLost` is the
|
|
218
|
-
// lost-internet / closed-the-laptop case; the rest cover provider hiccups
|
|
219
|
-
// and our own per-call timeout.
|
|
220
|
-
export def retryReasonLabel(reason: string): string {
|
|
221
|
-
if (reason == "connectionLost") {
|
|
222
|
-
return "Connection lost"
|
|
223
|
-
}
|
|
224
|
-
if (reason == "timeout") {
|
|
225
|
-
return "Request timed out"
|
|
226
|
-
}
|
|
227
|
-
if (reason == "streamInterrupted") {
|
|
228
|
-
return "Connection interrupted"
|
|
229
|
-
}
|
|
230
|
-
if (reason == "rateLimit") {
|
|
231
|
-
return "Rate limited"
|
|
232
|
-
}
|
|
233
|
-
if (reason == "overloaded") {
|
|
234
|
-
return "Model overloaded"
|
|
235
|
-
}
|
|
236
|
-
if (reason == "serverError") {
|
|
237
|
-
return "Server error"
|
|
238
|
-
}
|
|
239
|
-
if (reason == "invalidStructuredOutput") {
|
|
240
|
-
return "Output failed validation"
|
|
241
|
-
}
|
|
242
|
-
return reason
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// A transient LLM failure (lost connection, timeout, server hiccup) the
|
|
246
|
-
// runtime is about to retry. Surface a dim, one-line heads-up so the user
|
|
247
|
-
// understands the pause instead of staring at a frozen prompt — this is the
|
|
248
|
-
// "lost internet / closed the laptop" experience the abort taxonomy made
|
|
249
|
-
// distinguishable. Gated like the other traces so piped one-shot output
|
|
250
|
-
// stays clean. `attempt` is the 1-based retry number; `maxRetries` is the
|
|
251
|
-
// configured retry budget.
|
|
252
250
|
callback("onLLMRetry") as data {
|
|
253
251
|
if (_showTraces()) {
|
|
254
252
|
pushMessage(
|
|
@@ -259,58 +257,11 @@ callback("onLLMRetry") as data {
|
|
|
259
257
|
}
|
|
260
258
|
}
|
|
261
259
|
|
|
262
|
-
// Turn a terminal (retries-exhausted) LLM failure into a short, actionable
|
|
263
|
-
// message. The runtime's exhausted-retry error text embeds the reason in
|
|
264
|
-
// parens, e.g. "LLM call failed after 3 attempts (connectionLost): ...". We
|
|
265
|
-
// recognize the transport reasons that mean "the network or model dropped"
|
|
266
|
-
// and give concrete advice; anything else echoes the raw error so nothing
|
|
267
|
-
// is hidden.
|
|
268
|
-
export def formatTurnFailure(errText: string): string {
|
|
269
|
-
if (errText.includes("(connectionLost)") || errText.includes("(streamInterrupted)")) {
|
|
270
|
-
return "⚠ Lost the connection to the model and couldn't recover after retrying.\nCheck your internet connection and try again."
|
|
271
|
-
}
|
|
272
|
-
if (errText.includes("(timeout)")) {
|
|
273
|
-
return "⚠ The model took too long to respond — the request timed out after retrying.\nTry again, perhaps with a smaller request."
|
|
274
|
-
}
|
|
275
|
-
if (errText.includes("(rateLimit)") || errText.includes("(overloaded)") || errText.includes("(serverError)")) {
|
|
276
|
-
return "⚠ The model provider is busy and the request failed after retrying.\nWait a moment and try again."
|
|
277
|
-
}
|
|
278
|
-
return "⚠ The request failed: ${errText}"
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
type ToolCallData = {
|
|
282
|
-
name: string;
|
|
283
|
-
arguments: any
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
type LLMResponse = {
|
|
287
|
-
output?: string;
|
|
288
|
-
toolCalls: ToolCallData[];
|
|
289
|
-
usage: { inputTokens: number; outputTokens: number; totalTokens: number; cachedInputTokens: number };
|
|
290
|
-
cost: { inputCost: number; outputCost: number; cachedInputCost: number; totalCost: number; currency: string };
|
|
291
|
-
model: string
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
def renderLLMCallResponse(data: LLMResponse): string {
|
|
295
|
-
let response = []
|
|
296
|
-
if (data.output != "" && data.output != null && data.output != undefined) {
|
|
297
|
-
response.push(data.output)
|
|
298
|
-
}
|
|
299
|
-
if (data.toolCalls.length > 0) {
|
|
300
|
-
for (toolCall in data.toolCalls) {
|
|
301
|
-
response.push(`${toolCall.name}(${formatArgs(toolCall.arguments)})`)
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
return response.join("\n")
|
|
305
|
-
}
|
|
306
|
-
|
|
307
260
|
def loadAgentsMd(dir: string): string {
|
|
308
261
|
"""
|
|
309
262
|
Read `AGENTS.md` from `dir` if it exists and return it wrapped in a
|
|
310
263
|
<project_context> block ready to splice into a system prompt. Returns
|
|
311
|
-
an empty string if there is no AGENTS.md or it cannot be read.
|
|
312
|
-
read is auto-approved — the user implicitly opted in to scanning the
|
|
313
|
-
project directory by running the agent there.
|
|
264
|
+
an empty string if there is no AGENTS.md or it cannot be read.
|
|
314
265
|
"""
|
|
315
266
|
if (!exists("AGENTS.md", dir)) {
|
|
316
267
|
return ""
|
|
@@ -319,518 +270,166 @@ def loadAgentsMd(dir: string): string {
|
|
|
319
270
|
if (isFailure(result)) {
|
|
320
271
|
return ""
|
|
321
272
|
}
|
|
322
|
-
return "
|
|
273
|
+
return """
|
|
274
|
+
|
|
275
|
+
<project_context>
|
|
276
|
+
${result.value}
|
|
277
|
+
</project_context>
|
|
278
|
+
"""
|
|
323
279
|
}
|
|
324
280
|
|
|
325
281
|
// Per-turn grounding context. Captured at startup and re-used so the
|
|
326
|
-
// LLM doesn't have to re-ground each turn.
|
|
282
|
+
// LLM doesn't have to re-ground each turn.
|
|
327
283
|
let _context: string = ""
|
|
328
284
|
|
|
329
|
-
//
|
|
330
|
-
//
|
|
331
|
-
//
|
|
332
|
-
// user implicitly opted in by running the agent in this directory.
|
|
333
|
-
static const projectCommands = commandsDir("${cwd()}/.claude/commands") with approve
|
|
334
|
-
|
|
335
|
-
def builtinPalette(): Record<string, string> {
|
|
336
|
-
return {
|
|
337
|
-
"/exit": "Exit the agent",
|
|
338
|
-
"/clear": "Clear the conversation transcript",
|
|
339
|
-
"/clear-history": "Clear the input history (recall + saved file)",
|
|
340
|
-
"/cost": "Show cumulative LLM cost and tokens",
|
|
341
|
-
"/model": "Switch the model (bare to pick, or slot=model)",
|
|
342
|
-
"/models": "List / filter the hosted model catalog",
|
|
343
|
-
"/local": "Switch to a local model",
|
|
344
|
-
"/search": "Choose the web search backend (hosted / Tavily / Brave / off)",
|
|
345
|
-
"/settings": "View and change capability settings for the current model",
|
|
346
|
-
"/mcp": "List configured MCP servers and their tools",
|
|
347
|
-
"/paste": "Multi-line paste mode (Ctrl+D submits, Ctrl+C cancels)",
|
|
348
|
-
"/help": "Show available slash commands"
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// Built-ins win over file commands so a user `/clear.md` can't shadow
|
|
353
|
-
// the built-in `/clear`. Build file entries first, then overlay builtins.
|
|
354
|
-
def mergedPalette(): Record<string, string> {
|
|
355
|
-
let out: Record<string, string> = {}
|
|
356
|
-
for (cmd in projectCommands) {
|
|
357
|
-
let label = cmd.description
|
|
358
|
-
if (cmd.argHint != "") {
|
|
359
|
-
label = "${cmd.description} ${cmd.argHint}"
|
|
360
|
-
}
|
|
361
|
-
out["/${cmd.name}"] = label
|
|
362
|
-
}
|
|
363
|
-
const builtins = builtinPalette()
|
|
364
|
-
for (key in builtins) {
|
|
365
|
-
out[key] = builtins[key]
|
|
366
|
-
}
|
|
367
|
-
return out
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
// In-session model selection state. One mutable cell holding the current
|
|
371
|
-
// Session (the pin + per-slot overrides chosen via /model this run). switchModel
|
|
372
|
-
// is pure over this value — it copies, never mutates — so it stays testable.
|
|
373
|
-
export type Session = {
|
|
374
|
-
pin: string;
|
|
375
|
-
slots: Record<string, string>
|
|
376
|
-
}
|
|
377
|
-
|
|
285
|
+
// In-session model selection state (the pin + per-slot overrides chosen via
|
|
286
|
+
// `/model` this run). `switchModel` is pure over this value — it copies,
|
|
287
|
+
// never mutates — so the REPL folds its return back in here.
|
|
378
288
|
let _session: Session = {
|
|
379
289
|
pin: "",
|
|
380
290
|
slots: {}
|
|
381
291
|
}
|
|
382
292
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
ctx: Ctx,
|
|
392
|
-
before: Record<string, Resolved>,
|
|
393
|
-
): { session: Session; resolved: Record<string, Resolved>; changes: SlotChange[] } {
|
|
394
|
-
// The caller builds ctx from currentProvider(), which returns the
|
|
395
|
-
// PROMOTED name on openai sessions. priceDefault and the built-in
|
|
396
|
-
// tables key by public provider names, so demote before resolving —
|
|
397
|
-
// otherwise a switch that lets any slot fall through to the built-in
|
|
398
|
-
// defaults fails with "openai-responses has no built-in defaults".
|
|
399
|
-
const ctx2: Ctx = {
|
|
400
|
-
provider: demoteProvider(ctx.provider),
|
|
401
|
-
price: ctx.price
|
|
402
|
-
}
|
|
403
|
-
let nextSlots: Record<string, string> = {}
|
|
404
|
-
for (key in Object.keys(prior.slots)) {
|
|
405
|
-
nextSlots[key] = prior.slots[key]
|
|
406
|
-
}
|
|
407
|
-
let next: Session = {
|
|
408
|
-
pin: prior.pin,
|
|
409
|
-
slots: nextSlots
|
|
410
|
-
}
|
|
411
|
-
const parsed = parseModelFlag(spec)
|
|
412
|
-
if (parsed.slot != "" && !SLOTS.includes(parsed.slot)) {
|
|
413
|
-
pushMessage(
|
|
414
|
-
color.red("Unknown model slot '${parsed.slot}'. Valid slots: ${SLOTS.join(", ")}."),
|
|
415
|
-
)
|
|
416
|
-
return {
|
|
417
|
-
session: prior,
|
|
418
|
-
resolved: before,
|
|
419
|
-
changes: []
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
if (parsed.slot == "embedding" && !embeddingSpecHasProvider(parsed.model)) {
|
|
423
|
-
// The embedding slot exists to point AWAY from the chat provider, so
|
|
424
|
-
// the provider can never be inferred. Require the explicit form.
|
|
425
|
-
pushMessage(
|
|
426
|
-
color.red(
|
|
427
|
-
"Embedding models need the provider/model form, e.g. embedding=openai/text-embedding-3-small.",
|
|
428
|
-
),
|
|
429
|
-
)
|
|
430
|
-
return {
|
|
431
|
-
session: prior,
|
|
432
|
-
resolved: before,
|
|
433
|
-
changes: []
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
if (parsed.slot == "") {
|
|
437
|
-
next.pin = parsed.model
|
|
438
|
-
} else {
|
|
439
|
-
next.slots[parsed.slot] = parsed.model
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
// A saved provider (settings.model.provider) means a provider is established,
|
|
443
|
-
// so a unified provider/model spec should not be split during resolution.
|
|
444
|
-
const settings = getModelSettings(loadSettings())
|
|
445
|
-
const providerEstablished = settings.provider != null && settings.provider != ""
|
|
446
|
-
const layers = buildLayers(
|
|
447
|
-
next.pin,
|
|
448
|
-
next.slots,
|
|
449
|
-
"",
|
|
450
|
-
{},
|
|
451
|
-
settings,
|
|
452
|
-
knownProviders(),
|
|
453
|
-
providerEstablished,
|
|
293
|
+
def searchSlashCommand(): boolean {
|
|
294
|
+
const opts = availableBackendItems()
|
|
295
|
+
const choice = chooseOption(
|
|
296
|
+
"Web search backend",
|
|
297
|
+
"Pick how this agent searches the web. Current: ${getSearchBackend()}",
|
|
298
|
+
opts,
|
|
299
|
+
false,
|
|
300
|
+
true,
|
|
454
301
|
)
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
pushMessage(
|
|
458
|
-
return {
|
|
459
|
-
session: prior,
|
|
460
|
-
resolved: before,
|
|
461
|
-
changes: []
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
const resolved = promoteAll(all.value)
|
|
465
|
-
applyResolved(resolved)
|
|
466
|
-
return {
|
|
467
|
-
session: next,
|
|
468
|
-
resolved: resolved,
|
|
469
|
-
changes: diffResolved(before, resolved)
|
|
302
|
+
if (choice != "" && choice != null && choice != undefined) {
|
|
303
|
+
setSearchBackend(choice)
|
|
304
|
+
pushMessage("Web search backend set to: ${choice}")
|
|
470
305
|
}
|
|
306
|
+
return true
|
|
471
307
|
}
|
|
472
308
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
return true
|
|
495
|
-
}
|
|
496
|
-
if (change.slot == "embedding") {
|
|
497
|
-
const override = overrideFromResolved(change.after)
|
|
498
|
-
if (override != null) {
|
|
499
|
-
const missing = missingProviderEnvVar(override.provider)
|
|
500
|
-
if (missing != "") {
|
|
501
|
-
pushMessage(
|
|
502
|
-
color.yellow(
|
|
503
|
-
"Warning: the embedding slot needs ${missing}, which is not set. The assignment takes effect at next agent start, and memory falls back to the default embedding behavior until it is exported.",
|
|
504
|
-
),
|
|
505
|
-
)
|
|
506
|
-
return true
|
|
507
|
-
}
|
|
508
|
-
// The healthy path must not be silent: enableMemory already ran,
|
|
509
|
-
// so the user would otherwise believe embeddings switched now.
|
|
309
|
+
def costSlashCommand(): boolean {
|
|
310
|
+
// Derive BOTH the header total and the per-model breakdown from the
|
|
311
|
+
// same process-wide accumulator (getModelCosts), so the `Cost:` /
|
|
312
|
+
// `Tokens:` lines and the rows below them can never visibly disagree.
|
|
313
|
+
// (getCost()/getTokens() read the per-branch accumulator, a different
|
|
314
|
+
// scope — adjacent in the output, they could in principle diverge.)
|
|
315
|
+
// Rows are sorted by cost descending by getModelCosts(); a subagent
|
|
316
|
+
// on a pricier model (e.g. the oracle on opus-4.8) shows up here.
|
|
317
|
+
const byModel = getModelCosts()
|
|
318
|
+
let totalCost = 0.0
|
|
319
|
+
let totalTokens = 0
|
|
320
|
+
for (m in byModel) {
|
|
321
|
+
totalCost = totalCost + m.cost
|
|
322
|
+
totalTokens = totalTokens + m.inputTokens + m.outputTokens
|
|
323
|
+
}
|
|
324
|
+
pushMessage("Cost: $${totalCost.toFixed(4)}")
|
|
325
|
+
pushMessage("Tokens: ${totalTokens}")
|
|
326
|
+
if (byModel.length > 0) {
|
|
327
|
+
pushMessage("")
|
|
328
|
+
pushMessage("By model:")
|
|
329
|
+
for (m in byModel) {
|
|
510
330
|
pushMessage(
|
|
511
|
-
|
|
512
|
-
"Embedding slot set: memory embeddings use ${override.provider}/${override.model} at next agent start.",
|
|
513
|
-
),
|
|
331
|
+
" ${m.model} ↑${m.inputTokens} ↓${m.outputTokens} $${m.cost.toFixed(4)}",
|
|
514
332
|
)
|
|
515
|
-
return true
|
|
516
333
|
}
|
|
517
334
|
}
|
|
518
|
-
return
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
// ChoiceItem list for the model pickers: the hosted catalog, filtered, each
|
|
522
|
-
// labeled with provider + input price for an informed pick.
|
|
523
|
-
export def modelChoiceItems(filters: ModelFilters): ChoiceItem[] {
|
|
524
|
-
let items: ChoiceItem[] = []
|
|
525
|
-
for (model in filterHostedModels(listHostedModels(), filters)) {
|
|
526
|
-
const label = "${model.name} (${model.provider}, $${model.inputCost}/1M in)"
|
|
527
|
-
items.push({
|
|
528
|
-
key: model.name,
|
|
529
|
-
label: label
|
|
530
|
-
})
|
|
531
|
-
}
|
|
532
|
-
return items
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
// Parse a `/models [provider]` command line into filters. Bare `/models` (or a
|
|
536
|
-
// trailing-space-only arg) yields no filters; `/models <provider>` narrows to
|
|
537
|
-
// one provider. Extracted from the handler so it is unit-testable.
|
|
538
|
-
export def parseModelsFilters(cmd: string): ModelFilters {
|
|
539
|
-
const prefix = "/models "
|
|
540
|
-
if (!cmd.startsWith(prefix)) {
|
|
541
|
-
return {}
|
|
542
|
-
}
|
|
543
|
-
const arg = cmd.substring(prefix.length).trim()
|
|
544
|
-
if (arg == "") {
|
|
545
|
-
return {}
|
|
546
|
-
}
|
|
547
|
-
return {
|
|
548
|
-
provider: arg
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
/** Human label for a capability source layer. Interprets the source
|
|
553
|
-
strings resolveCapabilities emits (capabilities.agency). */
|
|
554
|
-
def capabilitySourceLabel(source: string): string {
|
|
555
|
-
if (source == "default") {
|
|
556
|
-
return "built-in default"
|
|
557
|
-
}
|
|
558
|
-
if (source == "provider") {
|
|
559
|
-
return "provider default: ${getCapabilityProvider()}"
|
|
560
|
-
}
|
|
561
|
-
if (source == "model") {
|
|
562
|
-
return "built-in for ${getCapabilityModelKey()}"
|
|
563
|
-
}
|
|
564
|
-
if (source == "user-global") {
|
|
565
|
-
return "your global override"
|
|
566
|
-
}
|
|
567
|
-
if (source == "user-provider") {
|
|
568
|
-
return "your override for ${getCapabilityProvider()}"
|
|
569
|
-
}
|
|
570
|
-
return "your override for ${getCapabilityModelKey()}"
|
|
335
|
+
return true
|
|
571
336
|
}
|
|
572
337
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
const values = resolved.values
|
|
578
|
-
const sources = resolved.sources
|
|
579
|
-
if (fieldKey == "prompt") {
|
|
580
|
-
return "${values.prompt} (${capabilitySourceLabel(sources.prompt)})"
|
|
581
|
-
}
|
|
582
|
-
if (fieldKey == "summarize") {
|
|
583
|
-
return "${values.summarize} (${capabilitySourceLabel(sources.summarize)})"
|
|
338
|
+
def modelSlashCommand(): boolean {
|
|
339
|
+
let spec = ""
|
|
340
|
+
if (trimmed != "/model") {
|
|
341
|
+
spec = trimmed.substring(7).trim()
|
|
584
342
|
}
|
|
585
|
-
if (
|
|
586
|
-
|
|
343
|
+
if (spec == "") {
|
|
344
|
+
pushMessage("Current models:")
|
|
345
|
+
const current = getResolvedSlots()
|
|
346
|
+
for (slot in Object.keys(current)) {
|
|
347
|
+
const resolved = current[slot]
|
|
348
|
+
if (resolved != null) {
|
|
349
|
+
pushMessage(" ${slot}: ${resolved.model} (${resolved.via})")
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
const items = modelChoiceItems({})
|
|
353
|
+
const choice = chooseOption(
|
|
354
|
+
"Model",
|
|
355
|
+
"Pick a model, or type one (or slot=model, e.g. reasoning=claude-opus-4-8). Empty to cancel.",
|
|
356
|
+
items,
|
|
357
|
+
allowFreeText: true,
|
|
358
|
+
allowCancel: true,
|
|
359
|
+
)
|
|
360
|
+
if (choice == "" || choice == null) {
|
|
361
|
+
return true
|
|
362
|
+
}
|
|
363
|
+
spec = choice
|
|
587
364
|
}
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
365
|
+
const ctx: Ctx = {
|
|
366
|
+
provider: currentProvider(),
|
|
367
|
+
price: "standard"
|
|
591
368
|
}
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
def showCapabilities() {
|
|
597
|
-
pushMessage("Capabilities for ${getCapabilityModelKey()} (${getCapabilityProvider()}):")
|
|
598
|
-
for (field in CAPABILITY_FIELDS) {
|
|
599
|
-
pushMessage(" ${field.label} ${capabilityRow(field.key)}")
|
|
369
|
+
const out = switchModel(_session, spec, ctx, getResolvedSlots())
|
|
370
|
+
_session = out.session
|
|
371
|
+
for (change in out.changes) {
|
|
372
|
+
reactToSlotChange(change)
|
|
600
373
|
}
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
374
|
+
pushMessage("Model set:")
|
|
375
|
+
for (slot in Object.keys(out.resolved)) {
|
|
376
|
+
const resolved = out.resolved[slot]
|
|
377
|
+
if (resolved != null) {
|
|
378
|
+
pushMessage(color.dim(" ${slot}: ${resolved.model} (${resolved.via})"))
|
|
379
|
+
}
|
|
607
380
|
}
|
|
608
|
-
return
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
/** Persist one override patch at a scope, then re-resolve. */
|
|
612
|
-
def saveCapabilityOverride(scopeKey: string, patch: CapabilityPatch) {
|
|
613
|
-
let settings = loadSettings()
|
|
614
|
-
settings.capabilities = mergeCapabilityOverride(
|
|
615
|
-
getCapabilitySettings(settings),
|
|
616
|
-
scopeKey,
|
|
617
|
-
scopeTargetName(scopeKey),
|
|
618
|
-
patch,
|
|
619
|
-
)
|
|
620
|
-
saveSettings(settings)
|
|
621
|
-
refreshCapabilities(getCapabilityModelKey(), getCapabilityProvider())
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
/** Remove one field override at a scope, then re-resolve. */
|
|
625
|
-
def removeCapabilityOverride(scopeKey: string, fieldKey: string) {
|
|
626
|
-
let settings = loadSettings()
|
|
627
|
-
settings.capabilities = removeCapabilityField(
|
|
628
|
-
getCapabilitySettings(settings),
|
|
629
|
-
scopeKey,
|
|
630
|
-
scopeTargetName(scopeKey),
|
|
631
|
-
fieldKey,
|
|
632
|
-
)
|
|
633
|
-
saveSettings(settings)
|
|
634
|
-
refreshCapabilities(getCapabilityModelKey(), getCapabilityProvider())
|
|
381
|
+
return true
|
|
635
382
|
}
|
|
636
383
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
})
|
|
644
|
-
scopeItems.push({
|
|
645
|
-
key: "provider",
|
|
646
|
-
label: "this provider (${getCapabilityProvider()})"
|
|
647
|
-
})
|
|
648
|
-
scopeItems.push({
|
|
649
|
-
key: "global",
|
|
650
|
-
label: "all models"
|
|
651
|
-
})
|
|
652
|
-
const scopeKey = chooseOption("Scope", "Where should this apply?", scopeItems, allowCancel: true)
|
|
653
|
-
if (scopeKey == null) {
|
|
654
|
-
return ""
|
|
384
|
+
def modelsSlashCommand(trimmed: string): boolean {
|
|
385
|
+
const filters = parseModelsFilters(trimmed)
|
|
386
|
+
const models = filterHostedModels(listHostedModels(), filters)
|
|
387
|
+
if (models.length == 0) {
|
|
388
|
+
pushMessage("No models match.")
|
|
389
|
+
return true
|
|
655
390
|
}
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
resolved value falls back to a built-in layer, which is always numeric
|
|
661
|
-
for known providers, so live-apply covers removal too. */
|
|
662
|
-
def applyLiveMaxTokens() {
|
|
663
|
-
const capValue = getCapabilities().maxTokens
|
|
664
|
-
if (capValue != null) {
|
|
665
|
-
setLlmOptions({ maxTokens: capValue })
|
|
391
|
+
for (model in models) {
|
|
392
|
+
pushMessage(
|
|
393
|
+
" ${model.name} (${model.provider}, $${model.inputCost}/1M in, ${model.contextWindow} ctx)",
|
|
394
|
+
)
|
|
666
395
|
}
|
|
396
|
+
return true
|
|
667
397
|
}
|
|
668
398
|
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
if (trimmed == "") {
|
|
678
|
-
return -1
|
|
679
|
-
}
|
|
680
|
-
for (ch in trimmed.split("")) {
|
|
681
|
-
if (!digits.includes(ch)) {
|
|
682
|
-
return -1
|
|
683
|
-
}
|
|
399
|
+
def localSlashCommand(): boolean {
|
|
400
|
+
if (!localModelsSupported()) {
|
|
401
|
+
pushMessage(
|
|
402
|
+
color.yellow(
|
|
403
|
+
"Local models need smoltalk-llama-cpp. Install: ! npm i -g smoltalk-llama-cpp",
|
|
404
|
+
),
|
|
405
|
+
)
|
|
406
|
+
return true
|
|
684
407
|
}
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
let fieldItems: ChoiceItem[] = []
|
|
691
|
-
for (field in CAPABILITY_FIELDS) {
|
|
692
|
-
fieldItems.push({
|
|
693
|
-
key: field.key,
|
|
694
|
-
label: "${field.label} — ${field.description}"
|
|
408
|
+
let items: ChoiceItem[] = []
|
|
409
|
+
for (modelName in listModelNames()) {
|
|
410
|
+
items.push({
|
|
411
|
+
key: modelName.name,
|
|
412
|
+
label: modelName.name
|
|
695
413
|
})
|
|
696
414
|
}
|
|
697
|
-
const
|
|
698
|
-
"
|
|
699
|
-
"Pick a
|
|
700
|
-
|
|
415
|
+
const choice = chooseOption(
|
|
416
|
+
"Local model",
|
|
417
|
+
"Pick a local model, or type an hf: URI / .gguf path (downloads on first use):",
|
|
418
|
+
items,
|
|
419
|
+
allowFreeText: true,
|
|
701
420
|
allowCancel: true,
|
|
702
421
|
)
|
|
703
|
-
if (
|
|
704
|
-
return
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
let patch: CapabilityPatch = {}
|
|
708
|
-
if (fieldKey == "prompt") {
|
|
709
|
-
let promptItems: ChoiceItem[] = []
|
|
710
|
-
promptItems.push({
|
|
711
|
-
key: "large",
|
|
712
|
-
label: "large — full coordinator prompt"
|
|
713
|
-
})
|
|
714
|
-
promptItems.push({
|
|
715
|
-
key: "small",
|
|
716
|
-
label: "small — compact prompt for small-context models"
|
|
717
|
-
})
|
|
718
|
-
const picked = chooseOption("prompt", "System prompt size.", promptItems, allowCancel: true)
|
|
719
|
-
if (picked == "" || picked == null) {
|
|
720
|
-
return
|
|
721
|
-
}
|
|
722
|
-
patch = {
|
|
723
|
-
prompt: picked
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
if (fieldKey == "summarize" || fieldKey == "memory") {
|
|
727
|
-
let onOffItems: ChoiceItem[] = []
|
|
728
|
-
onOffItems.push({
|
|
729
|
-
key: "on",
|
|
730
|
-
label: "on"
|
|
731
|
-
})
|
|
732
|
-
onOffItems.push({
|
|
733
|
-
key: "off",
|
|
734
|
-
label: "off"
|
|
735
|
-
})
|
|
736
|
-
const picked = chooseOption(fieldKey, "Enable ${fieldKey}?", onOffItems, allowCancel: true)
|
|
737
|
-
if (picked == "" || picked == null) {
|
|
738
|
-
return
|
|
739
|
-
}
|
|
740
|
-
if (fieldKey == "summarize") {
|
|
741
|
-
patch = {
|
|
742
|
-
summarize: picked == "on"
|
|
743
|
-
}
|
|
744
|
-
} else {
|
|
745
|
-
patch = {
|
|
746
|
-
memory: picked == "on"
|
|
747
|
-
}
|
|
748
|
-
if (picked == "on") {
|
|
749
|
-
const status = embeddingKeyStatus(getResolvedSlots())
|
|
750
|
-
if (status.state == "key-missing") {
|
|
751
|
-
pushMessage(
|
|
752
|
-
color.red(
|
|
753
|
-
"Cannot enable memory: the embedding slot needs ${status.varName}, which is not set. Export it, or point the embedding slot elsewhere via /model.",
|
|
754
|
-
),
|
|
755
|
-
)
|
|
756
|
-
return
|
|
757
|
-
}
|
|
758
|
-
if (status.state == "no-override" && providerLacksEmbeddings(getCapabilityProvider())) {
|
|
759
|
-
pushMessage(
|
|
760
|
-
color.dim(
|
|
761
|
-
"Note: ${getCapabilityProvider()} has no embeddings endpoint, so Tier-2 semantic recall stays off. Point the embedding slot at a provider that has one, e.g. /model embedding=openai/text-embedding-3-small.",
|
|
762
|
-
),
|
|
763
|
-
)
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
if (fieldKey == "maxTokens") {
|
|
769
|
-
let capItems: ChoiceItem[] = []
|
|
770
|
-
capItems.push({
|
|
771
|
-
key: "reset",
|
|
772
|
-
label: "reset — remove my override, use the default"
|
|
773
|
-
})
|
|
774
|
-
const picked = chooseOption(
|
|
775
|
-
"maxTokens",
|
|
776
|
-
"Default output-token cap. Type a number, or pick reset.",
|
|
777
|
-
capItems,
|
|
778
|
-
allowFreeText: true,
|
|
779
|
-
allowCancel: true,
|
|
780
|
-
)
|
|
781
|
-
if (picked == "" || picked == null) {
|
|
782
|
-
return
|
|
783
|
-
}
|
|
784
|
-
if (picked == "reset") {
|
|
785
|
-
const scopeKey = pickOverrideScope()
|
|
786
|
-
if (scopeKey == "") {
|
|
787
|
-
return
|
|
788
|
-
}
|
|
789
|
-
removeCapabilityOverride(scopeKey, "maxTokens")
|
|
790
|
-
applyLiveMaxTokens()
|
|
791
|
-
pushMessage("Override removed.")
|
|
792
|
-
showCapabilities()
|
|
793
|
-
return
|
|
794
|
-
}
|
|
795
|
-
const parsed = parsePositiveInt(picked)
|
|
796
|
-
if (parsed <= 0) {
|
|
797
|
-
pushMessage(color.red("Not a positive number: ${picked}"))
|
|
798
|
-
return
|
|
799
|
-
}
|
|
800
|
-
patch = {
|
|
801
|
-
maxTokens: parsed
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
const scopeKey = pickOverrideScope()
|
|
806
|
-
if (scopeKey == "") {
|
|
807
|
-
return
|
|
808
|
-
}
|
|
809
|
-
saveCapabilityOverride(scopeKey, patch)
|
|
810
|
-
|
|
811
|
-
// Live-apply what can apply now; explain what can't.
|
|
812
|
-
if (fieldKey == "memory") {
|
|
813
|
-
if (getCapabilities().memory) {
|
|
814
|
-
// The ON direction cannot live-apply. enableAgentMemory's
|
|
815
|
-
// `with approve` cannot override the outer policy handler that
|
|
816
|
-
// startInteractive installed, so the memory-dir fs interrupts
|
|
817
|
-
// would surface as policy prompts mid-flow, or get denied and
|
|
818
|
-
// half-apply the toggle. The OFF direction stays live below:
|
|
819
|
-
// disableMemory raises no fs interrupts and already runs in-REPL
|
|
820
|
-
// from reactToSlotChange.
|
|
821
|
-
pushMessage(color.dim("Memory turns on at next agent start (enabling mid-session would raise policy prompts)."))
|
|
822
|
-
} else {
|
|
823
|
-
disableMemory() with approve
|
|
824
|
-
}
|
|
825
|
-
}
|
|
826
|
-
if (fieldKey == "maxTokens") {
|
|
827
|
-
applyLiveMaxTokens()
|
|
422
|
+
if (choice == "" || choice == null) {
|
|
423
|
+
return true
|
|
828
424
|
}
|
|
829
|
-
|
|
830
|
-
|
|
425
|
+
const before = getResolvedSlots()
|
|
426
|
+
configureLocalModel(choice)
|
|
427
|
+
configureSearch(true)
|
|
428
|
+
for (change in diffResolved(before, getResolvedSlots())) {
|
|
429
|
+
reactToSlotChange(change)
|
|
831
430
|
}
|
|
832
|
-
pushMessage("
|
|
833
|
-
|
|
431
|
+
pushMessage("Switched to local model: ${choice}")
|
|
432
|
+
return true
|
|
834
433
|
}
|
|
835
434
|
|
|
836
435
|
// Slash-command + user-message dispatcher invoked by `repl()` for
|
|
@@ -842,618 +441,121 @@ def _runTurn(msg: string): boolean {
|
|
|
842
441
|
// raw `msg` so the LLM sees the exact text the user typed when
|
|
843
442
|
// nothing matches.
|
|
844
443
|
const trimmed = msg.trim()
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
if (trimmed == "/clear") {
|
|
852
|
-
clearMessages()
|
|
853
|
-
return true
|
|
854
|
-
}
|
|
855
|
-
if (trimmed == "/clear-history") {
|
|
856
|
-
clearHistory()
|
|
857
|
-
pushMessage("Input history cleared.")
|
|
858
|
-
return true
|
|
859
|
-
}
|
|
860
|
-
if (trimmed == "/help") {
|
|
861
|
-
pushMessage(
|
|
862
|
-
"Commands: /exit, /clear, /clear-history, /cost, /model, /models, /local, /search, /settings, /mcp, /paste, /help",
|
|
863
|
-
)
|
|
864
|
-
return true
|
|
865
|
-
}
|
|
866
|
-
if (trimmed == "/settings") {
|
|
867
|
-
showCapabilities()
|
|
868
|
-
editCapabilities()
|
|
869
|
-
return true
|
|
870
|
-
}
|
|
871
|
-
if (trimmed == "/mcp") {
|
|
872
|
-
if (!isMcpAvailable()) {
|
|
873
|
-
pushMessage(color.yellow("MCP is not installed. Run: npm install @agency-lang/mcp"))
|
|
874
|
-
return true
|
|
875
|
-
}
|
|
876
|
-
const merged = mergeMcpServers(getMcpServers(loadSettings()), readProjectMcpConfig(cwd()))
|
|
877
|
-
if (keys(merged).length == 0) {
|
|
878
|
-
pushMessage(color.dim("No MCP servers configured. Add an mcpServers block to agency.json or settings.json."))
|
|
444
|
+
return match(trimmed) {
|
|
445
|
+
"" => true
|
|
446
|
+
"/exit" => false
|
|
447
|
+
"/quit" => false
|
|
448
|
+
"/clear" => {
|
|
449
|
+
clearMessages()
|
|
879
450
|
return true
|
|
880
451
|
}
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
// config but absent from the status map was never loaded (e.g. /mcp run
|
|
885
|
-
// before startup finished) — show it as unknown rather than connected.
|
|
886
|
-
let status = mcpServerStatus[name]
|
|
887
|
-
if (status == null) {
|
|
888
|
-
status = "not loaded"
|
|
889
|
-
}
|
|
890
|
-
pushMessage(" ${name} — ${status}")
|
|
891
|
-
}
|
|
892
|
-
pushMessage(color.dim("Loaded ${mcpTools.length} MCP tool(s) this session."))
|
|
893
|
-
return true
|
|
894
|
-
}
|
|
895
|
-
if (trimmed == "/search") {
|
|
896
|
-
const opts = availableBackendItems()
|
|
897
|
-
const choice = chooseOption(
|
|
898
|
-
"Web search backend",
|
|
899
|
-
"Pick how this agent searches the web. Current: ${getSearchBackend()}",
|
|
900
|
-
opts,
|
|
901
|
-
false,
|
|
902
|
-
true,
|
|
903
|
-
)
|
|
904
|
-
if (choice != "" && choice != null && choice != undefined) {
|
|
905
|
-
setSearchBackend(choice)
|
|
906
|
-
pushMessage("Web search backend set to: ${choice}")
|
|
907
|
-
}
|
|
908
|
-
return true
|
|
909
|
-
}
|
|
910
|
-
if (trimmed == "/cost") {
|
|
911
|
-
// Derive BOTH the header total and the per-model breakdown from the
|
|
912
|
-
// same process-wide accumulator (getModelCosts), so the `Cost:` /
|
|
913
|
-
// `Tokens:` lines and the rows below them can never visibly disagree.
|
|
914
|
-
// (getCost()/getTokens() read the per-branch accumulator, a different
|
|
915
|
-
// scope — adjacent in the output, they could in principle diverge.)
|
|
916
|
-
// Rows are sorted by cost descending by getModelCosts(); a subagent
|
|
917
|
-
// on a pricier model (e.g. the oracle on opus-4.8) shows up here.
|
|
918
|
-
const byModel = getModelCosts()
|
|
919
|
-
let totalCost = 0.0
|
|
920
|
-
let totalTokens = 0
|
|
921
|
-
for (m in byModel) {
|
|
922
|
-
totalCost = totalCost + m.cost
|
|
923
|
-
totalTokens = totalTokens + m.inputTokens + m.outputTokens
|
|
924
|
-
}
|
|
925
|
-
pushMessage("Cost: $${totalCost.toFixed(4)}")
|
|
926
|
-
pushMessage("Tokens: ${totalTokens}")
|
|
927
|
-
if (byModel.length > 0) {
|
|
928
|
-
pushMessage("")
|
|
929
|
-
pushMessage("By model:")
|
|
930
|
-
for (m in byModel) {
|
|
931
|
-
pushMessage(
|
|
932
|
-
" ${m.model} ↑${m.inputTokens} ↓${m.outputTokens} $${m.cost.toFixed(4)}",
|
|
933
|
-
)
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
return true
|
|
937
|
-
}
|
|
938
|
-
if (trimmed == "/model" || trimmed.startsWith("/model ")) {
|
|
939
|
-
let spec = ""
|
|
940
|
-
if (trimmed != "/model") {
|
|
941
|
-
spec = trimmed.substring(7).trim()
|
|
942
|
-
}
|
|
943
|
-
if (spec == "") {
|
|
944
|
-
pushMessage("Current models:")
|
|
945
|
-
const current = getResolvedSlots()
|
|
946
|
-
for (slot in Object.keys(current)) {
|
|
947
|
-
const resolved = current[slot]
|
|
948
|
-
if (resolved != null) {
|
|
949
|
-
pushMessage(" ${slot}: ${resolved.model} (${resolved.via})")
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
const items = modelChoiceItems({})
|
|
953
|
-
const choice = chooseOption(
|
|
954
|
-
"Model",
|
|
955
|
-
"Pick a model, or type one (or slot=model, e.g. reasoning=claude-opus-4-8). Empty to cancel.",
|
|
956
|
-
items,
|
|
957
|
-
allowFreeText: true,
|
|
958
|
-
allowCancel: true,
|
|
959
|
-
)
|
|
960
|
-
if (choice == "" || choice == null) {
|
|
961
|
-
return true
|
|
962
|
-
}
|
|
963
|
-
spec = choice
|
|
964
|
-
}
|
|
965
|
-
const ctx: Ctx = {
|
|
966
|
-
provider: currentProvider(),
|
|
967
|
-
price: "standard"
|
|
968
|
-
}
|
|
969
|
-
const out = switchModel(_session, spec, ctx, getResolvedSlots())
|
|
970
|
-
_session = out.session
|
|
971
|
-
for (change in out.changes) {
|
|
972
|
-
reactToSlotChange(change)
|
|
973
|
-
}
|
|
974
|
-
pushMessage("Model set:")
|
|
975
|
-
for (slot in Object.keys(out.resolved)) {
|
|
976
|
-
const resolved = out.resolved[slot]
|
|
977
|
-
if (resolved != null) {
|
|
978
|
-
pushMessage(color.dim(" ${slot}: ${resolved.model} (${resolved.via})"))
|
|
979
|
-
}
|
|
980
|
-
}
|
|
981
|
-
return true
|
|
982
|
-
}
|
|
983
|
-
if (trimmed == "/models" || trimmed.startsWith("/models ")) {
|
|
984
|
-
const filters = parseModelsFilters(trimmed)
|
|
985
|
-
const models = filterHostedModels(listHostedModels(), filters)
|
|
986
|
-
if (models.length == 0) {
|
|
987
|
-
pushMessage("No models match.")
|
|
452
|
+
"/clear-history" => {
|
|
453
|
+
clearHistory()
|
|
454
|
+
pushMessage("Input history cleared.")
|
|
988
455
|
return true
|
|
989
456
|
}
|
|
990
|
-
|
|
457
|
+
"/help" => {
|
|
991
458
|
pushMessage(
|
|
992
|
-
"
|
|
993
|
-
)
|
|
994
|
-
}
|
|
995
|
-
return true
|
|
996
|
-
}
|
|
997
|
-
if (trimmed == "/local") {
|
|
998
|
-
if (!localModelsSupported()) {
|
|
999
|
-
pushMessage(
|
|
1000
|
-
color.yellow(
|
|
1001
|
-
"Local models need smoltalk-llama-cpp. Install: ! npm i -g smoltalk-llama-cpp",
|
|
1002
|
-
),
|
|
459
|
+
"Commands: /exit, /clear, /clear-history, /cost, /model, /models, /local, /search, /settings, /mcp, /paste, /help",
|
|
1003
460
|
)
|
|
1004
461
|
return true
|
|
1005
462
|
}
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
key: modelName.name,
|
|
1010
|
-
label: modelName.name
|
|
1011
|
-
})
|
|
1012
|
-
}
|
|
1013
|
-
const choice = chooseOption(
|
|
1014
|
-
"Local model",
|
|
1015
|
-
"Pick a local model, or type an hf: URI / .gguf path (downloads on first use):",
|
|
1016
|
-
items,
|
|
1017
|
-
allowFreeText: true,
|
|
1018
|
-
allowCancel: true,
|
|
1019
|
-
)
|
|
1020
|
-
if (choice == "" || choice == null) {
|
|
463
|
+
"/settings" => {
|
|
464
|
+
showCapabilities()
|
|
465
|
+
editCapabilities()
|
|
1021
466
|
return true
|
|
1022
467
|
}
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
// Failure object. Aborts/interrupts are NOT caught here — `try` re-throws
|
|
1038
|
-
// them so an Esc-cancel still reaches the policy handler.
|
|
1039
|
-
const result = try agentReply(msg)
|
|
1040
|
-
if (result is success(reply)) {
|
|
1041
|
-
if (reply != "" && reply != null && reply != undefined) {
|
|
1042
|
-
pushMessage(highlight("${reply}\n", language: "markdown"))
|
|
1043
|
-
} else {
|
|
1044
|
-
pushMessage(color.red("No reply generated."))
|
|
468
|
+
"/mcp" => mcpSlashCommand()
|
|
469
|
+
"/search" => searchSlashCommand()
|
|
470
|
+
"/cost" => costSlashCommand()
|
|
471
|
+
"/model" => modelSlashCommand()
|
|
472
|
+
"/models" => modelsSlashCommand(trimmed)
|
|
473
|
+
"/local" => localSlashCommand()
|
|
474
|
+
_ => {
|
|
475
|
+
if (trimmed.startsWith("/model ")) {
|
|
476
|
+
return modelSlashCommand()
|
|
477
|
+
}
|
|
478
|
+
if (trimmed.startsWith("/models ")) {
|
|
479
|
+
return modelsSlashCommand(trimmed)
|
|
480
|
+
}
|
|
481
|
+
return renderUserTurn(trimmed, msg)
|
|
1045
482
|
}
|
|
1046
|
-
} else if (result is failure(f)) {
|
|
1047
|
-
pushMessage(color.red(formatTurnFailure("${f.error}")))
|
|
1048
483
|
}
|
|
1049
|
-
return true
|
|
1050
484
|
}
|
|
1051
485
|
|
|
1052
486
|
let first = true
|
|
1053
487
|
|
|
1054
|
-
// Valid `--agent` targets. Empty / "main" routes through the coordinator
|
|
1055
|
-
// the rest route the starting prompt's first turn directly to that
|
|
1056
|
-
// subagent. Used to validate the flag and to dispatch in `agentReplyVia`.
|
|
488
|
+
// Valid `--agent` targets. Empty / "main" routes through the coordinator.
|
|
1057
489
|
static const START_AGENTS = ["main", "code", "research", "oracle", "explorer", "review"]
|
|
1058
|
-
|
|
1059
|
-
static const
|
|
1060
|
-
You are the top-level coordinator of an Agency-language assistant. You
|
|
1061
|
-
receive every user message and decide how to respond.
|
|
1062
|
-
|
|
1063
|
-
You have five subagent tools (each running in its own isolated
|
|
1064
|
-
context) and one direct tool:
|
|
1065
|
-
|
|
1066
|
-
- `codeAgent(userMsg)` — anything that touches code or the filesystem:
|
|
1067
|
-
reading, writing, editing, typechecking, running shell commands,
|
|
1068
|
-
and answering Agency syntax / CLI questions. Use this for any task
|
|
1069
|
-
that involves inspecting or modifying source code.
|
|
1070
|
-
- `researchAgent(userMsg)` — web search, URL fetches, Wikipedia,
|
|
1071
|
-
external API lookups, summarizing external content.
|
|
1072
|
-
- `reviewAgent(userMsg)` — reviews Agency code for syntax and type
|
|
1073
|
-
errors. Call this after `codeAgent` produces non-trivial new or
|
|
1074
|
-
modified Agency code, passing the code to be reviewed.
|
|
1075
|
-
- `oracleAgent(userMsg)` — a read-only senior reviewer on a stronger
|
|
1076
|
-
reasoning model. Read the **Oracle** section below — you are
|
|
1077
|
-
expected to use this tool **frequently**.
|
|
1078
|
-
- `explorerAgent(userMsg)` — a read-only researcher that produces
|
|
1079
|
-
broad, synthesizing answers about the codebase or bundled docs.
|
|
1080
|
-
Read the **Explorer** section below.
|
|
1081
|
-
- `generateImageFile(prompt, path, size, images)` — generate an image
|
|
1082
|
-
from a text prompt (or modify existing images by passing their paths
|
|
1083
|
-
in `images`) and save it to `path`. Call it directly whenever the
|
|
1084
|
-
user asks you to create, draw, edit, or restyle an image — do NOT
|
|
1085
|
-
route image generation to `codeAgent`.
|
|
1086
|
-
|
|
1087
|
-
**Answer directly (no tool call) when** the message is conversational,
|
|
1088
|
-
a clarifying question, or something you can answer from context alone.
|
|
1089
|
-
|
|
1090
|
-
**Delegate when** the message clearly needs one of the subagents'
|
|
1091
|
-
capabilities. Pick one based on what the message actually needs —
|
|
1092
|
-
don't pre-emptively call multiple subagents.
|
|
1093
|
-
|
|
1094
|
-
## Picking between oracle, explorer, and code agent
|
|
1095
|
-
|
|
1096
|
-
These three overlap on "reads the codebase," but they're for
|
|
1097
|
-
different jobs. Pick by **the shape of the answer the user wants**:
|
|
1098
|
-
|
|
1099
|
-
- **oracle** → sharp verdict on a specific plan, diff, or bug.
|
|
1100
|
-
Output: short, decisive ("this plan won't work because X").
|
|
1101
|
-
- **explorer** → broad synthesis from reading many files.
|
|
1102
|
-
Output: structured overview ("Agency's five main features
|
|
1103
|
-
are..."). Use for "summarize", "tour", "what are the main",
|
|
1104
|
-
"how does X work across...".
|
|
1105
|
-
- **codeAgent** → targeted action: edit, run, typecheck, or
|
|
1106
|
-
answer a focused Agency-syntax question. Output: the edit
|
|
1107
|
-
itself, or a concise factual reply.
|
|
1108
|
-
|
|
1109
|
-
Default: if the user asks a broad/synthesizing question,
|
|
1110
|
-
`explorerAgent` beats `codeAgent` every time. The code agent is
|
|
1111
|
-
optimized for terse action and will under-read on broad asks.
|
|
1112
|
-
|
|
1113
|
-
Subagents return summary text. Surface that result to the user (in
|
|
1114
|
-
your own words if you're combining multiple results), formatted as
|
|
1115
|
-
Markdown.
|
|
1116
|
-
|
|
1117
|
-
## Oracle
|
|
1118
|
-
|
|
1119
|
-
The oracle is the most powerful tool you have. Use it **FREQUENTLY**.
|
|
1120
|
-
It is a read-only senior reviewer running on a stronger reasoning
|
|
1121
|
-
model than you. It can read the codebase and the Agency docs but
|
|
1122
|
-
cannot write, edit, or run anything. Its job is to **think hard so
|
|
1123
|
-
you don't have to guess**.
|
|
1124
|
-
|
|
1125
|
-
Call the oracle:
|
|
1126
|
-
|
|
1127
|
-
- **Before** dispatching `codeAgent` for any non-trivial task — ask
|
|
1128
|
-
the oracle to sanity-check the plan, find existing code that
|
|
1129
|
-
already solves the problem, or suggest a simpler approach.
|
|
1130
|
-
- **After** `codeAgent` produces a non-trivial diff — ask the oracle
|
|
1131
|
-
to review the work for correctness, missed edge cases, and
|
|
1132
|
-
better alternatives.
|
|
1133
|
-
- When `codeAgent` reports it's stuck on a bug after one or two
|
|
1134
|
-
attempts — ask the oracle what's actually going on.
|
|
1135
|
-
- When the user proposes a plan or approach — ask the oracle
|
|
1136
|
-
whether the plan is sound, **before** you act on it. If the
|
|
1137
|
-
oracle finds a flaw, surface that to the user *before* taking
|
|
1138
|
-
any action.
|
|
1139
|
-
- Whenever you're tempted to guess about the codebase ("I think
|
|
1140
|
-
there's probably already a helper for X") — ask the oracle to
|
|
1141
|
-
look.
|
|
1142
|
-
|
|
1143
|
-
**Tell the user when you're consulting the oracle.** Say something
|
|
1144
|
-
like "Let me ask the oracle to review this plan before we start"
|
|
1145
|
-
or "I'll have the oracle look for an existing implementation." The
|
|
1146
|
-
visibility is half the value — the user benefits from knowing a
|
|
1147
|
-
second opinion is being sought.
|
|
1148
|
-
|
|
1149
|
-
Pass the oracle a self-contained question with full context. It
|
|
1150
|
-
does not see your conversation. Include the user's request, the
|
|
1151
|
-
plan or diff under review, relevant file paths, and the specific
|
|
1152
|
-
question you want answered.
|
|
1153
|
-
|
|
1154
|
-
Default bias: when in doubt, **consult the oracle**. The cost of
|
|
1155
|
-
asking is small; the cost of executing a flawed plan is large.
|
|
1156
|
-
|
|
1157
|
-
## Explorer
|
|
1158
|
-
|
|
1159
|
-
The explorer is your go-to for **broad, synthesizing questions**
|
|
1160
|
-
about the codebase or the bundled Agency docs. It is read-only and
|
|
1161
|
-
runs on a stronger reasoning model with extended thinking. Its job
|
|
1162
|
-
is **coverage** — it reads widely and returns a structured synthesis.
|
|
1163
|
-
|
|
1164
|
-
Call the explorer when the user asks:
|
|
1165
|
-
|
|
1166
|
-
- "Summarize the Agency docs" / "What are the main features?"
|
|
1167
|
-
- "Give me a tour of `lib/X/`" / "What's in this module?"
|
|
1168
|
-
- "How does X work across the codebase?"
|
|
1169
|
-
- "Walk me through the compilation pipeline"
|
|
1170
|
-
- Anything where a good answer requires reading 5+ files and
|
|
1171
|
-
organizing the findings by theme.
|
|
1172
|
-
|
|
1173
|
-
Do NOT use the explorer for:
|
|
1174
|
-
- Specific factual lookups ("what does function X return?") — use
|
|
1175
|
-
`codeAgent`.
|
|
1176
|
-
- Plan critique or bug diagnosis — use `oracleAgent`.
|
|
1177
|
-
- External / web research — use `researchAgent`.
|
|
1178
|
-
|
|
1179
|
-
**Tell the user when you're consulting the explorer.** "Let me have
|
|
1180
|
-
the explorer go through the docs and put together an overview." Like
|
|
1181
|
-
with the oracle, the visibility is part of the value — the user
|
|
1182
|
-
benefits from knowing breadth is being applied.
|
|
1183
|
-
|
|
1184
|
-
Pass the explorer a self-contained question with explicit scope
|
|
1185
|
-
("all of `docs/site/guide/`", "the `lib/parsers/` module"). It does
|
|
1186
|
-
not see your conversation. Be clear about the level of detail
|
|
1187
|
-
expected.
|
|
1188
|
-
|
|
1189
|
-
## Style
|
|
1190
|
-
|
|
1191
|
-
Never start a response by calling the user's question or idea good,
|
|
1192
|
-
great, fascinating, profound, excellent, or perfect. Skip flattery
|
|
1193
|
-
and respond directly. Don't pad replies with "happy to help",
|
|
1194
|
-
"certainly", or trailing summaries the user can read in the diff.
|
|
1195
|
-
|
|
1196
|
-
**Use ASCII diagrams when they clarify.** For control flow, state
|
|
1197
|
-
machines, pipelines, module relationships, or any "how do the parts
|
|
1198
|
-
fit together" answer, draw a small ASCII diagram in a fenced
|
|
1199
|
-
```text block. Boxes, arrows, and trees beat paragraphs for
|
|
1200
|
-
structural explanations:
|
|
1201
|
-
|
|
1202
|
-
```text
|
|
1203
|
-
parse → SymbolTable.build → preprocess → TypeScriptBuilder → printTs
|
|
1204
|
-
```
|
|
1205
|
-
|
|
1206
|
-
Keep diagrams small. Skip them where prose or code is clearer —
|
|
1207
|
-
diagrams earn their space by showing **relationships** or **flow**.
|
|
1208
|
-
|
|
1209
|
-
## Be proactive
|
|
1210
|
-
|
|
1211
|
-
When the user asks you to look at, debug, or change a file or some code,
|
|
1212
|
-
**delegate to `codeAgent` to do it** — don't ask the user to paste a file
|
|
1213
|
-
or describe code a subagent could read. The code agent has `read`, `glob`,
|
|
1214
|
-
and `ls` and resolves relative paths against the user's working directory
|
|
1215
|
-
automatically, so a bare filename like `foo.agency` is enough. Only ask
|
|
1216
|
-
the user for information you genuinely cannot obtain through a subagent.
|
|
1217
|
-
|
|
1218
|
-
## Answer before action
|
|
1219
|
-
|
|
1220
|
-
When the user asks a question, asks for an opinion, or asks how to
|
|
1221
|
-
plan or approach something, **answer the question first**. Don't
|
|
1222
|
-
jump straight into delegating to a subagent or making tool calls
|
|
1223
|
-
unless the user has clearly asked for an action ("do X", "fix Y",
|
|
1224
|
-
"build Z"). If the user is exploring or thinking out loud, think
|
|
1225
|
-
with them — don't sprint to implementation.
|
|
1226
|
-
"""
|
|
1227
|
-
|
|
1228
|
-
// Compact coordinator prompt for small-context models, ~350 tokens
|
|
1229
|
-
// instead of ~3,400. Selected when the capability profile says
|
|
1230
|
-
// prompt: "small". An 8K-context model loses 40% of its window to the
|
|
1231
|
-
// large prompt before the user types anything. The phrase "compact
|
|
1232
|
-
// coordinator" is a deliberate marker that verification greps for in
|
|
1233
|
-
// statelogs.
|
|
1234
|
-
static const MAIN_PROMPT_SMALL = """
|
|
1235
|
-
You are the compact coordinator of an Agency-language assistant. Decide
|
|
1236
|
-
how to answer each user message.
|
|
1237
|
-
|
|
1238
|
-
Tools (each runs in its own context; pass a self-contained message):
|
|
1239
|
-
- `codeAgent(userMsg)` — anything touching code or files: read, write,
|
|
1240
|
-
edit, run, typecheck. Also Agency syntax and CLI questions.
|
|
1241
|
-
- `researchAgent(userMsg)` — web search, URL fetches, external facts.
|
|
1242
|
-
- `reviewAgent(userMsg)` — check non-trivial new Agency code for
|
|
1243
|
-
syntax and type errors; pass the code to review.
|
|
1244
|
-
- `oracleAgent(userMsg)` — deep reasoning on a hard question; include
|
|
1245
|
-
all needed context in the message.
|
|
1246
|
-
- `explorerAgent(userMsg)` — broad read-only codebase/docs questions.
|
|
1247
|
-
- `generateImageFile(prompt, path, size, images)` — create or edit an
|
|
1248
|
-
image; do not route image work to codeAgent.
|
|
1249
|
-
|
|
1250
|
-
Routing rules:
|
|
1251
|
-
- Simple chat, greetings, quick factual answers: reply directly, no
|
|
1252
|
-
tools.
|
|
1253
|
-
- Anything code- or file-related: codeAgent. Current/external info:
|
|
1254
|
-
researchAgent. Broad "summarize/tour/how does X work" questions:
|
|
1255
|
-
explorerAgent.
|
|
1256
|
-
- Surface tool results to the user concisely; do not re-run a tool the
|
|
1257
|
-
user did not ask to re-run.
|
|
1258
|
-
|
|
1259
|
-
Style: plain, direct answers in Markdown. No preamble. Keep replies
|
|
1260
|
-
short unless the task demands detail.
|
|
1261
|
-
"""
|
|
1262
|
-
|
|
1263
|
-
// Exported for tests: agent-cwd resolution of edit inputs is a spec
|
|
1264
|
-
// invariant with no other observable (the deterministic image client
|
|
1265
|
-
// ignores its images argument). generateImage accepts path / URL /
|
|
1266
|
-
// data-URI strings, so only local paths get agent-cwd resolution —
|
|
1267
|
-
// running a URL through applyAgentCwd would mangle it into
|
|
1268
|
-
// "<agentCwd>/https:/…".
|
|
1269
|
-
export def resolveEditInputs(images: string[]): string[] {
|
|
1270
|
-
return map(images) as inputPath {
|
|
1271
|
-
if (inputPath.startsWith("http://") || inputPath.startsWith("https://") || inputPath.startsWith("data:")) {
|
|
1272
|
-
return inputPath
|
|
1273
|
-
}
|
|
1274
|
-
return applyAgentCwd(inputPath)
|
|
1275
|
-
}
|
|
1276
|
-
}
|
|
1277
|
-
|
|
1278
|
-
// Direct image generation for the coordinator. Saves to disk and
|
|
1279
|
-
// returns the path — never base64, which would flood the LLM context.
|
|
1280
|
-
// The write rides the std::writeBinary interrupt gate (writes are not
|
|
1281
|
-
// auto-approved by policy) and generation cost accrues to the agent's
|
|
1282
|
-
// cost tracking like any llm() call.
|
|
1283
|
-
export def generateImageFile(
|
|
1284
|
-
prompt: string,
|
|
1285
|
-
path: string,
|
|
1286
|
-
size: string = "",
|
|
1287
|
-
images: string[] = [],
|
|
1288
|
-
): string {
|
|
1289
|
-
"""
|
|
1290
|
-
Generate an image from a text prompt and save it to `path`. To MODIFY
|
|
1291
|
-
existing images (edit / variation), pass their paths in `images`.
|
|
1292
|
-
Returns the saved path on success. Use this when the user asks to
|
|
1293
|
-
create, draw, edit, or restyle an image.
|
|
1294
|
-
|
|
1295
|
-
@param prompt - What to generate, or how to modify the input images.
|
|
1296
|
-
@param path - Where to save the resulting image (e.g. "diagram.png").
|
|
1297
|
-
@param size - Optional size like "1024x1024".
|
|
1298
|
-
@param images - Optional input image paths to edit / vary.
|
|
1299
|
-
"""
|
|
1300
|
-
// LLM-supplied paths resolve against the agent cwd, like every other
|
|
1301
|
-
// agent file tool.
|
|
1302
|
-
const inputs = resolveEditInputs(images)
|
|
1303
|
-
const generated = generateImage(prompt, size: size, images: inputs)
|
|
1304
|
-
if (isFailure(generated)) {
|
|
1305
|
-
return "Image generation failed: ${generated.error}"
|
|
1306
|
-
}
|
|
1307
|
-
// writeBinary rejects an absolute filename when dir is set, so split
|
|
1308
|
-
// path into (dir, name); useAgentCwd resolves a relative dir against
|
|
1309
|
-
// the agent cwd (absolute dirs pass through untouched).
|
|
1310
|
-
const written = writeBinary(
|
|
1311
|
-
basename(path),
|
|
1312
|
-
generated.value.base64,
|
|
1313
|
-
dirname(path),
|
|
1314
|
-
useAgentCwd: true,
|
|
1315
|
-
)
|
|
1316
|
-
if (isFailure(written)) {
|
|
1317
|
-
return "Generated the image, but saving to ${path} failed: ${written.error}"
|
|
1318
|
-
}
|
|
1319
|
-
// Show the model what it made (generate -> view -> refine). The loop
|
|
1320
|
-
// inlines the bytes when it builds the injected user message; the
|
|
1321
|
-
// path resolves the same way the write did.
|
|
1322
|
-
attachToReply(image(applyAgentCwd(path)))
|
|
1323
|
-
return "Saved image to ${path}"
|
|
1324
|
-
}
|
|
490
|
+
static const MAIN_PROMPT_LARGE = read("prompts/main-large.md") with approve
|
|
491
|
+
static const MAIN_PROMPT_SMALL = read("prompts/main-small.md") with approve
|
|
1325
492
|
|
|
1326
493
|
static const mainAgentTools = [
|
|
1327
|
-
researchAgent
|
|
1328
|
-
codeAgent
|
|
1329
|
-
reviewAgent
|
|
1330
|
-
oracleAgent
|
|
1331
|
-
explorerAgent
|
|
494
|
+
researchAgent,
|
|
495
|
+
codeAgent,
|
|
496
|
+
reviewAgent,
|
|
497
|
+
oracleAgent,
|
|
498
|
+
explorerAgent,
|
|
1332
499
|
generateImageFile,
|
|
1333
500
|
]
|
|
1334
501
|
|
|
1335
|
-
// MCP tools are appended to the coordinator's tools. ORDERING CONTRACT:
|
|
1336
|
-
// maybeLoadMcp (session start, in main()) must run before mainAgent's first
|
|
1337
|
-
// turn, or MCP tools silently vanish.
|
|
1338
|
-
let mcpTools: any[] = []
|
|
1339
|
-
// Per-server load outcome ("connected" / "unavailable"), for /mcp.
|
|
1340
|
-
let mcpServerStatus: Record<string, string> = {}
|
|
1341
|
-
|
|
1342
|
-
export def setMcpTools(tools: any[]) {
|
|
1343
|
-
mcpTools = tools
|
|
1344
|
-
}
|
|
1345
|
-
|
|
1346
|
-
def setMcpServerStatus(status: Record<string, string>) {
|
|
1347
|
-
mcpServerStatus = status
|
|
1348
|
-
}
|
|
1349
|
-
|
|
1350
|
-
// MCP OAuth notification. The @agency-lang/mcp package opens the browser itself
|
|
1351
|
-
// (macOS-only today); we only print the URL so the user can complete auth.
|
|
1352
|
-
def onMcpOAuth(data: any) {
|
|
1353
|
-
print(color.yellow("MCP authorization needed — opening browser. If it does not open, visit:\n${data.authUrl}"))
|
|
1354
|
-
}
|
|
1355
|
-
|
|
1356
|
-
/** Load MCP tools from the project agency.json merged with the global settings
|
|
1357
|
-
and hand them to the coordinator. No-op when @agency-lang/mcp is not installed
|
|
1358
|
-
or no servers are configured. loadMcpTools warns-and-skips internally on any
|
|
1359
|
-
per-server failure, so it always returns an array. */
|
|
1360
|
-
def maybeLoadMcp(settings: Settings) {
|
|
1361
|
-
if (!isMcpAvailable()) {
|
|
1362
|
-
return
|
|
1363
|
-
}
|
|
1364
|
-
const merged = mergeMcpServers(getMcpServers(settings), readProjectMcpConfig(cwd()))
|
|
1365
|
-
if (keys(merged).length == 0) {
|
|
1366
|
-
return
|
|
1367
|
-
}
|
|
1368
|
-
// Always returns a result (per-server failures warn-and-skip internally), so
|
|
1369
|
-
// there is no Failure branch. The status map drives /mcp.
|
|
1370
|
-
const loaded = loadMcpToolsWithStatus(merged, onMcpOAuth)
|
|
1371
|
-
setMcpTools(loaded.tools)
|
|
1372
|
-
setMcpServerStatus(loaded.status)
|
|
1373
|
-
}
|
|
1374
|
-
|
|
1375
502
|
/** Enable memory when the capability profile allows it. When the user
|
|
1376
503
|
pointed the embedding slot at a provider, embeddings run there. A
|
|
1377
504
|
missing API key degrades to the default embedding behavior with one
|
|
1378
|
-
notice. It never blocks startup.
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
// re-deriving from the slots: a keyless override took the fallback path,
|
|
1382
|
-
// so memory runs on DERIVED embeddings and a main-provider change still
|
|
1383
|
-
// shifts the embedding space. Exported mutator so tests can drive the
|
|
1384
|
-
// guard directly.
|
|
1385
|
-
let _memoryEmbeddingsPinned = false
|
|
1386
|
-
|
|
1387
|
-
export def markMemoryEmbeddingsPinned(pinned: boolean) {
|
|
1388
|
-
_memoryEmbeddingsPinned = pinned
|
|
1389
|
-
}
|
|
1390
|
-
|
|
505
|
+
notice. It never blocks startup. The `_memoryEmbeddingsPinned` guard that
|
|
506
|
+
`reactToSlotChange` consults lives in `./lib/session.agency`; we set it
|
|
507
|
+
here via `markMemoryEmbeddingsPinned`. */
|
|
1391
508
|
def maybeEnableMemory() {
|
|
1392
509
|
const status = embeddingKeyStatus(getResolvedSlots())
|
|
1393
510
|
const plan = memoryEnablePlan(getCapabilities().memory, status)
|
|
1394
511
|
markMemoryEmbeddingsPinned(plan == "enable-override")
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
512
|
+
return match(plan) {
|
|
513
|
+
"skip" => null
|
|
514
|
+
"enable-override" => {
|
|
515
|
+
enableAgentMemory(status.override)
|
|
516
|
+
return null
|
|
517
|
+
}
|
|
518
|
+
"enable-fallback" => {
|
|
519
|
+
print(
|
|
520
|
+
color.dim(
|
|
521
|
+
"Memory: the embedding slot needs ${status.varName}, which is not set. Falling back to the default embedding behavior.",
|
|
522
|
+
),
|
|
523
|
+
)
|
|
524
|
+
enableAgentMemory()
|
|
525
|
+
return null
|
|
526
|
+
}
|
|
527
|
+
_ => {
|
|
528
|
+
enableAgentMemory()
|
|
529
|
+
return null
|
|
530
|
+
}
|
|
1408
531
|
}
|
|
1409
|
-
enableAgentMemory()
|
|
1410
532
|
}
|
|
1411
533
|
|
|
1412
534
|
def mainAgent(prompt: string | (string | Attachment)[]): string {
|
|
1413
|
-
const doSummarize = getCapabilities().summarize
|
|
1414
|
-
thread(label: "main",
|
|
535
|
+
// const doSummarize = getCapabilities().summarize
|
|
536
|
+
thread(label: "main", session: "main") {
|
|
1415
537
|
setMemoryId("main")
|
|
1416
538
|
if (first) {
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
if (getCapabilities().prompt == "small") {
|
|
1422
|
-
sysPrompt = MAIN_PROMPT_SMALL
|
|
539
|
+
const promptSize = getCapabilities().prompt
|
|
540
|
+
const sysPrompt = match(promptSize) {
|
|
541
|
+
"small" => MAIN_PROMPT_SMALL catch ""
|
|
542
|
+
_ => MAIN_PROMPT_LARGE catch ""
|
|
1423
543
|
}
|
|
1424
544
|
systemMessage("${sysPrompt}${_context}")
|
|
1425
545
|
first = false
|
|
1426
546
|
}
|
|
1427
|
-
const result = llm(
|
|
547
|
+
const result = llm(
|
|
548
|
+
prompt,
|
|
549
|
+
{
|
|
1428
550
|
memory: true,
|
|
1429
|
-
tools: [...mainAgentTools, ...
|
|
1430
|
-
}
|
|
551
|
+
tools: [...mainAgentTools, ...getMcpTools()]
|
|
552
|
+
},
|
|
553
|
+
)
|
|
1431
554
|
}
|
|
1432
555
|
return result
|
|
1433
556
|
}
|
|
1434
557
|
|
|
1435
|
-
|
|
1436
|
-
// `mainAgent`, which routes as usual; any subagent name routes the turn
|
|
1437
|
-
// directly to that subagent. All subagents share `(userMsg, allowHandoff)`
|
|
1438
|
-
// and the seed turn never hands off (allowHandoff: false). Used by both
|
|
1439
|
-
// the one-shot path and the interactive seed turn.
|
|
1440
|
-
export def agentReplyVia(target: string, userMsg: string): string {
|
|
1441
|
-
const expanded = expandSlash(userMsg, projectCommands)
|
|
1442
|
-
if (target == "code") {
|
|
1443
|
-
return codeAgent(expanded, false)
|
|
1444
|
-
}
|
|
1445
|
-
if (target == "research") {
|
|
1446
|
-
return researchAgent(expanded, false)
|
|
1447
|
-
}
|
|
1448
|
-
if (target == "oracle") {
|
|
1449
|
-
return oracleAgent(expanded, false)
|
|
1450
|
-
}
|
|
1451
|
-
if (target == "explorer") {
|
|
1452
|
-
return explorerAgent(expanded, false)
|
|
1453
|
-
}
|
|
1454
|
-
if (target == "review") {
|
|
1455
|
-
return reviewAgent(expanded, false)
|
|
1456
|
-
}
|
|
558
|
+
def checkForAttachments(expanded: string): Attachment[] {
|
|
1457
559
|
// Auto-attach image/PDF files the user referenced (drag-drop or typed
|
|
1458
560
|
// path), filtered to what the resolved model accepts. Never silent:
|
|
1459
561
|
// every attach and every skip prints a visible line.
|
|
@@ -1464,11 +566,7 @@ export def agentReplyVia(target: string, userMsg: string): string {
|
|
|
1464
566
|
)
|
|
1465
567
|
}
|
|
1466
568
|
if (detected.attached.length == 0) {
|
|
1467
|
-
|
|
1468
|
-
// one-element array is API-equivalent, but the stored thread message
|
|
1469
|
-
// shape (string vs parts array) — and with it every serialized
|
|
1470
|
-
// session and message fixture — would change for EVERY turn.
|
|
1471
|
-
return mainAgent(expanded)
|
|
569
|
+
return []
|
|
1472
570
|
}
|
|
1473
571
|
for (item in detected.attached) {
|
|
1474
572
|
pushMessage(color.dim("📎 attached ${item.label}"))
|
|
@@ -1476,174 +574,66 @@ export def agentReplyVia(target: string, userMsg: string): string {
|
|
|
1476
574
|
const attachments = map(detected.attached) as item {
|
|
1477
575
|
return item.attachment
|
|
1478
576
|
}
|
|
1479
|
-
|
|
1480
|
-
return mainAgent(parts)
|
|
577
|
+
return attachments
|
|
1481
578
|
}
|
|
1482
579
|
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
580
|
+
type AgentTarget =
|
|
581
|
+
| "code"
|
|
582
|
+
| "explorer"
|
|
583
|
+
| "main"
|
|
584
|
+
| "oracle"
|
|
585
|
+
| "research"
|
|
586
|
+
| "review"
|
|
587
|
+
| "coordinator"
|
|
588
|
+
|
|
589
|
+
export def dispatchAgent(agentName: AgentTarget, userMsg: string): string {
|
|
590
|
+
const expanded = expandSlash(userMsg, projectCommands)
|
|
591
|
+
return match(agentName) {
|
|
592
|
+
"code" => codeAgent(expanded)
|
|
593
|
+
"research" => researchAgent(expanded)
|
|
594
|
+
"oracle" => oracleAgent(expanded)
|
|
595
|
+
"explorer" => explorerAgent(expanded)
|
|
596
|
+
"review" => reviewAgent(expanded)
|
|
597
|
+
_ => {
|
|
598
|
+
const attachments = checkForAttachments(expanded)
|
|
599
|
+
return mainAgent([expanded, ...attachments])
|
|
600
|
+
}
|
|
601
|
+
}
|
|
1491
602
|
}
|
|
1492
603
|
|
|
1493
|
-
def
|
|
1494
|
-
|
|
604
|
+
def renderUserTurn(trimmed: string, msg: string): boolean {
|
|
605
|
+
const result = try dispatchAgent("coordinator", msg)
|
|
606
|
+
renderAgentResponse(result)
|
|
607
|
+
return true
|
|
1495
608
|
}
|
|
1496
609
|
|
|
1497
610
|
def _buildStatus(): { left: string; right: string; context: string } {
|
|
611
|
+
const roundedCost = "$${getCost().toFixed(4)}"
|
|
1498
612
|
return {
|
|
1499
613
|
left: "",
|
|
1500
|
-
right: roundedCost
|
|
614
|
+
right: roundedCost,
|
|
1501
615
|
context: ""
|
|
1502
616
|
}
|
|
1503
617
|
}
|
|
1504
618
|
|
|
1505
|
-
def sample(arr: any[]): any {
|
|
1506
|
-
return arr[Math.floor(Math.random() * arr.length)]
|
|
1507
|
-
}
|
|
1508
|
-
|
|
1509
|
-
def printHeader() {
|
|
1510
|
-
const fig = sample(figs)
|
|
1511
|
-
const data = box(
|
|
1512
|
-
title: "Agency",
|
|
1513
|
-
padding: 1,
|
|
1514
|
-
borderColor: "cyan",
|
|
1515
|
-
titleColor: "cyan",
|
|
1516
|
-
width: "full",
|
|
1517
|
-
) as b {
|
|
1518
|
-
b.row(gap: 1) as r {
|
|
1519
|
-
r.column(width: "66%") as left {
|
|
1520
|
-
left.text("Welcome to the Agency Agent!", bold: true)
|
|
1521
|
-
left.text("Ask me to write code, look up docs, or just chat.")
|
|
1522
|
-
left.text("All costs are estimates. Actual costs may be higher.", dim: true)
|
|
1523
|
-
left.hline()
|
|
1524
|
-
left.text("Getting Started", bold: true, fgColor: "cyan")
|
|
1525
|
-
left.text("/help for commands · /exit to quit", dim: true)
|
|
1526
|
-
}
|
|
1527
|
-
r.vline()
|
|
1528
|
-
r.column(width: "30%", align: "center") as right {
|
|
1529
|
-
right.raw(fig, align: "center")
|
|
1530
|
-
}
|
|
1531
|
-
}
|
|
1532
|
-
}
|
|
1533
|
-
print(render(data, color: true))
|
|
1534
|
-
}
|
|
1535
|
-
|
|
1536
|
-
// Print `--policy` usage and list the built-in policies (name + one-line
|
|
1537
|
-
// description). Shown when `--policy` is passed with no value.
|
|
1538
|
-
def printPolicyHelp() {
|
|
1539
|
-
print(color.bold("Usage: agency agent --policy <name | path>"))
|
|
1540
|
-
print(
|
|
1541
|
-
"Select the approval policy for this run: a built-in name below, or a path to a policy JSON file.",
|
|
1542
|
-
)
|
|
1543
|
-
print("")
|
|
1544
|
-
print(color.bold("Built-in policies:"))
|
|
1545
|
-
for (p in BUILTIN_POLICIES) {
|
|
1546
|
-
print(" ${color.cyan(p.name)}")
|
|
1547
|
-
print(" ${p.description}")
|
|
1548
|
-
}
|
|
1549
|
-
}
|
|
1550
|
-
|
|
1551
|
-
def givePolicyChoice() {
|
|
1552
|
-
const title = "Welcome to the agency agent. Please pick a policy to start."
|
|
1553
|
-
const body = "Don't worry, you can change this later. The policy just controls how the agent asks for your approval when it wants to do something. You can also create your own custom policy file at ${POLICY_PATH} and the agent will use it automatically."
|
|
1554
|
-
let items: ChoiceItem[] = [
|
|
1555
|
-
{
|
|
1556
|
-
key: "minimal",
|
|
1557
|
-
label: "minimal default policy, you do most approvals manually"
|
|
1558
|
-
},
|
|
1559
|
-
{
|
|
1560
|
-
key: "recommended",
|
|
1561
|
-
label: "recommended default policy, allow reading files and browsing the web, no writes"
|
|
1562
|
-
},
|
|
1563
|
-
]
|
|
1564
|
-
|
|
1565
|
-
const answer = chooseOption(title, body, items, allowFreeText: true)
|
|
1566
|
-
return match(answer) {
|
|
1567
|
-
"minimal" => minimalAutoApprovePolicy
|
|
1568
|
-
"recommended" => recommendedAutoApprovePolicy
|
|
1569
|
-
}
|
|
1570
|
-
}
|
|
1571
|
-
|
|
1572
619
|
// Shared session setup for both interactive and one-shot modes. Builds
|
|
1573
|
-
// the
|
|
1574
|
-
// returns the installed CLI policy handler.
|
|
1575
|
-
// first-run policy-choice prompt: one-shot has no user to answer it, so
|
|
1576
|
-
// it falls back to the recommended default (reads/web auto-approved;
|
|
1577
|
-
// writes still require approval, which a non-interactive run can't
|
|
1578
|
-
// grant — those tasks block, which is the safe behavior).
|
|
620
|
+
// the grounding context, loads (or initializes) the policy, and
|
|
621
|
+
// returns the installed CLI policy handler.
|
|
1579
622
|
def setupSession(interactive: boolean): any {
|
|
1580
|
-
// Point every path-taking tool at the user's working directory so the
|
|
1581
|
-
// agent's relative file/shell commands resolve against where the user
|
|
1582
|
-
// launched it. The user (via the agent) can change this later with
|
|
1583
|
-
// setAgentCwd.
|
|
1584
623
|
setAgentCwd(cwd())
|
|
1585
624
|
|
|
1586
625
|
// Grounding: the LLM shouldn't have to ask where it is or what day it
|
|
1587
|
-
// is.
|
|
1588
|
-
// present so the LLM follows the project's conventions.
|
|
626
|
+
// is. Also load `AGENTS.md` if present.
|
|
1589
627
|
const projectContext = loadAgentsMd(cwd()) with approve
|
|
1590
|
-
_context = "
|
|
628
|
+
_context = """
|
|
1591
629
|
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
630
|
+
Current date: ${today()}
|
|
631
|
+
Current working directory: ${cwd()}
|
|
632
|
+
${projectContext}
|
|
633
|
+
"""
|
|
634
|
+
|
|
635
|
+
const { policyFile, sessionPolicy } = getPolicyForAgent(_policyArg, interactive)
|
|
1598
636
|
|
|
1599
|
-
if (_policyArg != "") {
|
|
1600
|
-
// `--policy` override: a built-in name or a path to a policy file.
|
|
1601
|
-
// The cwd-scoped built-ins pin the launch directory, so we resolve
|
|
1602
|
-
// them against `cwd()` (what `setAgentCwd` just stored, and what the
|
|
1603
|
-
// write/git/bash interrupts resolve their default paths back to).
|
|
1604
|
-
const builtin = builtinPolicy(_policyArg, cwd())
|
|
1605
|
-
if (builtin != null) {
|
|
1606
|
-
// Hand the policy to the handler in memory (no disk write): a
|
|
1607
|
-
// one-off, cwd-scoped override must not clobber the saved policy
|
|
1608
|
-
// or leak into a later run from a different directory. Any new
|
|
1609
|
-
// interactive "always" decision persists to the session file.
|
|
1610
|
-
policyFile = SESSION_POLICY_PATH
|
|
1611
|
-
sessionPolicy = builtin
|
|
1612
|
-
} else {
|
|
1613
|
-
// Not a built-in name — treat it as a policy file path and use it
|
|
1614
|
-
// directly (the handler loads/persists it). Validate up front so a
|
|
1615
|
-
// typo fails fast instead of silently starting from empty.
|
|
1616
|
-
const loaded = parsePolicyFile(_policyArg)
|
|
1617
|
-
if (loaded is failure(f)) {
|
|
1618
|
-
print(
|
|
1619
|
-
color.red(
|
|
1620
|
-
"--policy '${_policyArg}' is neither a built-in policy (${builtinPolicyNames().join(", ")}) nor a readable policy file (${f.status}).",
|
|
1621
|
-
),
|
|
1622
|
-
)
|
|
1623
|
-
process.exit(1)
|
|
1624
|
-
}
|
|
1625
|
-
policyFile = _policyArg
|
|
1626
|
-
}
|
|
1627
|
-
} else {
|
|
1628
|
-
const policy = parsePolicyFile(POLICY_PATH)
|
|
1629
|
-
if (policy is failure(f)) {
|
|
1630
|
-
if (f.status == "doesnt-exist") {
|
|
1631
|
-
if (interactive) {
|
|
1632
|
-
print(color.yellow("No existing policy found at ${POLICY_PATH}."))
|
|
1633
|
-
setPolicy(POLICY_PATH, givePolicyChoice())
|
|
1634
|
-
} else {
|
|
1635
|
-
setPolicy(POLICY_PATH, recommendedAutoApprovePolicy)
|
|
1636
|
-
}
|
|
1637
|
-
} else {
|
|
1638
|
-
print(color.red("Failed to load policy: ${JSON.stringify(f.error)}"))
|
|
1639
|
-
process.exit(1)
|
|
1640
|
-
}
|
|
1641
|
-
} else {
|
|
1642
|
-
setPolicy(POLICY_PATH, policy.value)
|
|
1643
|
-
}
|
|
1644
|
-
}
|
|
1645
|
-
// Bind the handler to a local var so `with handler` parses (the
|
|
1646
|
-
// `with` clause only accepts an identifier, not a call expression).
|
|
1647
637
|
return cliPolicyHandler(
|
|
1648
638
|
file: policyFile,
|
|
1649
639
|
fields: ALWAYS_FIELDS,
|
|
@@ -1651,59 +641,45 @@ def setupSession(interactive: boolean): any {
|
|
|
1651
641
|
)
|
|
1652
642
|
}
|
|
1653
643
|
|
|
1654
|
-
// One-shot entry: run a single turn through the same `mainAgent` the
|
|
1655
|
-
// REPL uses, with the same session setup and policy handler, and return
|
|
1656
|
-
// the reply. Used for `agency agent -p "..."`, a positional query, or
|
|
1657
|
-
// piped stdin. Slash commands are expanded so `-p /foo bar` works like
|
|
1658
|
-
// typing it in the REPL; the loop-only built-ins (`/exit` etc.) are
|
|
1659
|
-
// meaningless here and aren't checked.
|
|
1660
644
|
def oneShotAgent(target: string, prompt: string): string {
|
|
1661
645
|
_isInteractive = false
|
|
1662
|
-
const handler = setupSession(false)
|
|
1663
|
-
let reply: string = ""
|
|
646
|
+
const handler = setupSession(interactive: false)
|
|
1664
647
|
handle {
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
if (result is success(r)) {
|
|
1670
|
-
reply = r
|
|
1671
|
-
} else if (result is failure(f)) {
|
|
1672
|
-
reply = formatTurnFailure("${f.error}")
|
|
648
|
+
const result = try dispatchAgent(target, prompt)
|
|
649
|
+
return match(result) {
|
|
650
|
+
success(r) => r
|
|
651
|
+
failure(f) => formatTurnFailure("${f.error}")
|
|
1673
652
|
}
|
|
1674
|
-
} with
|
|
1675
|
-
return handler(data)
|
|
1676
|
-
}
|
|
1677
|
-
return reply
|
|
653
|
+
} with handler
|
|
1678
654
|
}
|
|
1679
655
|
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
def
|
|
656
|
+
/* A seeded turn is if the user invokes the Agency agent with
|
|
657
|
+
an initial prompt – by using the agency doctor command, for example.
|
|
658
|
+
It means that agent starts with an initial prompt already there to respond to. */
|
|
659
|
+
def runSeedTurn(target: string, msg: string) {
|
|
1684
660
|
pushMessage(color.dim("> ${msg}"))
|
|
1685
|
-
const
|
|
1686
|
-
|
|
1687
|
-
|
|
661
|
+
const response = try dispatchAgent(target, msg)
|
|
662
|
+
renderAgentResponse(response)
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
def renderAgentResponse(
|
|
666
|
+
response: Result<string | null, { error: string }>,
|
|
667
|
+
): void {
|
|
668
|
+
if (response is success(reply)) {
|
|
669
|
+
if (reply != "" && reply != null) {
|
|
1688
670
|
pushMessage(highlight("${reply}\n", language: "markdown"))
|
|
1689
671
|
} else {
|
|
1690
672
|
pushMessage(color.red("No reply generated."))
|
|
1691
673
|
}
|
|
1692
|
-
} else if (
|
|
674
|
+
} else if (response is failure(f)) {
|
|
1693
675
|
pushMessage(color.red(formatTurnFailure("${f.error}")))
|
|
1694
676
|
}
|
|
1695
677
|
}
|
|
1696
678
|
|
|
1697
|
-
// Start the interactive REPL. When `seedPrompt` is non-empty it is run as
|
|
1698
|
-
// the first turn (routed via `seedTarget`) *inside* the handle block so
|
|
1699
|
-
// its interrupts reach the policy `handler`; the user is then left at the
|
|
1700
|
-
// prompt. `seedPrompt == ""` is the plain REPL with no seed. Subsequent
|
|
1701
|
-
// turns always go through `_runTurn` (the coordinator) regardless of
|
|
1702
|
-
// `seedTarget` — the target only kicks off the first turn.
|
|
1703
679
|
def startInteractive(handler: any, seedTarget: string, seedPrompt: string) {
|
|
1704
680
|
handle {
|
|
1705
681
|
if (seedPrompt != "") {
|
|
1706
|
-
|
|
682
|
+
runSeedTurn(seedTarget, seedPrompt)
|
|
1707
683
|
}
|
|
1708
684
|
repl(
|
|
1709
685
|
status: _buildStatus,
|
|
@@ -1713,107 +689,20 @@ def startInteractive(handler: any, seedTarget: string, seedPrompt: string) {
|
|
|
1713
689
|
historyMax: 1000,
|
|
1714
690
|
paletteCommands: mergedPalette(),
|
|
1715
691
|
)
|
|
1716
|
-
} with
|
|
1717
|
-
return handler(data)
|
|
1718
|
-
}
|
|
692
|
+
} with handler
|
|
1719
693
|
print(color.cyan("\nGoodbye!"))
|
|
1720
694
|
}
|
|
1721
695
|
|
|
1722
696
|
node main() {
|
|
1723
697
|
// Parse CLI flags first — `parseArgs` exits on --help / --version /
|
|
1724
698
|
// usage errors, before any handlers or the TUI are installed.
|
|
1725
|
-
const args =
|
|
1726
|
-
{
|
|
1727
|
-
programName: "agency agent",
|
|
1728
|
-
description: "Agency language assistant — interactive REPL by default; one-shot when piped or passed a query / --print.",
|
|
1729
|
-
version: getVersion(),
|
|
1730
|
-
flags: {
|
|
1731
|
-
print: {
|
|
1732
|
-
type: "boolean",
|
|
1733
|
-
short: "p",
|
|
1734
|
-
description: "Run one-shot: print the reply to stdout and exit (no REPL)"
|
|
1735
|
-
},
|
|
1736
|
-
debug: {
|
|
1737
|
-
type: "boolean",
|
|
1738
|
-
description: "Log tool returns (toolName, result, timing). Same as AGENT_DEBUG=1"
|
|
1739
|
-
},
|
|
1740
|
-
verbose: {
|
|
1741
|
-
type: "boolean",
|
|
1742
|
-
description: "Echo tool-call starts to stdout in non-interactive mode"
|
|
1743
|
-
},
|
|
1744
|
-
model: {
|
|
1745
|
-
type: "string",
|
|
1746
|
-
description: "Model for all LLM calls (overrides provider auto-detection)"
|
|
1747
|
-
},
|
|
1748
|
-
fastmodel: {
|
|
1749
|
-
type: "string",
|
|
1750
|
-
description: "Model for ordinary work (default: per detected provider)"
|
|
1751
|
-
},
|
|
1752
|
-
slowmodel: {
|
|
1753
|
-
type: "string",
|
|
1754
|
-
description: "Model for deep reasoning, e.g. the oracle/explorer subagents"
|
|
1755
|
-
},
|
|
1756
|
-
provider: {
|
|
1757
|
-
type: "string",
|
|
1758
|
-
description: "Force the LLM provider. Given alone (openai/anthropic/google/openrouter) it selects that provider's default models and skips env auto-detection; pair it with --model for any other provider (e.g. litellm, openai-compat, or a custom/local model)"
|
|
1759
|
-
},
|
|
1760
|
-
interactive: {
|
|
1761
|
-
type: "boolean",
|
|
1762
|
-
short: "i",
|
|
1763
|
-
description: "Run the given prompt as the first turn of an interactive session, then hand over the REPL (instead of one-shot)"
|
|
1764
|
-
},
|
|
1765
|
-
agent: {
|
|
1766
|
-
type: "string",
|
|
1767
|
-
description: "Route the starting prompt to a named subagent: code, research, oracle, explorer, review (default: coordinator)"
|
|
1768
|
-
},
|
|
1769
|
-
policy: {
|
|
1770
|
-
type: "string",
|
|
1771
|
-
optional: true,
|
|
1772
|
-
description: "Approval policy for this run: a built-in name (minimal, recommended, with-writes, approve-all) or a path to a policy JSON file. with-writes auto-approves file writes and git changes scoped to the current directory and its children; approve-all approves EVERY interrupt with no scoping (sandbox use only). Overrides the saved policy without persisting it. Pass --policy with no value to list the built-in policies."
|
|
1773
|
-
},
|
|
1774
|
-
"agent-home": {
|
|
1775
|
-
type: "string",
|
|
1776
|
-
description: "Directory to use instead of ~/.agency-agent for settings, policy, and history (equivalent to the AGENCY_AGENT_HOME env var; the flag wins when both are set)"
|
|
1777
|
-
},
|
|
1778
|
-
local: {
|
|
1779
|
-
type: "string",
|
|
1780
|
-
optional: true,
|
|
1781
|
-
description: "Run a local model (downloads if needed): a curated short name, an hf: URI, or a .gguf path. Run without a value for guided setup. Pins all slots to the local model; mutually exclusive with --model/--fastmodel/--slowmodel. Requires: npm i -g smoltalk-llama-cpp"
|
|
1782
|
-
},
|
|
1783
|
-
"local-model": {
|
|
1784
|
-
type: "string",
|
|
1785
|
-
optional: true,
|
|
1786
|
-
description: "Deprecated alias for --local."
|
|
1787
|
-
},
|
|
1788
|
-
trace: {
|
|
1789
|
-
type: "string",
|
|
1790
|
-
optional: true,
|
|
1791
|
-
description: "Write an execution trace to this file for debugging (bare --trace writes <runId>.agencytrace in the current directory)"
|
|
1792
|
-
},
|
|
1793
|
-
"log-file": {
|
|
1794
|
-
type: "string",
|
|
1795
|
-
description: "Append structured statelog events (one JSON object per line) to this file for debugging"
|
|
1796
|
-
},
|
|
1797
|
-
"max-tool-call-rounds": {
|
|
1798
|
-
type: "number",
|
|
1799
|
-
description: "Max LLM tool-call rounds before a tool loop halts (default 10 interactive, 50 in one-shot/-p mode)"
|
|
1800
|
-
},
|
|
1801
|
-
"max-tool-result-chars": {
|
|
1802
|
-
type: "number",
|
|
1803
|
-
description: "Max chars of a single tool result fed back to the model (0 disables; default 100000)"
|
|
1804
|
-
}
|
|
1805
|
-
}
|
|
1806
|
-
},
|
|
1807
|
-
)
|
|
699
|
+
const args = parseAgentArgs()
|
|
1808
700
|
|
|
1809
|
-
// NOTE: --trace / --log
|
|
701
|
+
// NOTE: --trace / --log / --agent-home are declared here only for
|
|
1810
702
|
// --help and so parseArgs accepts them. runBundledAgent extracts them from
|
|
1811
703
|
// the forwarded args and passes them to this process via env vars
|
|
1812
704
|
// (AGENCY_CONFIG_OVERRIDES for the first two, AGENCY_AGENT_HOME for the
|
|
1813
|
-
// last)
|
|
1814
|
-
// config overrides at construction, and lib/config.agency derives the
|
|
1815
|
-
// settings/policy/history paths from the agent home in its static-const
|
|
1816
|
-
// initializers. Do not re-handle them here.
|
|
705
|
+
// last).
|
|
1817
706
|
|
|
1818
707
|
if (args.flags.debug) {
|
|
1819
708
|
AGENT_DEBUG = true
|
|
@@ -1821,6 +710,21 @@ node main() {
|
|
|
1821
710
|
if (args.flags.verbose) {
|
|
1822
711
|
VERBOSE = true
|
|
1823
712
|
}
|
|
713
|
+
|
|
714
|
+
if (isFailure(MAIN_PROMPT_LARGE)) {
|
|
715
|
+
print(
|
|
716
|
+
color.red("Failed to read prompts/main-large.md: ${MAIN_PROMPT_LARGE.error}"),
|
|
717
|
+
)
|
|
718
|
+
process.exit(1)
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
if (isFailure(MAIN_PROMPT_SMALL)) {
|
|
722
|
+
print(
|
|
723
|
+
color.red("Failed to read prompts/main-small.md: ${MAIN_PROMPT_SMALL.error}"),
|
|
724
|
+
)
|
|
725
|
+
process.exit(1)
|
|
726
|
+
}
|
|
727
|
+
|
|
1824
728
|
// `--policy` is an optional-value flag: a bare `--policy` (no name or
|
|
1825
729
|
// path) prints the usage + the built-in policies and exits, rather than
|
|
1826
730
|
// silently doing nothing.
|
|
@@ -1830,100 +734,7 @@ node main() {
|
|
|
1830
734
|
}
|
|
1831
735
|
_policyArg = args.flags.policy ?? ""
|
|
1832
736
|
|
|
1833
|
-
|
|
1834
|
-
// value flag with three states: absent (null); bare empty-string for guided
|
|
1835
|
-
// setup; a value for that local model. `--local` and the hosted flags
|
|
1836
|
-
// (--model / --fastmodel / --slowmodel) are mutually exclusive -- local and
|
|
1837
|
-
// hosted are different in kind (provider llama-cpp, no embedding endpoint).
|
|
1838
|
-
const localVal = args.flags["local"] ?? args.flags["local-model"]
|
|
1839
|
-
const modelArg = args.flags.model ?? ""
|
|
1840
|
-
const hasModelFlags = modelArg != "" || (args.flags.fastmodel ?? "") != "" || (args.flags.slowmodel ?? "") != ""
|
|
1841
|
-
if (localVal != null && hasModelFlags) {
|
|
1842
|
-
print(
|
|
1843
|
-
color.red("Pass either --model (hosted) or --local (local), not both."),
|
|
1844
|
-
)
|
|
1845
|
-
process.exit(1)
|
|
1846
|
-
}
|
|
1847
|
-
if (localVal == "") {
|
|
1848
|
-
// Bare --local: guided setup. Non-interactive can't prompt, so keep the
|
|
1849
|
-
// print-catalog-and-exit behavior there.
|
|
1850
|
-
if (!isTTY()) {
|
|
1851
|
-
printLocalCatalog()
|
|
1852
|
-
process.exit(0)
|
|
1853
|
-
}
|
|
1854
|
-
if (!localModelsSupported()) {
|
|
1855
|
-
print(color.yellow("Local models need the smoltalk-llama-cpp package."))
|
|
1856
|
-
print("Install it, then re-run: ! npm i -g smoltalk-llama-cpp")
|
|
1857
|
-
process.exit(1)
|
|
1858
|
-
}
|
|
1859
|
-
let items: ChoiceItem[] = []
|
|
1860
|
-
for (modelName in listModelNames()) {
|
|
1861
|
-
items.push({
|
|
1862
|
-
key: modelName.name,
|
|
1863
|
-
label: modelName.name
|
|
1864
|
-
})
|
|
1865
|
-
}
|
|
1866
|
-
const choice = chooseOption(
|
|
1867
|
-
"Local model",
|
|
1868
|
-
"Pick a model, or type an hf: URI / .gguf path (downloads on first use):",
|
|
1869
|
-
items,
|
|
1870
|
-
allowFreeText: true,
|
|
1871
|
-
allowCancel: true,
|
|
1872
|
-
)
|
|
1873
|
-
if (choice == "" || choice == null) {
|
|
1874
|
-
process.exit(0)
|
|
1875
|
-
}
|
|
1876
|
-
configureLocalModel(choice)
|
|
1877
|
-
configureSearch(true)
|
|
1878
|
-
// fall through into the session on the chosen local model
|
|
1879
|
-
} else if (localVal != null) {
|
|
1880
|
-
configureLocalModel(localVal)
|
|
1881
|
-
configureSearch(true)
|
|
1882
|
-
} else {
|
|
1883
|
-
// Hosted path. --model accepts `slot=model` (per-slot) or a bare model
|
|
1884
|
-
// (global pin); explicit --fastmodel/--slowmodel win over a --model slot=.
|
|
1885
|
-
const parsed = parseModelFlag(modelArg)
|
|
1886
|
-
let modelGlobal = ""
|
|
1887
|
-
let fastFlag = ""
|
|
1888
|
-
let slowFlag = ""
|
|
1889
|
-
let embeddingFlag = ""
|
|
1890
|
-
if (modelArg != "") {
|
|
1891
|
-
if (parsed.slot == "") {
|
|
1892
|
-
modelGlobal = parsed.model
|
|
1893
|
-
} else if (parsed.slot == "main") {
|
|
1894
|
-
fastFlag = parsed.model
|
|
1895
|
-
} else if (parsed.slot == "reasoning") {
|
|
1896
|
-
slowFlag = parsed.model
|
|
1897
|
-
} else if (parsed.slot == "embedding") {
|
|
1898
|
-
if (!embeddingSpecHasProvider(parsed.model)) {
|
|
1899
|
-
print(
|
|
1900
|
-
color.red(
|
|
1901
|
-
"Embedding models need the provider/model form, e.g. --model embedding=openai/text-embedding-3-small.",
|
|
1902
|
-
),
|
|
1903
|
-
)
|
|
1904
|
-
process.exit(1)
|
|
1905
|
-
}
|
|
1906
|
-
embeddingFlag = parsed.model
|
|
1907
|
-
} else {
|
|
1908
|
-
print(
|
|
1909
|
-
color.red(
|
|
1910
|
-
"Unknown model slot '${parsed.slot}' in --model. Valid slots: ${SLOTS.join(", ")}.",
|
|
1911
|
-
),
|
|
1912
|
-
)
|
|
1913
|
-
process.exit(1)
|
|
1914
|
-
}
|
|
1915
|
-
}
|
|
1916
|
-
const fm = args.flags.fastmodel ?? ""
|
|
1917
|
-
const sm = args.flags.slowmodel ?? ""
|
|
1918
|
-
if (fm != "") {
|
|
1919
|
-
fastFlag = fm
|
|
1920
|
-
}
|
|
1921
|
-
if (sm != "") {
|
|
1922
|
-
slowFlag = sm
|
|
1923
|
-
}
|
|
1924
|
-
configureModels(modelGlobal, fastFlag, slowFlag, embeddingFlag, args.flags.provider ?? "")
|
|
1925
|
-
configureSearch(false)
|
|
1926
|
-
}
|
|
737
|
+
pickModelsForAgent(args)
|
|
1927
738
|
|
|
1928
739
|
// Persistent knowledge graph, gated on the capability profile. Must run
|
|
1929
740
|
// before any handler/REPL is installed.
|
|
@@ -1939,83 +750,53 @@ node main() {
|
|
|
1939
750
|
const positionalQuery = args.positionals.join(" ")
|
|
1940
751
|
const hasQuery = positionalQuery != ""
|
|
1941
752
|
const startAgent = args.flags.agent ?? ""
|
|
753
|
+
// Validate the target subagent name up front (empty = coordinator).
|
|
754
|
+
if (startAgent != "" && !START_AGENTS.includes(startAgent)) {
|
|
755
|
+
print(
|
|
756
|
+
color.red(
|
|
757
|
+
"Unknown --agent value: ${startAgent}. Valid: ${START_AGENTS.join(", ")}",
|
|
758
|
+
),
|
|
759
|
+
)
|
|
760
|
+
process.exit(1)
|
|
761
|
+
}
|
|
762
|
+
|
|
1942
763
|
const wantInteractive = args.flags.interactive
|
|
1943
764
|
|
|
1944
765
|
// Seeded interactive: run the prompt as the first turn, then hand over the
|
|
1945
766
|
// REPL (needs a TTY + query, and not --print). Everything else is one-shot.
|
|
1946
767
|
const seededInteractive = wantInteractive && hasQuery && isTTY() && !args.flags.print
|
|
1947
|
-
const
|
|
768
|
+
const isOneShot = !seededInteractive && (args.flags.print || hasQuery || !isTTY())
|
|
1948
769
|
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
// is dispatched, so every (fork-seeded) subagent inherits them. setLlmOptions
|
|
1954
|
-
// merges only present keys, so this never clobbers the model defaults above.
|
|
1955
|
-
// Separate calls, each gated on its own value: setLlmOptions only skips
|
|
1956
|
-
// `undefined`, not `null`, so passing an absent key would clobber the bag.
|
|
1957
|
-
const maxRounds = resolveMaxToolCallRounds(args.flags["max-tool-call-rounds"] ?? null, runningOneShot)
|
|
770
|
+
const maxRounds = resolveMaxToolCallRounds(
|
|
771
|
+
args.flags["max-tool-call-rounds"] ?? null,
|
|
772
|
+
isOneShot,
|
|
773
|
+
)
|
|
1958
774
|
if (maxRounds != null) {
|
|
1959
|
-
setLlmOptions({
|
|
775
|
+
setLlmOptions({
|
|
776
|
+
maxToolCallRounds: maxRounds
|
|
777
|
+
})
|
|
1960
778
|
}
|
|
1961
779
|
const maxResultChars = args.flags["max-tool-result-chars"]
|
|
1962
780
|
if (maxResultChars != null) {
|
|
1963
|
-
setLlmOptions({
|
|
781
|
+
setLlmOptions({
|
|
782
|
+
maxToolResultChars: maxResultChars
|
|
783
|
+
})
|
|
1964
784
|
}
|
|
1965
785
|
|
|
1966
|
-
// Per-model default output cap from the capability profile. The hosted
|
|
1967
|
-
// default of 20000 carries the adaptive-thinking empty-reply fix that
|
|
1968
|
-
// used to live here as a TEMPORARY block. Local profiles cap lower.
|
|
1969
|
-
// Branch-scoped, so every subagent inherits it. A per-call
|
|
1970
|
-
// llm(..., { maxTokens }) still wins, e.g. the summarizer's 256.
|
|
1971
786
|
const capsMaxTokens = getCapabilities().maxTokens
|
|
1972
787
|
if (capsMaxTokens != null) {
|
|
1973
|
-
setLlmOptions({
|
|
788
|
+
setLlmOptions({
|
|
789
|
+
maxTokens: capsMaxTokens
|
|
790
|
+
})
|
|
1974
791
|
}
|
|
1975
792
|
|
|
1976
793
|
// One-shot autonomy: tell the code subagent there is no human to answer
|
|
1977
794
|
// questions and no next turn, so it must drive to a finished artifact.
|
|
1978
795
|
// Set before any dispatch so both the pinned `--agent code` path and the
|
|
1979
796
|
// coordinator (which calls codeAgent as a tool) pick it up.
|
|
1980
|
-
setCodeOneShot(
|
|
797
|
+
setCodeOneShot(isOneShot)
|
|
1981
798
|
|
|
1982
|
-
|
|
1983
|
-
if (startAgent != "" && !START_AGENTS.includes(startAgent)) {
|
|
1984
|
-
print(
|
|
1985
|
-
color.red(
|
|
1986
|
-
"Unknown --agent value: ${startAgent}. Valid: ${START_AGENTS.join(", ")}",
|
|
1987
|
-
),
|
|
1988
|
-
)
|
|
1989
|
-
process.exit(1)
|
|
1990
|
-
}
|
|
1991
|
-
|
|
1992
|
-
// Seeded interactive: run the given prompt as the first turn, then hand
|
|
1993
|
-
// the REPL to the user. Requires a seed prompt and a TTY (the REPL reads
|
|
1994
|
-
// stdin). `--print` wins if both are passed — its whole job is to print
|
|
1995
|
-
// and exit with no REPL. Without a TTY (or with `--print`) we fall
|
|
1996
|
-
// through to one-shot below; `--interactive` with no prompt falls through
|
|
1997
|
-
// to the plain REPL.
|
|
1998
|
-
if (seededInteractive) {
|
|
1999
|
-
setTitle("Agency Agent")
|
|
2000
|
-
clearScreen()
|
|
2001
|
-
printHeader()
|
|
2002
|
-
const seededHandler = setupSession(true)
|
|
2003
|
-
startInteractive(seededHandler, startAgent, positionalQuery)
|
|
2004
|
-
process.exit(0)
|
|
2005
|
-
}
|
|
2006
|
-
|
|
2007
|
-
// One-shot invocation: run one turn through the same agent the REPL uses
|
|
2008
|
-
// and write the reply to stdout. No banner, no REPL. Entered when
|
|
2009
|
-
// `--print` / `-p` is passed, a positional query is given, or stdin is
|
|
2010
|
-
// piped (no TTY) — so the agent behaves like a Unix filter:
|
|
2011
|
-
// `echo "..." | agency agent`. `--interactive` is NOT a one-shot trigger
|
|
2012
|
-
// on its own: with a TTY and no prompt it falls through to the plain
|
|
2013
|
-
// REPL; without a TTY the `!isTTY()` check below routes it here to
|
|
2014
|
-
// degrade gracefully. The prompt comes from the positional when present,
|
|
2015
|
-
// otherwise from stdin. (Title/clearScreen are skipped so piped output
|
|
2016
|
-
// stays free of escape codes.)
|
|
2017
|
-
const forceOneShot = args.flags.print || hasQuery
|
|
2018
|
-
if (forceOneShot || !isTTY()) {
|
|
799
|
+
if (isOneShot) {
|
|
2019
800
|
let prompt = positionalQuery
|
|
2020
801
|
if (!hasQuery) {
|
|
2021
802
|
const fromStdin = readStdin()
|
|
@@ -2025,17 +806,13 @@ node main() {
|
|
|
2025
806
|
prompt = fromStdin
|
|
2026
807
|
}
|
|
2027
808
|
print(oneShotAgent(startAgent, prompt))
|
|
2028
|
-
|
|
809
|
+
const roundedCost = "$${getCost().toFixed(4)}"
|
|
810
|
+
print(color.dim("\nSession cost: ${roundedCost}"))
|
|
811
|
+
} else {
|
|
812
|
+
setTitle("Agency Agent")
|
|
813
|
+
clearScreen()
|
|
814
|
+
print(getHeader())
|
|
815
|
+
const handler = setupSession(interactive: true)
|
|
816
|
+
startInteractive(handler, startAgent, positionalQuery)
|
|
2029
817
|
}
|
|
2030
|
-
|
|
2031
|
-
// Plain interactive REPL (no seed). `startInteractive` owns the runloop;
|
|
2032
|
-
// every Enter dispatches into `_runTurn` which calls `mainAgent`.
|
|
2033
|
-
// Interrupts raised during a turn propagate to the policy handler
|
|
2034
|
-
// installed by the enclosing `handle` block via the AsyncLocalStorage
|
|
2035
|
-
// handler stack.
|
|
2036
|
-
setTitle("Agency Agent")
|
|
2037
|
-
clearScreen()
|
|
2038
|
-
printHeader()
|
|
2039
|
-
const handler = setupSession(true)
|
|
2040
|
-
startInteractive(handler, "", "")
|
|
2041
818
|
}
|