@neurocode-ai/core 1.18.8
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/bin/neurocode +199 -0
- package/bunfig.toml +2 -0
- package/drizzle.config.ts +10 -0
- package/package.json +131 -0
- package/schema.json +2071 -0
- package/script/fix-node-pty.ts +28 -0
- package/script/migration.ts +196 -0
- package/src/account/sql.ts +39 -0
- package/src/account.ts +101 -0
- package/src/agent.ts +111 -0
- package/src/aisdk.ts +235 -0
- package/src/background-job.ts +365 -0
- package/src/catalog.ts +301 -0
- package/src/command.ts +64 -0
- package/src/config/agent.ts +25 -0
- package/src/config/attachments.ts +15 -0
- package/src/config/command.ts +12 -0
- package/src/config/compaction.ts +15 -0
- package/src/config/experimental.ts +18 -0
- package/src/config/formatter.ts +12 -0
- package/src/config/lsp.ts +18 -0
- package/src/config/markdown.ts +36 -0
- package/src/config/mcp.ts +48 -0
- package/src/config/plugin/agent.ts +179 -0
- package/src/config/plugin/command.ts +83 -0
- package/src/config/plugin/external.ts +91 -0
- package/src/config/plugin/provider.ts +113 -0
- package/src/config/plugin/reference.ts +70 -0
- package/src/config/plugin/skill.ts +50 -0
- package/src/config/plugin.ts +13 -0
- package/src/config/provider.ts +71 -0
- package/src/config/reference.ts +22 -0
- package/src/config/tool-output.ts +9 -0
- package/src/config/watcher.ts +7 -0
- package/src/config.ts +227 -0
- package/src/control-plane/move-session.ts +148 -0
- package/src/control-plane/workspace.sql.ts +20 -0
- package/src/credential/sql.ts +14 -0
- package/src/credential.ts +138 -0
- package/src/cross-spawn-spawner.ts +507 -0
- package/src/data-migration.sql.ts +6 -0
- package/src/database/database.ts +57 -0
- package/src/database/migration/20260127222353_familiar_lady_ursula.ts +107 -0
- package/src/database/migration/20260211171708_add_project_commands.ts +11 -0
- package/src/database/migration/20260213144116_wakeful_the_professor.ts +23 -0
- package/src/database/migration/20260225215848_workspace.ts +19 -0
- package/src/database/migration/20260227213759_add_session_workspace_id.ts +12 -0
- package/src/database/migration/20260228203230_blue_harpoon.ts +30 -0
- package/src/database/migration/20260303231226_add_workspace_fields.ts +15 -0
- package/src/database/migration/20260309230000_move_org_to_state.ts +15 -0
- package/src/database/migration/20260312043431_session_message_cursor.ts +16 -0
- package/src/database/migration/20260323234822_events.ts +26 -0
- package/src/database/migration/20260410174513_workspace-name.ts +29 -0
- package/src/database/migration/20260413175956_chief_energizer.ts +24 -0
- package/src/database/migration/20260423070820_add_icon_url_override.ts +14 -0
- package/src/database/migration/20260427172553_slow_nightmare.ts +30 -0
- package/src/database/migration/20260428004200_add_session_path.ts +11 -0
- package/src/database/migration/20260501142318_next_venus.ts +12 -0
- package/src/database/migration/20260504145000_add_sync_owner.ts +11 -0
- package/src/database/migration/20260507164347_add_workspace_time.ts +11 -0
- package/src/database/migration/20260510033149_session_usage.ts +56 -0
- package/src/database/migration/20260511000411_data_migration_state.ts +16 -0
- package/src/database/migration/20260511173437_session-metadata.ts +16 -0
- package/src/database/migration/20260601010001_normalize_storage_paths.ts +22 -0
- package/src/database/migration/20260601202201_amazing_prowler.ts +11 -0
- package/src/database/migration/20260602002951_lowly_union_jack.ts +24 -0
- package/src/database/migration/20260602182828_add_project_directories.ts +20 -0
- package/src/database/migration/20260603001617_session_message_projection_indexes.ts +19 -0
- package/src/database/migration/20260603040000_session_message_projection_order.ts +19 -0
- package/src/database/migration/20260603141458_session_input_inbox.ts +25 -0
- package/src/database/migration/20260603160727_jittery_ezekiel_stane.ts +20 -0
- package/src/database/migration/20260604172448_event_sourced_session_input.ts +47 -0
- package/src/database/migration/20260605003541_add_session_context_snapshot.ts +21 -0
- package/src/database/migration/20260605042240_add_context_epoch_agent.ts +11 -0
- package/src/database/migration/20260611035744_credential.ts +25 -0
- package/src/database/migration/20260611192811_lush_chimera.ts +25 -0
- package/src/database/migration/20260612174303_project_dir_strategy.ts +29 -0
- package/src/database/migration/20260622142730_simplify_session_context_epoch.ts +13 -0
- package/src/database/migration/20260622170816_reset_v2_session_state.ts +17 -0
- package/src/database/migration/20260622202450_simplify_session_input.ts +17 -0
- package/src/database/migration.gen.ts +44 -0
- package/src/database/migration.ts +81 -0
- package/src/database/path.ts +91 -0
- package/src/database/schema.gen.ts +274 -0
- package/src/database/schema.sql.ts +10 -0
- package/src/database/sqlite.bun.ts +183 -0
- package/src/database/sqlite.node.ts +178 -0
- package/src/database/sqlite.ts +8 -0
- package/src/effect/app-node-builder.ts +23 -0
- package/src/effect/app-node-platform.ts +18 -0
- package/src/effect/app-node.ts +14 -0
- package/src/effect/dfdf +1 -0
- package/src/effect/keyed-mutex.ts +45 -0
- package/src/effect/layer-node.ts +333 -0
- package/src/effect/memo-map.ts +3 -0
- package/src/effect/runtime.ts +21 -0
- package/src/effect/service-use.ts +43 -0
- package/src/event/sql.ts +25 -0
- package/src/event.ts +638 -0
- package/src/file-mutation.ts +207 -0
- package/src/file.ts +6 -0
- package/src/filesystem/fff.bun.ts +140 -0
- package/src/filesystem/fff.node.ts +138 -0
- package/src/filesystem/ignore.ts +67 -0
- package/src/filesystem/protected.ts +53 -0
- package/src/filesystem/search.ts +239 -0
- package/src/filesystem/watcher.ts +140 -0
- package/src/filesystem.ts +118 -0
- package/src/flag/flag.ts +78 -0
- package/src/fs-util.ts +274 -0
- package/src/git.ts +987 -0
- package/src/github-copilot/README.md +5 -0
- package/src/github-copilot/chat/convert-to-openai-compatible-chat-messages.ts +170 -0
- package/src/github-copilot/chat/get-response-metadata.ts +15 -0
- package/src/github-copilot/chat/map-openai-compatible-finish-reason.ts +19 -0
- package/src/github-copilot/chat/openai-compatible-api-types.ts +64 -0
- package/src/github-copilot/chat/openai-compatible-chat-language-model.ts +815 -0
- package/src/github-copilot/chat/openai-compatible-chat-options.ts +28 -0
- package/src/github-copilot/chat/openai-compatible-metadata-extractor.ts +44 -0
- package/src/github-copilot/chat/openai-compatible-prepare-tools.ts +83 -0
- package/src/github-copilot/copilot-provider.ts +100 -0
- package/src/github-copilot/openai-compatible-error.ts +27 -0
- package/src/github-copilot/responses/convert-to-openai-responses-input.ts +335 -0
- package/src/github-copilot/responses/map-openai-responses-finish-reason.ts +22 -0
- package/src/github-copilot/responses/openai-config.ts +18 -0
- package/src/github-copilot/responses/openai-error.ts +22 -0
- package/src/github-copilot/responses/openai-responses-api-types.ts +214 -0
- package/src/github-copilot/responses/openai-responses-language-model.ts +1770 -0
- package/src/github-copilot/responses/openai-responses-prepare-tools.ts +173 -0
- package/src/github-copilot/responses/openai-responses-settings.ts +1 -0
- package/src/github-copilot/responses/tool/code-interpreter.ts +87 -0
- package/src/github-copilot/responses/tool/file-search.ts +127 -0
- package/src/github-copilot/responses/tool/image-generation.ts +114 -0
- package/src/github-copilot/responses/tool/local-shell.ts +64 -0
- package/src/github-copilot/responses/tool/web-search-preview.ts +103 -0
- package/src/github-copilot/responses/tool/web-search.ts +102 -0
- package/src/global.ts +87 -0
- package/src/id/id.ts +47 -0
- package/src/image/photon.ts +94 -0
- package/src/image.ts +81 -0
- package/src/installation/version.ts +8 -0
- package/src/instruction-context.ts +101 -0
- package/src/integration/connection.ts +12 -0
- package/src/integration.ts +520 -0
- package/src/location-mutation.ts +162 -0
- package/src/location-service-map.ts +18 -0
- package/src/location-services.ts +115 -0
- package/src/location.ts +39 -0
- package/src/markdown.d.ts +4 -0
- package/src/model.ts +41 -0
- package/src/models-dev.ts +260 -0
- package/src/npm-config.ts +40 -0
- package/src/npm.ts +269 -0
- package/src/oauth/page.ts +276 -0
- package/src/observability/logging.ts +71 -0
- package/src/observability/otlp.ts +79 -0
- package/src/observability/shared.ts +1 -0
- package/src/observability.ts +24 -0
- package/src/patch.ts +197 -0
- package/src/permission/saved.ts +79 -0
- package/src/permission/sql.ts +20 -0
- package/src/permission.ts +310 -0
- package/src/plugin/agent.ts +206 -0
- package/src/plugin/command/initialize.txt +65 -0
- package/src/plugin/command/review.txt +100 -0
- package/src/plugin/command.ts +25 -0
- package/src/plugin/host.ts +219 -0
- package/src/plugin/internal.ts +153 -0
- package/src/plugin/layer-map.example.ts +94 -0
- package/src/plugin/models-dev.ts +183 -0
- package/src/plugin/promise.ts +93 -0
- package/src/plugin/provider/alibaba.ts +15 -0
- package/src/plugin/provider/amazon-bedrock.ts +126 -0
- package/src/plugin/provider/anthropic.ts +27 -0
- package/src/plugin/provider/azure.ts +82 -0
- package/src/plugin/provider/cerebras.ts +26 -0
- package/src/plugin/provider/cloudflare-ai-gateway.ts +81 -0
- package/src/plugin/provider/cloudflare-workers-ai.ts +81 -0
- package/src/plugin/provider/cohere.ts +15 -0
- package/src/plugin/provider/deepinfra.ts +15 -0
- package/src/plugin/provider/dynamic.ts +31 -0
- package/src/plugin/provider/gateway.ts +15 -0
- package/src/plugin/provider/github-copilot.ts +52 -0
- package/src/plugin/provider/gitlab.ts +65 -0
- package/src/plugin/provider/google-vertex.ts +173 -0
- package/src/plugin/provider/google.ts +15 -0
- package/src/plugin/provider/groq.ts +15 -0
- package/src/plugin/provider/kilo.ts +21 -0
- package/src/plugin/provider/llmgateway.ts +26 -0
- package/src/plugin/provider/mistral.ts +15 -0
- package/src/plugin/provider/nvidia.ts +22 -0
- package/src/plugin/provider/openai-compatible.ts +17 -0
- package/src/plugin/provider/openai.ts +292 -0
- package/src/plugin/provider/opencode.ts +311 -0
- package/src/plugin/provider/openrouter.ts +36 -0
- package/src/plugin/provider/perplexity.ts +15 -0
- package/src/plugin/provider/sap-ai-core.ts +46 -0
- package/src/plugin/provider/snowflake-cortex.ts +89 -0
- package/src/plugin/provider/togetherai.ts +15 -0
- package/src/plugin/provider/venice.ts +15 -0
- package/src/plugin/provider/vercel.ts +27 -0
- package/src/plugin/provider/xai.ts +22 -0
- package/src/plugin/provider/zenmux.ts +21 -0
- package/src/plugin/provider.ts +71 -0
- package/src/plugin/skill/customize-neurocode.md +452 -0
- package/src/plugin/skill.ts +31 -0
- package/src/plugin/variant.ts +39 -0
- package/src/plugin.ts +167 -0
- package/src/policy.ts +49 -0
- package/src/process.ts +261 -0
- package/src/project/copy-strategies.ts +35 -0
- package/src/project/copy.ts +292 -0
- package/src/project/directories.ts +158 -0
- package/src/project/schema.ts +16 -0
- package/src/project/sql.ts +35 -0
- package/src/project.ts +136 -0
- package/src/provider.ts +25 -0
- package/src/pty/protocol.ts +37 -0
- package/src/pty/pty.bun.ts +26 -0
- package/src/pty/pty.node.ts +29 -0
- package/src/pty/pty.ts +25 -0
- package/src/pty/schema.ts +1 -0
- package/src/pty/ticket.ts +56 -0
- package/src/pty.ts +318 -0
- package/src/public-event-manifest.ts +7 -0
- package/src/question.ts +153 -0
- package/src/reference/guidance.ts +69 -0
- package/src/reference.ts +125 -0
- package/src/repository-cache.ts +259 -0
- package/src/repository.ts +214 -0
- package/src/ripgrep/binary.ts +132 -0
- package/src/ripgrep.ts +281 -0
- package/src/schema.ts +79 -0
- package/src/session/compaction.ts +241 -0
- package/src/session/context-epoch.ts +174 -0
- package/src/session/error.ts +24 -0
- package/src/session/event.ts +2 -0
- package/src/session/execution/local.ts +46 -0
- package/src/session/execution.ts +34 -0
- package/src/session/history.ts +101 -0
- package/src/session/info.ts +50 -0
- package/src/session/input.ts +288 -0
- package/src/session/message-updater.ts +397 -0
- package/src/session/message.ts +2 -0
- package/src/session/projector.ts +458 -0
- package/src/session/prompt.ts +1 -0
- package/src/session/revert.ts +121 -0
- package/src/session/run-coordinator.ts +104 -0
- package/src/session/runner/index.ts +28 -0
- package/src/session/runner/llm.ts +432 -0
- package/src/session/runner/max-steps.ts +16 -0
- package/src/session/runner/model.ts +218 -0
- package/src/session/runner/publish-llm-event.ts +423 -0
- package/src/session/runner/to-llm-message.ts +171 -0
- package/src/session/schema.ts +9 -0
- package/src/session/sql.ts +176 -0
- package/src/session/store.ts +63 -0
- package/src/session/todo.ts +78 -0
- package/src/session.ts +486 -0
- package/src/share/sql.ts +13 -0
- package/src/shell.ts +226 -0
- package/src/skill/discovery.ts +213 -0
- package/src/skill/guidance.ts +76 -0
- package/src/skill.ts +132 -0
- package/src/snapshot.ts +266 -0
- package/src/state.ts +128 -0
- package/src/system-context/builtins.ts +50 -0
- package/src/system-context/index.ts +320 -0
- package/src/system-context/registry.ts +49 -0
- package/src/tool/AGENTS.md +59 -0
- package/src/tool/application-tools.ts +57 -0
- package/src/tool/apply-patch.ts +219 -0
- package/src/tool/bash.ts +207 -0
- package/src/tool/builtins.ts +48 -0
- package/src/tool/edit.ts +223 -0
- package/src/tool/glob.ts +105 -0
- package/src/tool/grep.ts +137 -0
- package/src/tool/http-body.ts +30 -0
- package/src/tool/question.ts +94 -0
- package/src/tool/read-filesystem.ts +366 -0
- package/src/tool/read.ts +117 -0
- package/src/tool/registry.ts +147 -0
- package/src/tool/skill.ts +109 -0
- package/src/tool/todowrite.ts +62 -0
- package/src/tool/tool.ts +162 -0
- package/src/tool/tools.ts +13 -0
- package/src/tool/webfetch.ts +218 -0
- package/src/tool/websearch.ts +260 -0
- package/src/tool/write.ts +101 -0
- package/src/tool-output-store.ts +211 -0
- package/src/util/array.ts +10 -0
- package/src/util/binary.ts +41 -0
- package/src/util/effect-flock.ts +284 -0
- package/src/util/encode.ts +51 -0
- package/src/util/error.ts +70 -0
- package/src/util/flock.ts +358 -0
- package/src/util/glob.ts +34 -0
- package/src/util/hash.ts +11 -0
- package/src/util/identifier.ts +1 -0
- package/src/util/iife.ts +3 -0
- package/src/util/lazy.ts +11 -0
- package/src/util/module.ts +10 -0
- package/src/util/path.ts +37 -0
- package/src/util/retry.ts +42 -0
- package/src/util/slug.ts +74 -0
- package/src/util/token.ts +5 -0
- package/src/util/which.ts +14 -0
- package/src/util/wildcard.ts +14 -0
- package/src/v1/config/agent.ts +89 -0
- package/src/v1/config/attachment.ts +25 -0
- package/src/v1/config/command.ts +13 -0
- package/src/v1/config/config.ts +192 -0
- package/src/v1/config/console-state.ts +16 -0
- package/src/v1/config/error.ts +39 -0
- package/src/v1/config/formatter.ts +13 -0
- package/src/v1/config/layout.ts +6 -0
- package/src/v1/config/lsp.ts +80 -0
- package/src/v1/config/mcp.ts +63 -0
- package/src/v1/config/migrate.ts +254 -0
- package/src/v1/config/permission.ts +50 -0
- package/src/v1/config/plugin.ts +9 -0
- package/src/v1/config/provider-options.ts +227 -0
- package/src/v1/config/provider.ts +121 -0
- package/src/v1/config/server.ts +19 -0
- package/src/v1/config/skills.ts +13 -0
- package/src/v1/permission.ts +33 -0
- package/src/v1/session.ts +68 -0
- package/src/v2-schema.ts +3 -0
- package/src/workspace.ts +6 -0
- package/sst-env.d.ts +10 -0
- package/test/agent.test.ts +131 -0
- package/test/application-tools.test.ts +287 -0
- package/test/background-job.test.ts +106 -0
- package/test/catalog.test.ts +353 -0
- package/test/command.test.ts +56 -0
- package/test/config/agent.test.ts +349 -0
- package/test/config/command.test.ts +83 -0
- package/test/config/config.test.ts +798 -0
- package/test/config/fixtures/plugin/directory-plugin.ts +13 -0
- package/test/config/plugin.test.ts +248 -0
- package/test/config/provider-options.test.ts +224 -0
- package/test/config/provider.test.ts +269 -0
- package/test/config/skill.test.ts +80 -0
- package/test/credential.test.ts +36 -0
- package/test/database-migration.test.ts +644 -0
- package/test/effect/cross-spawn-spawner.test.ts +426 -0
- package/test/effect/keyed-mutex.test.ts +73 -0
- package/test/effect/layer-node/layer-node-types.test.ts +143 -0
- package/test/effect/layer-node/layer-node.test.ts +261 -0
- package/test/effect/layer-node/node-build.test.ts +125 -0
- package/test/effect/observability.test.ts +109 -0
- package/test/event.test.ts +1124 -0
- package/test/file-mutation.test.ts +368 -0
- package/test/filesystem/filesystem.test.ts +387 -0
- package/test/filesystem/ignore.test.ts +10 -0
- package/test/filesystem/search.test.ts +44 -0
- package/test/filesystem/watcher.test.ts +266 -0
- package/test/fixture/effect-flock-worker.ts +60 -0
- package/test/fixture/flock-worker.ts +72 -0
- package/test/fixture/git.ts +49 -0
- package/test/fixture/location.ts +26 -0
- package/test/fixture/tmpdir.ts +25 -0
- package/test/fixtures/recordings/session-runner/openai-chat-streams-text.json +27 -0
- package/test/git.test.ts +164 -0
- package/test/github-copilot/convert-to-copilot-messages.test.ts +523 -0
- package/test/github-copilot/copilot-chat-model.test.ts +592 -0
- package/test/github-copilot/openai-responses-language-model.test.ts +206 -0
- package/test/global.test.ts +16 -0
- package/test/instruction-context.test.ts +323 -0
- package/test/integration.test.ts +349 -0
- package/test/legacy-event-schema.test.ts +16 -0
- package/test/lib/effect.ts +53 -0
- package/test/lib/tool.ts +20 -0
- package/test/location-filesystem.test.ts +69 -0
- package/test/location-layer.test.ts +226 -0
- package/test/location-mutation.test.ts +178 -0
- package/test/location.test.ts +42 -0
- package/test/model.test.ts +23 -0
- package/test/models.test.ts +290 -0
- package/test/move-session.test.ts +235 -0
- package/test/npm-config.test.ts +51 -0
- package/test/npm.test.ts +84 -0
- package/test/oauth-page.test.ts +15 -0
- package/test/patch.test.ts +68 -0
- package/test/permission.test.ts +315 -0
- package/test/plugin/command.test.ts +47 -0
- package/test/plugin/fixture.ts +52 -0
- package/test/plugin/fixtures/config-effect-plugin.ts +15 -0
- package/test/plugin/fixtures/config-promise-plugin.ts +13 -0
- package/test/plugin/fixtures/invalid-plugin.ts +1 -0
- package/test/plugin/fixtures/models-dev.json +14 -0
- package/test/plugin/fixtures/provider-factory.ts +9 -0
- package/test/plugin/host.ts +303 -0
- package/test/plugin/models-dev.test.ts +170 -0
- package/test/plugin/promise.test.ts +67 -0
- package/test/plugin/provider-alibaba.test.ts +92 -0
- package/test/plugin/provider-amazon-bedrock.test.ts +622 -0
- package/test/plugin/provider-anthropic.test.ts +94 -0
- package/test/plugin/provider-azure-cognitive-services.test.ts +199 -0
- package/test/plugin/provider-azure.test.ts +293 -0
- package/test/plugin/provider-cerebras.test.ts +136 -0
- package/test/plugin/provider-cloudflare-ai-gateway.test.ts +426 -0
- package/test/plugin/provider-cloudflare-workers-ai.test.ts +279 -0
- package/test/plugin/provider-cohere.test.ts +123 -0
- package/test/plugin/provider-deepinfra.test.ts +159 -0
- package/test/plugin/provider-dynamic.test.ts +184 -0
- package/test/plugin/provider-gateway.test.ts +115 -0
- package/test/plugin/provider-github-copilot.test.ts +278 -0
- package/test/plugin/provider-gitlab.test.ts +308 -0
- package/test/plugin/provider-google-vertex-anthropic.test.ts +264 -0
- package/test/plugin/provider-google-vertex.test.ts +387 -0
- package/test/plugin/provider-google.test.ts +80 -0
- package/test/plugin/provider-groq.test.ts +121 -0
- package/test/plugin/provider-kilo.test.ts +99 -0
- package/test/plugin/provider-llmgateway.test.ts +80 -0
- package/test/plugin/provider-mistral.test.ts +129 -0
- package/test/plugin/provider-nvidia.test.ts +97 -0
- package/test/plugin/provider-openai-compatible.test.ts +107 -0
- package/test/plugin/provider-openai.test.ts +176 -0
- package/test/plugin/provider-opencode.test.ts +423 -0
- package/test/plugin/provider-openrouter.test.ts +113 -0
- package/test/plugin/provider-perplexity.test.ts +122 -0
- package/test/plugin/provider-sap-ai-core.test.ts +159 -0
- package/test/plugin/provider-snowflake-cortex.test.ts +261 -0
- package/test/plugin/provider-togetherai.test.ts +129 -0
- package/test/plugin/provider-venice.test.ts +115 -0
- package/test/plugin/provider-vercel.test.ts +83 -0
- package/test/plugin/provider-xai.test.ts +125 -0
- package/test/plugin/provider-zenmux.test.ts +110 -0
- package/test/plugin/skill.test.ts +25 -0
- package/test/plugin/variant.test.ts +67 -0
- package/test/plugin.test.ts +71 -0
- package/test/policy.test.ts +85 -0
- package/test/preload.ts +5 -0
- package/test/process/process.test.ts +368 -0
- package/test/project-copy.test.ts +400 -0
- package/test/project-directories.test.ts +61 -0
- package/test/project.test.ts +221 -0
- package/test/provider-mistral.test.ts +282 -0
- package/test/provider-xai-responses.test.ts +32 -0
- package/test/pty/info-schema.test.ts +32 -0
- package/test/pty/protocol.test.ts +27 -0
- package/test/pty/pty-session.test.ts +240 -0
- package/test/pty/ticket.test.ts +62 -0
- package/test/question.test.ts +114 -0
- package/test/reference-guidance.test.ts +76 -0
- package/test/reference.test.ts +78 -0
- package/test/repository-cache.test.ts +153 -0
- package/test/repository.test.ts +71 -0
- package/test/ripgrep.test.ts +65 -0
- package/test/session-compaction.test.ts +27 -0
- package/test/session-create.test.ts +425 -0
- package/test/session-history.test.ts +165 -0
- package/test/session-projector.test.ts +533 -0
- package/test/session-prompt.test.ts +584 -0
- package/test/session-run-coordinator.test.ts +418 -0
- package/test/session-runner-message.test.ts +501 -0
- package/test/session-runner-model.test.ts +347 -0
- package/test/session-runner-recorded.test.ts +193 -0
- package/test/session-runner-tool-events.test.ts +136 -0
- package/test/session-runner-tool-registry.test.ts +452 -0
- package/test/session-runner.test.ts +3365 -0
- package/test/session-todo.test.ts +94 -0
- package/test/session-tool-progress.test.ts +158 -0
- package/test/shared-schema.test.ts +206 -0
- package/test/shell.test.ts +108 -0
- package/test/skill/guidance.test.ts +144 -0
- package/test/skill-discovery.test.ts +165 -0
- package/test/skill.test.ts +125 -0
- package/test/snapshot.test.ts +178 -0
- package/test/state.test.ts +115 -0
- package/test/system-context/builtins.test.ts +129 -0
- package/test/system-context/index.test.ts +307 -0
- package/test/system-context/registry.test.ts +114 -0
- package/test/tool-apply-patch.test.ts +436 -0
- package/test/tool-bash.test.ts +437 -0
- package/test/tool-edit.test.ts +434 -0
- package/test/tool-output-store.test.ts +247 -0
- package/test/tool-question.test.ts +162 -0
- package/test/tool-read-filesystem.test.ts +118 -0
- package/test/tool-read.test.ts +674 -0
- package/test/tool-skill.test.ts +149 -0
- package/test/tool-todowrite.test.ts +125 -0
- package/test/tool-webfetch.test.ts +281 -0
- package/test/tool-websearch.test.ts +316 -0
- package/test/tool-write.test.ts +305 -0
- package/test/util/effect-flock.test.ts +392 -0
- package/test/util/flock.test.ts +428 -0
- package/test/util/which.test.ts +100 -0
- package/tsconfig.json +7 -0
package/src/ripgrep.ts
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
export * as Ripgrep from "./ripgrep"
|
|
2
|
+
|
|
3
|
+
import { Context, Effect, Fiber, Layer, Schema, Stream } from "effect"
|
|
4
|
+
import { ChildProcess } from "effect/unstable/process"
|
|
5
|
+
import { Entry, Match } from "@neurocode-ai/schema/filesystem"
|
|
6
|
+
import { makeGlobalNode } from "./effect/app-node"
|
|
7
|
+
import { AppProcess, collectStream, waitForAbort } from "./process"
|
|
8
|
+
import { NonNegativeInt, PositiveInt, RelativePath } from "./schema"
|
|
9
|
+
import { RipgrepBinary } from "./ripgrep/binary"
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Small core-owned ripgrep execution adapter. It deliberately exposes raw
|
|
13
|
+
* process-oriented rows, not model text or permission behavior. Search maps
|
|
14
|
+
* these rows into filesystem results; leaf tools own
|
|
15
|
+
* presentation and permission prompts.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const ERROR_BYTES = 8 * 1024
|
|
19
|
+
const MAX_RECORD_BYTES = 64 * 1024
|
|
20
|
+
const MAX_SUBMATCHES = 100
|
|
21
|
+
|
|
22
|
+
const RawMatch = Schema.Struct({
|
|
23
|
+
type: Schema.Literal("match"),
|
|
24
|
+
data: Schema.Struct({
|
|
25
|
+
path: Schema.Struct({ text: Schema.String }),
|
|
26
|
+
lines: Schema.Struct({ text: Schema.String }),
|
|
27
|
+
line_number: PositiveInt,
|
|
28
|
+
absolute_offset: NonNegativeInt,
|
|
29
|
+
submatches: Schema.Array(
|
|
30
|
+
Schema.Struct({
|
|
31
|
+
match: Schema.Struct({ text: Schema.String }),
|
|
32
|
+
start: NonNegativeInt,
|
|
33
|
+
end: NonNegativeInt,
|
|
34
|
+
}),
|
|
35
|
+
),
|
|
36
|
+
}),
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
type RawMatchData = (typeof RawMatch.Type)["data"]
|
|
40
|
+
|
|
41
|
+
export class Error extends Schema.TaggedErrorClass<Error>()("Ripgrep.Error", {
|
|
42
|
+
message: Schema.String,
|
|
43
|
+
cause: Schema.optional(Schema.Defect()),
|
|
44
|
+
}) {}
|
|
45
|
+
|
|
46
|
+
export class InvalidPatternError extends Schema.TaggedErrorClass<InvalidPatternError>()("Ripgrep.InvalidPatternError", {
|
|
47
|
+
pattern: Schema.String,
|
|
48
|
+
message: Schema.String,
|
|
49
|
+
}) {}
|
|
50
|
+
|
|
51
|
+
export interface FindInput {
|
|
52
|
+
readonly cwd: string
|
|
53
|
+
readonly pattern: string
|
|
54
|
+
readonly limit: number
|
|
55
|
+
readonly hidden?: boolean
|
|
56
|
+
readonly follow?: boolean
|
|
57
|
+
readonly signal?: AbortSignal
|
|
58
|
+
readonly onEntry?: (entry: Entry) => Effect.Effect<void>
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface GlobInput {
|
|
62
|
+
readonly cwd: string
|
|
63
|
+
readonly pattern: string
|
|
64
|
+
readonly limit: number
|
|
65
|
+
readonly hidden?: boolean
|
|
66
|
+
readonly follow?: boolean
|
|
67
|
+
readonly signal?: AbortSignal
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface GrepInput {
|
|
71
|
+
readonly cwd: string
|
|
72
|
+
readonly pattern: string
|
|
73
|
+
readonly file?: string
|
|
74
|
+
readonly include?: string
|
|
75
|
+
readonly limit: number
|
|
76
|
+
readonly signal?: AbortSignal
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface Interface {
|
|
80
|
+
readonly find: (input: FindInput) => Effect.Effect<readonly Entry[], Error>
|
|
81
|
+
readonly glob: (input: GlobInput) => Effect.Effect<readonly Entry[], Error>
|
|
82
|
+
readonly grep: (input: GrepInput) => Effect.Effect<readonly Match[], Error | InvalidPatternError>
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export class Service extends Context.Service<Service, Interface>()("@neurocode/v2/Ripgrep") {}
|
|
86
|
+
|
|
87
|
+
const failure = (message: string, cause?: unknown) => new Error({ message, cause })
|
|
88
|
+
|
|
89
|
+
const isInvalidPattern = (stderr: string) =>
|
|
90
|
+
stderr.includes("regex parse error") || stderr.includes("error parsing regex")
|
|
91
|
+
|
|
92
|
+
const layer = Layer.effect(
|
|
93
|
+
Service,
|
|
94
|
+
Effect.gen(function* () {
|
|
95
|
+
const process = yield* AppProcess.Service
|
|
96
|
+
const binary = yield* RipgrepBinary.Service
|
|
97
|
+
|
|
98
|
+
const run = <A>(input: {
|
|
99
|
+
readonly cwd: string
|
|
100
|
+
readonly args: string[]
|
|
101
|
+
readonly limit: number
|
|
102
|
+
readonly signal?: AbortSignal
|
|
103
|
+
readonly parse: (line: string) => Effect.Effect<A | undefined, Error>
|
|
104
|
+
readonly pattern?: string
|
|
105
|
+
readonly onItem?: (item: A) => Effect.Effect<void>
|
|
106
|
+
}) => {
|
|
107
|
+
const program = Effect.scoped(
|
|
108
|
+
Effect.gen(function* () {
|
|
109
|
+
const handle = yield* process.spawn(
|
|
110
|
+
ChildProcess.make(yield* binary.filepath, input.args, { cwd: input.cwd, extendEnv: true, stdin: "ignore" }),
|
|
111
|
+
)
|
|
112
|
+
const stderrFiber = yield* collectStream(handle.stderr, ERROR_BYTES).pipe(
|
|
113
|
+
Effect.map((output) => output.buffer.toString("utf8")),
|
|
114
|
+
Effect.forkScoped,
|
|
115
|
+
)
|
|
116
|
+
let observed = 0
|
|
117
|
+
const rows = yield* Stream.decodeText(handle.stdout).pipe(
|
|
118
|
+
Stream.splitLines,
|
|
119
|
+
Stream.filter((line) => line.length > 0),
|
|
120
|
+
Stream.mapEffect(input.parse),
|
|
121
|
+
Stream.filter((row): row is A => row !== undefined),
|
|
122
|
+
Stream.tap((row) => {
|
|
123
|
+
if (!input.onItem || observed++ >= input.limit) return Effect.void
|
|
124
|
+
return input.onItem(row)
|
|
125
|
+
}),
|
|
126
|
+
Stream.take(input.limit + 1),
|
|
127
|
+
Stream.runCollect,
|
|
128
|
+
Effect.map((chunk) => [...chunk]),
|
|
129
|
+
)
|
|
130
|
+
const truncated = rows.length > input.limit
|
|
131
|
+
if (truncated) return { items: rows.slice(0, input.limit), truncated, partial: false }
|
|
132
|
+
|
|
133
|
+
const code = yield* handle.exitCode
|
|
134
|
+
const stderr = yield* Fiber.join(stderrFiber)
|
|
135
|
+
if (input.pattern && code === 2 && isInvalidPattern(stderr)) {
|
|
136
|
+
return yield* new InvalidPatternError({ pattern: input.pattern, message: stderr.trim() })
|
|
137
|
+
}
|
|
138
|
+
if (code !== 0 && code !== 1 && code !== 2) {
|
|
139
|
+
return yield* failure(stderr.trim() || `ripgrep failed with code ${code}`)
|
|
140
|
+
}
|
|
141
|
+
return { items: code === 1 ? [] : rows, truncated: false, partial: code === 2 }
|
|
142
|
+
}),
|
|
143
|
+
)
|
|
144
|
+
const abortable = input.signal ? program.pipe(Effect.raceFirst(waitForAbort(input.signal))) : program
|
|
145
|
+
return abortable.pipe(
|
|
146
|
+
Effect.mapError((cause) =>
|
|
147
|
+
cause instanceof Error || cause instanceof InvalidPatternError
|
|
148
|
+
? cause
|
|
149
|
+
: failure("ripgrep execution failed", cause),
|
|
150
|
+
),
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return Service.of({
|
|
155
|
+
glob: (input) =>
|
|
156
|
+
run<string>({
|
|
157
|
+
cwd: input.cwd,
|
|
158
|
+
limit: input.limit,
|
|
159
|
+
signal: input.signal,
|
|
160
|
+
args: [
|
|
161
|
+
"--no-config",
|
|
162
|
+
"--files",
|
|
163
|
+
...(input.hidden ? ["--hidden"] : []),
|
|
164
|
+
...(input.follow ? ["--follow"] : []),
|
|
165
|
+
`--glob=${input.pattern}`,
|
|
166
|
+
"--glob=!**/.git/**",
|
|
167
|
+
".",
|
|
168
|
+
],
|
|
169
|
+
parse: (line) =>
|
|
170
|
+
Effect.succeed(
|
|
171
|
+
line
|
|
172
|
+
.replace(/^(?:\.[\\/])+/u, "")
|
|
173
|
+
.replace(/^[\\/]+/u, "")
|
|
174
|
+
.replaceAll("\\", "/"),
|
|
175
|
+
),
|
|
176
|
+
}).pipe(
|
|
177
|
+
Effect.map((result) =>
|
|
178
|
+
result.items.map((relative) =>
|
|
179
|
+
Entry.make({
|
|
180
|
+
path: RelativePath.make(relative),
|
|
181
|
+
type: "file",
|
|
182
|
+
}),
|
|
183
|
+
),
|
|
184
|
+
),
|
|
185
|
+
Effect.catchTag("Ripgrep.InvalidPatternError", (cause) => Effect.fail(failure(cause.message, cause))),
|
|
186
|
+
),
|
|
187
|
+
find: (input) =>
|
|
188
|
+
run<Entry>({
|
|
189
|
+
cwd: input.cwd,
|
|
190
|
+
limit: input.limit,
|
|
191
|
+
signal: input.signal,
|
|
192
|
+
args: [
|
|
193
|
+
"--no-config",
|
|
194
|
+
"--files",
|
|
195
|
+
...(input.hidden ? ["--hidden"] : []),
|
|
196
|
+
...(input.follow ? ["--follow"] : []),
|
|
197
|
+
...(input.pattern === "*" ? [] : [`--glob=${input.pattern}`]),
|
|
198
|
+
"--glob=!**/.git/**",
|
|
199
|
+
".",
|
|
200
|
+
],
|
|
201
|
+
parse: (line) => {
|
|
202
|
+
const relative = line
|
|
203
|
+
.replace(/^(?:\.[\\/])+/u, "")
|
|
204
|
+
.replace(/^[\\/]+/u, "")
|
|
205
|
+
.replaceAll("\\", "/")
|
|
206
|
+
return Effect.succeed(
|
|
207
|
+
Entry.make({
|
|
208
|
+
path: RelativePath.make(relative),
|
|
209
|
+
type: "file",
|
|
210
|
+
}),
|
|
211
|
+
)
|
|
212
|
+
},
|
|
213
|
+
onItem: input.onEntry,
|
|
214
|
+
}).pipe(
|
|
215
|
+
Effect.map((result) => result.items),
|
|
216
|
+
Effect.catchTag("Ripgrep.InvalidPatternError", (cause) => Effect.fail(failure(cause.message, cause))),
|
|
217
|
+
),
|
|
218
|
+
grep: (input) =>
|
|
219
|
+
run<RawMatchData>({
|
|
220
|
+
...input,
|
|
221
|
+
args: [
|
|
222
|
+
"--no-config",
|
|
223
|
+
"--json",
|
|
224
|
+
"--hidden",
|
|
225
|
+
"--no-messages",
|
|
226
|
+
...(input.include ? [`--glob=${input.include}`] : []),
|
|
227
|
+
"--glob=!**/.git/**",
|
|
228
|
+
"--",
|
|
229
|
+
input.pattern,
|
|
230
|
+
input.file ?? ".",
|
|
231
|
+
],
|
|
232
|
+
parse: (line) =>
|
|
233
|
+
(Buffer.byteLength(line, "utf8") > MAX_RECORD_BYTES
|
|
234
|
+
? Effect.fail(failure(`Ripgrep JSON record exceeded ${MAX_RECORD_BYTES} bytes`))
|
|
235
|
+
: Effect.try({
|
|
236
|
+
try: () => JSON.parse(line) as unknown,
|
|
237
|
+
catch: (cause) => failure("Invalid ripgrep JSON output", cause),
|
|
238
|
+
})
|
|
239
|
+
).pipe(
|
|
240
|
+
Effect.flatMap((json) => {
|
|
241
|
+
if (!json || typeof json !== "object" || !("type" in json) || json.type !== "match")
|
|
242
|
+
return Effect.succeed(undefined)
|
|
243
|
+
return Schema.decodeUnknownEffect(RawMatch)(json).pipe(
|
|
244
|
+
Effect.map((match) => ({
|
|
245
|
+
...match.data,
|
|
246
|
+
path: { text: match.data.path.text.replace(/^\.[\\/]/, "") },
|
|
247
|
+
submatches: match.data.submatches.slice(0, MAX_SUBMATCHES),
|
|
248
|
+
})),
|
|
249
|
+
Effect.mapError((cause) => failure("Invalid ripgrep match output", cause)),
|
|
250
|
+
)
|
|
251
|
+
}),
|
|
252
|
+
),
|
|
253
|
+
}).pipe(
|
|
254
|
+
Effect.map((result) =>
|
|
255
|
+
result.items.map((match) => {
|
|
256
|
+
const relative = match.path.text
|
|
257
|
+
.replace(/^(?:\.[\\/])+/u, "")
|
|
258
|
+
.replace(/^[\\/]+/u, "")
|
|
259
|
+
.replaceAll("\\", "/")
|
|
260
|
+
return Match.make({
|
|
261
|
+
entry: Entry.make({
|
|
262
|
+
path: RelativePath.make(relative),
|
|
263
|
+
type: "file",
|
|
264
|
+
}),
|
|
265
|
+
line: match.line_number,
|
|
266
|
+
offset: match.absolute_offset,
|
|
267
|
+
text: match.lines.text.length > 2_000 ? match.lines.text.slice(0, 2_000) + "..." : match.lines.text,
|
|
268
|
+
submatches: match.submatches.map((submatch) => ({
|
|
269
|
+
text: submatch.match.text,
|
|
270
|
+
start: submatch.start,
|
|
271
|
+
end: submatch.end,
|
|
272
|
+
})),
|
|
273
|
+
})
|
|
274
|
+
}),
|
|
275
|
+
),
|
|
276
|
+
),
|
|
277
|
+
})
|
|
278
|
+
}),
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
export const node = makeGlobalNode({ service: Service, layer: layer, deps: [RipgrepBinary.node, AppProcess.node] })
|
package/src/schema.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Schema } from "effect"
|
|
2
|
+
import {
|
|
3
|
+
AbsolutePath,
|
|
4
|
+
DateTimeUtcFromMillis,
|
|
5
|
+
NonNegativeInt,
|
|
6
|
+
optional,
|
|
7
|
+
PositiveInt,
|
|
8
|
+
RelativePath,
|
|
9
|
+
statics,
|
|
10
|
+
} from "@neurocode-ai/schema/schema"
|
|
11
|
+
|
|
12
|
+
export { AbsolutePath, DateTimeUtcFromMillis, NonNegativeInt, optional, PositiveInt, RelativePath, statics }
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Strip `readonly` from a nested type. Stand-in for `effect`'s `Types.DeepMutable`
|
|
16
|
+
* until `effect:core/x228my` ("Types.DeepMutable widens unknown to `{}`") lands.
|
|
17
|
+
*
|
|
18
|
+
* The upstream version falls through `unknown` into `{ -readonly [K in keyof T]: ... }`
|
|
19
|
+
* where `keyof unknown = never`, so `unknown` collapses to `{}`. This local
|
|
20
|
+
* version gates the object branch on `extends object` (which `unknown` does
|
|
21
|
+
* not) so `unknown` passes through untouched.
|
|
22
|
+
*
|
|
23
|
+
* Primitive bailout matches upstream — without it, branded strings like
|
|
24
|
+
* `string & Brand<"SessionID">` fall into the object branch and get their
|
|
25
|
+
* prototype methods walked.
|
|
26
|
+
*
|
|
27
|
+
* Tuple branch preserves readonly tuples (e.g. `ConfigPlugin.Spec`'s
|
|
28
|
+
* `readonly [string, Options]`); the general array branch would otherwise
|
|
29
|
+
* widen them to unbounded arrays.
|
|
30
|
+
*/
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
32
|
+
export type DeepMutable<T> = T extends string | number | boolean | bigint | symbol | Function
|
|
33
|
+
? T
|
|
34
|
+
: T extends readonly [unknown, ...unknown[]]
|
|
35
|
+
? { -readonly [K in keyof T]: DeepMutable<T[K]> }
|
|
36
|
+
: T extends readonly (infer U)[]
|
|
37
|
+
? DeepMutable<U>[]
|
|
38
|
+
: T extends object
|
|
39
|
+
? { -readonly [K in keyof T]: DeepMutable<T[K]> }
|
|
40
|
+
: T
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Nominal wrapper for scalar types. The class itself is a valid schema —
|
|
44
|
+
* pass it directly to `Schema.decode`, `Schema.decodeEffect`, etc.
|
|
45
|
+
*
|
|
46
|
+
* Overrides `~type.make` on the derived `Schema.Opaque` so `Schema.Schema.Type`
|
|
47
|
+
* of a field using this newtype resolves to `Self` rather than the underlying
|
|
48
|
+
* branded phantom. Without that override, passing a class instance to code
|
|
49
|
+
* typed against `Schema.Schema.Type<FieldSchema>` would require a cast even
|
|
50
|
+
* though the values are structurally equivalent at runtime.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* class QuestionID extends Newtype<QuestionID>()("QuestionID", Schema.String) {
|
|
54
|
+
* static make(id: string): QuestionID {
|
|
55
|
+
* return this.make(id)
|
|
56
|
+
* }
|
|
57
|
+
* }
|
|
58
|
+
*
|
|
59
|
+
* Schema.decodeEffect(QuestionID)(input)
|
|
60
|
+
*/
|
|
61
|
+
export function Newtype<Self>() {
|
|
62
|
+
return <const Tag extends string, S extends Schema.Top>(tag: Tag, schema: S) => {
|
|
63
|
+
abstract class Base {
|
|
64
|
+
declare readonly _newtype: Tag
|
|
65
|
+
|
|
66
|
+
static make(value: Schema.Schema.Type<S>): Self {
|
|
67
|
+
return value as unknown as Self
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
Object.setPrototypeOf(Base, schema)
|
|
72
|
+
|
|
73
|
+
return Base as unknown as (abstract new (_: never) => { readonly _newtype: Tag }) & {
|
|
74
|
+
readonly make: (value: Schema.Schema.Type<S>) => Self
|
|
75
|
+
} & Omit<Schema.Opaque<Self, S, {}>, "make" | "~type.make"> & {
|
|
76
|
+
readonly "~type.make": Self
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
export * as SessionCompaction from "./compaction"
|
|
2
|
+
|
|
3
|
+
import { LLM, LLMError, LLMEvent, Message, type LLMRequest, type Model } from "@neurocode-ai/llm"
|
|
4
|
+
import { DateTime, Effect, Stream } from "effect"
|
|
5
|
+
import type { Config } from "../config"
|
|
6
|
+
import type { EventV2 } from "../event"
|
|
7
|
+
import { SessionEvent } from "./event"
|
|
8
|
+
import { SessionMessage } from "./message"
|
|
9
|
+
import { SessionSchema } from "./schema"
|
|
10
|
+
import { Token } from "../util/token"
|
|
11
|
+
|
|
12
|
+
const DEFAULT_BUFFER = 20_000
|
|
13
|
+
const DEFAULT_KEEP_TOKENS = 8_000
|
|
14
|
+
const TOOL_OUTPUT_MAX_CHARS = 2_000
|
|
15
|
+
const SUMMARY_OUTPUT_TOKENS = 4_096
|
|
16
|
+
const SUMMARY_TEMPLATE = `Output exactly the Markdown structure shown inside <template> and keep the section order unchanged. Do not include the <template> tags in your response.
|
|
17
|
+
<template>
|
|
18
|
+
## Objective
|
|
19
|
+
- [one or two brief sentences describing what the user is trying to accomplish]
|
|
20
|
+
|
|
21
|
+
## Important Details
|
|
22
|
+
- [constraints/preferences, decisions and why, important facts/assumptions, exact context needed to continue, or "(none)"]
|
|
23
|
+
|
|
24
|
+
## Work State
|
|
25
|
+
### Completed
|
|
26
|
+
- [finished work, verified facts, or changes made; otherwise "(none)"]
|
|
27
|
+
|
|
28
|
+
### Active
|
|
29
|
+
- [current work, partial changes, or investigation state; otherwise "(none)"]
|
|
30
|
+
|
|
31
|
+
### Blocked
|
|
32
|
+
- [blockers, failing commands, or unknowns; otherwise "(none)"]
|
|
33
|
+
|
|
34
|
+
## Next Move
|
|
35
|
+
1. [immediate concrete action, or "(none)"]
|
|
36
|
+
2. [next action if known, or "(none)"]
|
|
37
|
+
|
|
38
|
+
## Relevant Files
|
|
39
|
+
- [file or directory path: why it matters, or "(none)"]
|
|
40
|
+
</template>
|
|
41
|
+
|
|
42
|
+
Rules:
|
|
43
|
+
- Keep every section, even when empty.
|
|
44
|
+
- Use terse bullets, not prose paragraphs.
|
|
45
|
+
- Preserve exact file paths, symbols, commands, error strings, URLs, and identifiers when known.
|
|
46
|
+
- Do not mention the summary process or that context was compacted.`
|
|
47
|
+
|
|
48
|
+
type Entry = {
|
|
49
|
+
readonly seq: number
|
|
50
|
+
readonly message: SessionMessage.Message
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
type Settings = {
|
|
54
|
+
readonly auto: boolean
|
|
55
|
+
readonly buffer: number
|
|
56
|
+
readonly tokens: number
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
type Dependencies = {
|
|
60
|
+
readonly events: EventV2.Interface
|
|
61
|
+
readonly llm: {
|
|
62
|
+
readonly stream: (request: LLMRequest) => Stream.Stream<LLMEvent, LLMError>
|
|
63
|
+
}
|
|
64
|
+
readonly config: readonly Config.Entry[]
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
type Input = {
|
|
68
|
+
readonly sessionID: SessionSchema.ID
|
|
69
|
+
readonly entries: readonly Entry[]
|
|
70
|
+
readonly model: Model
|
|
71
|
+
readonly request: LLMRequest
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const estimate = (value: unknown) => Token.estimate(JSON.stringify(value))
|
|
75
|
+
|
|
76
|
+
const truncate = (value: string) =>
|
|
77
|
+
value.length <= TOOL_OUTPUT_MAX_CHARS ? value : `${value.slice(0, TOOL_OUTPUT_MAX_CHARS)}\n[truncated]`
|
|
78
|
+
|
|
79
|
+
export const serializeToolContent = (content: SessionMessage.ToolStateCompleted["content"]) =>
|
|
80
|
+
content
|
|
81
|
+
.map((item) =>
|
|
82
|
+
item.type === "text" ? item.text : `[Attached ${item.mime}${item.name === undefined ? "" : `: ${item.name}`}]`,
|
|
83
|
+
)
|
|
84
|
+
.join("\n")
|
|
85
|
+
|
|
86
|
+
const serialize = (message: SessionMessage.Message) => {
|
|
87
|
+
if (message.type === "user") {
|
|
88
|
+
const files = message.files?.map((file) => `[Attached ${file.mime}: ${file.name ?? file.uri}]`) ?? []
|
|
89
|
+
return [`[User]: ${message.text}`, ...files].join("\n")
|
|
90
|
+
}
|
|
91
|
+
if (message.type === "assistant") {
|
|
92
|
+
return message.content
|
|
93
|
+
.flatMap((part) => {
|
|
94
|
+
if (part.type === "text") return [`[Assistant]: ${part.text}`]
|
|
95
|
+
if (part.type === "reasoning") return part.text ? [`[Assistant reasoning]: ${part.text}`] : []
|
|
96
|
+
const input = typeof part.state.input === "string" ? part.state.input : JSON.stringify(part.state.input)
|
|
97
|
+
if (part.state.status === "completed")
|
|
98
|
+
return [
|
|
99
|
+
`[Assistant tool call]: ${part.name}(${input})`,
|
|
100
|
+
`[Tool result]: ${truncate(serializeToolContent(part.state.content))}`,
|
|
101
|
+
]
|
|
102
|
+
if (part.state.status === "error")
|
|
103
|
+
return [`[Assistant tool call]: ${part.name}(${input})`, `[Tool error]: ${part.state.error.message}`]
|
|
104
|
+
return [`[Assistant tool call]: ${part.name}(${input})`]
|
|
105
|
+
})
|
|
106
|
+
.join("\n")
|
|
107
|
+
}
|
|
108
|
+
if (message.type === "system") return `[System update]: ${message.text}`
|
|
109
|
+
if (message.type === "synthetic") return `[Synthetic context]: ${message.text}`
|
|
110
|
+
if (message.type === "shell") return `[Shell]: ${message.command}\n${truncate(message.output)}`
|
|
111
|
+
return ""
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const settings = (documents: readonly Config.Entry[]) => {
|
|
115
|
+
const configured = documents
|
|
116
|
+
.filter((entry): entry is Config.Document => entry.type === "document")
|
|
117
|
+
.flatMap((entry) => (entry.info.compaction ? [entry.info.compaction] : []))
|
|
118
|
+
return configured.reduce<Settings>(
|
|
119
|
+
(result, current) => ({
|
|
120
|
+
auto: current.auto ?? result.auto,
|
|
121
|
+
buffer: current.buffer ?? result.buffer,
|
|
122
|
+
tokens: current.keep?.tokens ?? result.tokens,
|
|
123
|
+
}),
|
|
124
|
+
{ auto: true, buffer: DEFAULT_BUFFER, tokens: DEFAULT_KEEP_TOKENS },
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const select = (
|
|
129
|
+
entries: readonly Entry[],
|
|
130
|
+
tokens: number,
|
|
131
|
+
): { readonly head: string; readonly recent: string } | undefined => {
|
|
132
|
+
const conversation = entries
|
|
133
|
+
.filter((entry) => entry.message.type !== "compaction")
|
|
134
|
+
.map((entry) => serialize(entry.message))
|
|
135
|
+
.filter(Boolean)
|
|
136
|
+
if (conversation.length === 0) return
|
|
137
|
+
let total = 0
|
|
138
|
+
let split = conversation.length
|
|
139
|
+
let splitPrefix = ""
|
|
140
|
+
let splitSuffix = ""
|
|
141
|
+
for (let index = conversation.length - 1; index >= 0; index--) {
|
|
142
|
+
const next = total + Token.estimate(conversation[index])
|
|
143
|
+
if (next > tokens) {
|
|
144
|
+
const remaining = Math.max(0, tokens - total) * 4
|
|
145
|
+
if (remaining > 0) {
|
|
146
|
+
splitPrefix = conversation[index].slice(0, -remaining)
|
|
147
|
+
splitSuffix = conversation[index].slice(-remaining)
|
|
148
|
+
split = index + 1
|
|
149
|
+
}
|
|
150
|
+
break
|
|
151
|
+
}
|
|
152
|
+
total = next
|
|
153
|
+
split = index
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
head: [...conversation.slice(0, split), splitPrefix].filter(Boolean).join("\n\n"),
|
|
157
|
+
recent: [splitSuffix, ...conversation.slice(split)].filter(Boolean).join("\n\n"),
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export const buildPrompt = (input: { readonly previousSummary?: string; readonly context: readonly string[] }) =>
|
|
162
|
+
[
|
|
163
|
+
input.previousSummary
|
|
164
|
+
? `Update the anchored summary below using the conversation history above.\nPreserve still-true details, remove stale details, and merge in the new facts.\n<previous-summary>\n${input.previousSummary}\n</previous-summary>`
|
|
165
|
+
: "Create a new anchored summary from the conversation history.",
|
|
166
|
+
SUMMARY_TEMPLATE,
|
|
167
|
+
...input.context,
|
|
168
|
+
].join("\n\n")
|
|
169
|
+
|
|
170
|
+
export const make = (dependencies: Dependencies) => {
|
|
171
|
+
const config = settings(dependencies.config)
|
|
172
|
+
const compactAfterOverflow = Effect.fn("SessionCompaction.compactAfterOverflow")(function* (input: Input) {
|
|
173
|
+
const context = input.model.route.defaults.limits?.context
|
|
174
|
+
if (context === undefined || context <= 0) return false
|
|
175
|
+
const output = input.request.generation?.maxTokens ?? input.model.route.defaults.limits?.output ?? 0
|
|
176
|
+
const selected = select(input.entries, config.tokens)
|
|
177
|
+
const previousSummary = input.entries.find((entry) => entry.message.type === "compaction")?.message
|
|
178
|
+
if (!selected || (selected.head.length === 0 && previousSummary?.type !== "compaction")) return false
|
|
179
|
+
const summaryPrompt = buildPrompt({
|
|
180
|
+
previousSummary: previousSummary?.type === "compaction" ? previousSummary.summary : undefined,
|
|
181
|
+
context: [previousSummary?.type === "compaction" ? previousSummary.recent : "", selected.head].filter(Boolean),
|
|
182
|
+
})
|
|
183
|
+
const summaryOutput = Math.min(output || SUMMARY_OUTPUT_TOKENS, SUMMARY_OUTPUT_TOKENS)
|
|
184
|
+
if (Token.estimate(summaryPrompt) > context - summaryOutput) return false
|
|
185
|
+
const messageID = SessionMessage.ID.create()
|
|
186
|
+
yield* dependencies.events.publish(SessionEvent.Compaction.Started, {
|
|
187
|
+
sessionID: input.sessionID,
|
|
188
|
+
messageID,
|
|
189
|
+
timestamp: yield* DateTime.now,
|
|
190
|
+
reason: "auto",
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
const chunks: string[] = []
|
|
194
|
+
let failed = false
|
|
195
|
+
const summarized = yield* dependencies.llm
|
|
196
|
+
.stream(
|
|
197
|
+
LLM.request({
|
|
198
|
+
model: input.model,
|
|
199
|
+
messages: [Message.user(summaryPrompt)],
|
|
200
|
+
tools: [],
|
|
201
|
+
generation: { maxTokens: summaryOutput },
|
|
202
|
+
}),
|
|
203
|
+
)
|
|
204
|
+
.pipe(
|
|
205
|
+
Stream.runForEach((event) => {
|
|
206
|
+
if (LLMEvent.is.providerError(event)) failed = true
|
|
207
|
+
if (LLMEvent.is.textDelta(event)) chunks.push(event.text)
|
|
208
|
+
return Effect.void
|
|
209
|
+
}),
|
|
210
|
+
Effect.as(true),
|
|
211
|
+
Effect.catchTag("LLM.Error", () => Effect.succeed(false)),
|
|
212
|
+
)
|
|
213
|
+
const summary = chunks.join("")
|
|
214
|
+
if (!summarized || failed || !summary.trim()) return false
|
|
215
|
+
yield* dependencies.events.publish(SessionEvent.Compaction.Ended, {
|
|
216
|
+
sessionID: input.sessionID,
|
|
217
|
+
messageID,
|
|
218
|
+
timestamp: yield* DateTime.now,
|
|
219
|
+
reason: "auto",
|
|
220
|
+
text: summary,
|
|
221
|
+
recent: selected.recent,
|
|
222
|
+
})
|
|
223
|
+
return true
|
|
224
|
+
})
|
|
225
|
+
const compactIfNeeded = Effect.fn("SessionCompaction.compactIfNeeded")(function* (input: Input) {
|
|
226
|
+
if (!config.auto) return false
|
|
227
|
+
const context = input.model.route.defaults.limits?.context
|
|
228
|
+
if (context === undefined || context <= 0) return false
|
|
229
|
+
const output = input.request.generation?.maxTokens ?? input.model.route.defaults.limits?.output ?? 0
|
|
230
|
+
if (
|
|
231
|
+
estimate({ system: input.request.system, messages: input.request.messages, tools: input.request.tools }) <=
|
|
232
|
+
context - Math.max(output, config.buffer)
|
|
233
|
+
)
|
|
234
|
+
return false
|
|
235
|
+
return yield* compactAfterOverflow(input)
|
|
236
|
+
})
|
|
237
|
+
return {
|
|
238
|
+
compactIfNeeded,
|
|
239
|
+
compactAfterOverflow,
|
|
240
|
+
}
|
|
241
|
+
}
|