saeeol 1.3.0 → 1.3.1
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/.turbo/turbo-typecheck.log +1 -0
- package/AGENTS.md +72 -0
- package/BUN_SHELL_MIGRATION_PLAN.md +136 -0
- package/Dockerfile +18 -0
- package/assets/saeeol.ico +0 -0
- package/bin/saeeol.cjs +0 -0
- package/database.db +0 -0
- package/drizzle.config.ts +10 -0
- package/git +0 -0
- package/migration/20260127222353_familiar_lady_ursula/migration.sql +90 -0
- package/migration/20260127222353_familiar_lady_ursula/snapshot.json +796 -0
- package/migration/20260211171708_add_project_commands/migration.sql +1 -0
- package/migration/20260211171708_add_project_commands/snapshot.json +806 -0
- package/migration/20260213144116_wakeful_the_professor/migration.sql +11 -0
- package/migration/20260213144116_wakeful_the_professor/snapshot.json +897 -0
- package/migration/20260225215848_workspace/migration.sql +7 -0
- package/migration/20260225215848_workspace/snapshot.json +959 -0
- package/migration/20260227213759_add_session_workspace_id/migration.sql +2 -0
- package/migration/20260227213759_add_session_workspace_id/snapshot.json +983 -0
- package/migration/20260228203230_blue_harpoon/migration.sql +17 -0
- package/migration/20260228203230_blue_harpoon/snapshot.json +1102 -0
- package/migration/20260303231226_add_workspace_fields/migration.sql +5 -0
- package/migration/20260303231226_add_workspace_fields/snapshot.json +1013 -0
- package/migration/20260309230000_move_org_to_state/migration.sql +3 -0
- package/migration/20260309230000_move_org_to_state/snapshot.json +1156 -0
- package/migration/20260312043431_session_message_cursor/migration.sql +4 -0
- package/migration/20260312043431_session_message_cursor/snapshot.json +1168 -0
- package/migration/20260323234822_events/migration.sql +13 -0
- package/migration/20260323234822_events/snapshot.json +1271 -0
- package/migration/20260410174513_workspace-name/migration.sql +16 -0
- package/migration/20260410174513_workspace-name/snapshot.json +1271 -0
- package/migration/20260413175956_chief_energizer/migration.sql +13 -0
- package/migration/20260413175956_chief_energizer/snapshot.json +1399 -0
- package/migration/20260423070820_add_icon_url_override/migration.sql +2 -0
- package/migration/20260423070820_add_icon_url_override/snapshot.json +1409 -0
- package/migration/20260428004200_add_session_path/migration.sql +1 -0
- package/migration/20260428004200_add_session_path/snapshot.json +1419 -0
- package/npm/bin/saeeol +42 -0
- package/npm/package.json +39 -0
- package/npm/postinstall.js +162 -0
- package/package.json +201 -207
- package/parsers-config.ts +289 -0
- package/script/build.ts +393 -0
- package/script/check-migrations.ts +16 -0
- package/script/fix-node-pty.ts +34 -0
- package/script/generate.ts +23 -0
- package/script/postinstall.mjs +189 -0
- package/script/publish.ts +200 -0
- package/script/run-workspace-server +106 -0
- package/script/schema.ts +63 -0
- package/script/test-runner.ts +420 -0
- package/script/time.ts +6 -0
- package/script/trace-imports.ts +153 -0
- package/script/upgrade-opentui.ts +64 -0
- package/scripts/diff-sdk-types.sh +52 -0
- package/specs/effect/facades.md +221 -0
- package/specs/effect/http-api.md +401 -0
- package/specs/effect/instance-context.md +309 -0
- package/specs/effect/loose-ends.md +34 -0
- package/specs/effect/migration.md +299 -0
- package/specs/effect/routes.md +64 -0
- package/specs/effect/schema.md +399 -0
- package/specs/effect/server-package.md +668 -0
- package/specs/effect/tools.md +90 -0
- package/specs/tui-plugins.md +433 -0
- package/specs/v2/api.ts +67 -0
- package/specs/v2/keymappings.md +10 -0
- package/specs/v2/message-shape.md +136 -0
- package/src/acp/agent-message.ts +1 -1
- package/src/acp/agent-utils.ts +1 -1
- package/src/boxes/ansi.ts +17 -0
- package/src/boxes/atomic-write.ts +35 -0
- package/src/boxes/b64.ts +58 -0
- package/src/boxes/bash-security.ts +129 -0
- package/src/boxes/bom.ts +18 -0
- package/src/boxes/cancel.ts +16 -0
- package/src/boxes/chop.ts +12 -0
- package/src/boxes/clamp.ts +3 -0
- package/src/boxes/compact.ts +9 -0
- package/src/boxes/cost-tracker.ts +116 -0
- package/src/boxes/dataurl.ts +29 -0
- package/src/boxes/delay.ts +27 -0
- package/src/boxes/diff-apply.ts +53 -0
- package/src/boxes/disposable.ts +13 -0
- package/src/boxes/err.ts +34 -0
- package/src/boxes/human.ts +47 -0
- package/src/boxes/iife.ts +9 -0
- package/src/boxes/latch.ts +8 -0
- package/src/boxes/memory.ts +198 -0
- package/src/boxes/net.ts +16 -0
- package/src/boxes/plural.ts +4 -0
- package/src/boxes/puny.ts +21 -0
- package/src/boxes/retry.ts +49 -0
- package/src/boxes/rwlock.ts +41 -0
- package/src/boxes/schedule.ts +71 -0
- package/src/boxes/scope.ts +21 -0
- package/src/boxes/tokens.ts +9 -0
- package/src/boxes/ttl-cache.ts +63 -0
- package/src/boxes/typed-event.ts +51 -0
- package/src/boxes/uid.ts +50 -0
- package/src/boxes/wave6.test.ts +296 -0
- package/src/boxes/wildcard.ts +58 -0
- package/src/bus/global.ts +1 -1
- package/src/cli/cmd/github-run-api.ts +2 -2
- package/src/cli/cmd/run-events.ts +2 -2
- package/src/cli/cmd/tui/component/logo.tsx +1 -1
- package/src/cli/cmd/tui/component/prompt/use-prompt-memos.ts +2 -2
- package/src/cli/cmd/tui/context/app/editor-zed.ts +1 -1
- package/src/cli/cmd/tui/context/app/editor.ts +1 -1
- package/src/cli/cmd/tui/context/app/theme.tsx +1 -1
- package/src/cli/cmd/tui/util/revert-diff.ts +1 -1
- package/src/overlay/cli/cmd/roll-call-call.ts +1 -1
- package/src/overlay/cost-tracker/format.ts +1 -1
- package/src/overlay/cost-tracker/index.ts +4 -4
- package/src/overlay/cost-tracker/state.ts +2 -2
- package/src/overlay/cost-tracker/types.ts +2 -2
- package/src/overlay/memory/age.ts +1 -1
- package/src/overlay/memory/index.ts +4 -4
- package/src/overlay/memory/paths.ts +2 -2
- package/src/overlay/memory/scan.ts +1 -1
- package/src/overlay/memory/types.ts +2 -2
- package/src/overlay/tool/bash-security.ts +3 -3
- package/src/overlay/util/url.ts +1 -1
- package/src/plugin/codex-auth.ts +1 -1
- package/src/provider/model-cache.ts +2 -2
- package/src/provider/provider-resolve.ts +3 -3
- package/src/provider/transform-message.ts +1 -1
- package/src/server/routes/game.ts +284 -0
- package/src/server/server.ts +2 -0
- package/src/session/core/compaction/compaction-helpers.ts +1 -1
- package/src/session/core/compaction/compaction.ts +1 -1
- package/src/session/core/session.ts +2 -0
- package/src/sessions/ingest-queue.ts +2 -2
- package/src/sessions/remote-ws.ts +1 -1
- package/src/tool/workflow/question.ts +1 -1
- package/src/util/abort.ts +1 -1
- package/src/util/bom.ts +2 -2
- package/src/util/color.ts +1 -1
- package/src/util/data-url.ts +1 -1
- package/src/util/defer.ts +1 -1
- package/src/util/error.ts +2 -2
- package/src/util/filesystem.ts +2 -2
- package/src/util/format.ts +1 -1
- package/src/util/iife.ts +1 -1
- package/src/util/local-context.ts +1 -1
- package/src/util/locale.ts +2 -2
- package/src/util/lock.ts +1 -1
- package/src/util/network.ts +1 -1
- package/src/util/signal.ts +1 -1
- package/src/util/token.ts +1 -1
- package/src/util/wildcard.ts +1 -1
- package/sst-env.d.ts +10 -0
- package/test/AGENTS.md +133 -0
- package/test/account/repo.test.ts +352 -0
- package/test/account/service.test.ts +456 -0
- package/test/acp/agent-interface.test.ts +51 -0
- package/test/acp/event-subscription.test.ts +725 -0
- package/test/agent/agent.test.ts +890 -0
- package/test/auth/auth.test.ts +86 -0
- package/test/bun/registry.test.ts +75 -0
- package/test/bus/bus-effect.test.ts +161 -0
- package/test/bus/bus-integration.test.ts +87 -0
- package/test/bus/bus.test.ts +219 -0
- package/test/cli/account.test.ts +26 -0
- package/test/cli/auto-mode.test.ts +75 -0
- package/test/cli/bin-saeeol.test.ts +8 -0
- package/test/cli/cmd/tui/prompt-part.test.ts +47 -0
- package/test/cli/cmd/tui/prompt-traits.test.ts +38 -0
- package/test/cli/cmd/tui/sync.test.tsx +159 -0
- package/test/cli/error.test.ts +18 -0
- package/test/cli/github-action.test.ts +198 -0
- package/test/cli/github-remote.test.ts +85 -0
- package/test/cli/import.test.ts +97 -0
- package/test/cli/install-artifact.test.ts +72 -0
- package/test/cli/plugin-auth-picker.test.ts +120 -0
- package/test/cli/pr.test.ts +59 -0
- package/test/cli/tui/editor-context-zed.test.ts +356 -0
- package/test/cli/tui/editor-context.test.tsx +228 -0
- package/test/cli/tui/keybind-plugin.test.ts +90 -0
- package/test/cli/tui/markdown.test.ts +161 -0
- package/test/cli/tui/plugin-add.test.ts +111 -0
- package/test/cli/tui/plugin-install.test.ts +87 -0
- package/test/cli/tui/plugin-lifecycle.test.ts +224 -0
- package/test/cli/tui/plugin-loader-entrypoint.test.ts +484 -0
- package/test/cli/tui/plugin-loader-pure.test.ts +71 -0
- package/test/cli/tui/plugin-loader.test.ts +816 -0
- package/test/cli/tui/plugin-toggle.test.ts +157 -0
- package/test/cli/tui/revert-diff.test.ts +35 -0
- package/test/cli/tui/slot-replace.test.tsx +47 -0
- package/test/cli/tui/theme-store.test.ts +54 -0
- package/test/cli/tui/thread.test.ts +28 -0
- package/test/cli/tui/transcript.test.ts +426 -0
- package/test/cli/tui/usage.test.ts +60 -0
- package/test/cli/tui/use-event.test.tsx +175 -0
- package/test/config/agent-color.test.ts +67 -0
- package/test/config/config.test.ts +2544 -0
- package/test/config/fixtures/empty-frontmatter.md +4 -0
- package/test/config/fixtures/frontmatter.md +28 -0
- package/test/config/fixtures/markdown-header.md +11 -0
- package/test/config/fixtures/no-frontmatter.md +1 -0
- package/test/config/fixtures/weird-model-id.md +13 -0
- package/test/config/lsp.test.ts +87 -0
- package/test/config/markdown.test.ts +228 -0
- package/test/config/plugin.test.ts +0 -0
- package/test/config/tui.test.ts +624 -0
- package/test/control-plane/adapters.test.ts +71 -0
- package/test/control-plane/workspace.test.ts +1526 -0
- package/test/effect/app-runtime-logger.test.ts +98 -0
- package/test/effect/config-service.test.ts +65 -0
- package/test/effect/instance-state.test.ts +394 -0
- package/test/effect/run-service.test.ts +89 -0
- package/test/effect/runner.test.ts +523 -0
- package/test/fake/provider.ts +82 -0
- package/test/file/fsmonitor.test.ts +68 -0
- package/test/file/ignore.test.ts +10 -0
- package/test/file/index.test.ts +954 -0
- package/test/file/path-traversal.test.ts +205 -0
- package/test/file/ripgrep.test.ts +226 -0
- package/test/file/watcher.test.ts +249 -0
- package/test/filesystem/filesystem.test.ts +319 -0
- package/test/fixture/db.ts +11 -0
- package/test/fixture/fixture.test.ts +26 -0
- package/test/fixture/fixture.ts +175 -0
- package/test/fixture/flock-worker.ts +72 -0
- package/test/fixture/log-init-worker.ts +62 -0
- package/test/fixture/lsp/fake-lsp-server.js +249 -0
- package/test/fixture/plug-worker.ts +93 -0
- package/test/fixture/plugin-meta-worker.ts +19 -0
- package/test/fixture/skills/agents-sdk/SKILL.md +152 -0
- package/test/fixture/skills/cloudflare/SKILL.md +211 -0
- package/test/fixture/skills/index.json +6 -0
- package/test/fixture/tui-plugin.ts +323 -0
- package/test/fixture/tui-runtime.ts +31 -0
- package/test/format/format.test.ts +272 -0
- package/test/git/git.test.ts +128 -0
- package/test/ide/ide.test.ts +82 -0
- package/test/installation/installation.test.ts +168 -0
- package/test/keybind.test.ts +421 -0
- package/test/lib/effect.ts +53 -0
- package/test/lib/filesystem.ts +10 -0
- package/test/lib/llm-server.ts +778 -0
- package/test/lib/websocket.ts +46 -0
- package/test/lsp/client.test.ts +482 -0
- package/test/lsp/index.test.ts +160 -0
- package/test/lsp/launch.test.ts +22 -0
- package/test/lsp/lifecycle.test.ts +184 -0
- package/test/ltm/ltm.test.ts +230 -0
- package/test/mcp/headers.test.ts +178 -0
- package/test/mcp/lifecycle.test.ts +787 -0
- package/test/mcp/oauth-auto-connect.test.ts +311 -0
- package/test/mcp/oauth-browser.test.ts +276 -0
- package/test/mcp/oauth-callback.test.ts +34 -0
- package/test/memory/abort-leak-webfetch.ts +49 -0
- package/test/memory/abort-leak.test.ts +128 -0
- package/test/patch/patch.test.ts +348 -0
- package/test/permission/arity.test.ts +33 -0
- package/test/permission/next.test.ts +1227 -0
- package/test/permission/next.toConfig.test.ts +110 -0
- package/test/permission-task.test.ts +326 -0
- package/test/plugin/auth-override.test.ts +79 -0
- package/test/plugin/cloudflare.test.ts +68 -0
- package/test/plugin/codex.test.ts +123 -0
- package/test/plugin/github-copilot-models.test.ts +261 -0
- package/test/plugin/install-concurrency.test.ts +140 -0
- package/test/plugin/install.test.ts +570 -0
- package/test/plugin/loader-shared.test.ts +1169 -0
- package/test/plugin/meta.test.ts +137 -0
- package/test/plugin/plugin-contract.test.ts +291 -0
- package/test/plugin/shared.test.ts +88 -0
- package/test/plugin/trigger.test.ts +102 -0
- package/test/plugin/workspace-adapter.test.ts +109 -0
- package/test/preload.ts +77 -0
- package/test/project/instance.test.ts +276 -0
- package/test/project/migrate-global.test.ts +152 -0
- package/test/project/project.test.ts +600 -0
- package/test/project/vcs.test.ts +286 -0
- package/test/project/worktree-remove.test.ts +126 -0
- package/test/project/worktree.test.ts +223 -0
- package/test/provider/amazon-bedrock.test.ts +462 -0
- package/test/provider/copilot/convert-to-copilot-messages.test.ts +523 -0
- package/test/provider/copilot/copilot-chat-model.test.ts +592 -0
- package/test/provider/gitlab-duo.test.ts +413 -0
- package/test/provider/local.test.ts +208 -0
- package/test/provider/models.test.ts +261 -0
- package/test/provider/provider-category.test.ts +190 -0
- package/test/provider/provider.test.ts +2758 -0
- package/test/provider/transform.test.ts +3681 -0
- package/test/pty/pty-output-isolation.test.ts +147 -0
- package/test/pty/pty-session.test.ts +102 -0
- package/test/pty/pty-shell.test.ts +104 -0
- package/test/question/question.test.ts +490 -0
- package/test/saeeol/agent-global-config-dirs.test.ts +24 -0
- package/test/saeeol/agent-manager-tool.test.ts +71 -0
- package/test/saeeol/agent-permission-overrides.test.ts +75 -0
- package/test/saeeol/agent-skill-permissions.test.ts +37 -0
- package/test/saeeol/ask-agent-permissions.test.ts +303 -0
- package/test/saeeol/bash-hierarchy.test.ts +64 -0
- package/test/saeeol/bash-permission-metadata.test.ts +66 -0
- package/test/saeeol/bash-security-extended.test.ts +243 -0
- package/test/saeeol/bedrock-claude-empty-content.test.ts +138 -0
- package/test/saeeol/boxes-integration.test.ts +415 -0
- package/test/saeeol/builtin-skills.test.ts +75 -0
- package/test/saeeol/cleanup.ts +28 -0
- package/test/saeeol/cli/dev-setup.test.ts +74 -0
- package/test/saeeol/cli/roll-call.test.ts +161 -0
- package/test/saeeol/cli-run-auto-helper.test.ts +58 -0
- package/test/saeeol/codex-auth-refresh.test.ts +124 -0
- package/test/saeeol/commit-message/generate.test.ts +188 -0
- package/test/saeeol/commit-message/git-context.test.ts +303 -0
- package/test/saeeol/commit-message-windows.test.ts +38 -0
- package/test/saeeol/compaction-payload-recovery.test.ts +406 -0
- package/test/saeeol/compaction-preservation-audit.test.ts +122 -0
- package/test/saeeol/compaction-skip-guard.test.ts +224 -0
- package/test/saeeol/compaction-smart-select.test.ts +100 -0
- package/test/saeeol/config/config.test.ts +166 -0
- package/test/saeeol/config/indexing-default-plugin.test.ts +82 -0
- package/test/saeeol/config/opentelemetry-default.test.ts +29 -0
- package/test/saeeol/config-gitignore.test.ts +70 -0
- package/test/saeeol/config-injector.test.ts +305 -0
- package/test/saeeol/config-resilience.test.ts +234 -0
- package/test/saeeol/config-validation.test.ts +183 -0
- package/test/saeeol/cost-propagation.test.ts +94 -0
- package/test/saeeol/cost-tracker-extended.test.ts +141 -0
- package/test/saeeol/cost-tracker.test.ts +64 -0
- package/test/saeeol/custom-provider-delete.test.ts +149 -0
- package/test/saeeol/diff-full.test.ts +226 -0
- package/test/saeeol/edit-permission-filediff.test.ts +223 -0
- package/test/saeeol/encoding.test.ts +364 -0
- package/test/saeeol/enhance-prompt.test.ts +61 -0
- package/test/saeeol/ensure-plan-dir.test.ts +32 -0
- package/test/saeeol/errors.test.ts +144 -0
- package/test/saeeol/external-directory-boundary.test.ts +96 -0
- package/test/saeeol/gateway-headers.test.ts +88 -0
- package/test/saeeol/help.test.ts +191 -0
- package/test/saeeol/ignore-migrator.test.ts +308 -0
- package/test/saeeol/indexing-auth.test.ts +45 -0
- package/test/saeeol/indexing-feature.test.ts +44 -0
- package/test/saeeol/indexing-label.test.ts +70 -0
- package/test/saeeol/indexing-startup.test.ts +381 -0
- package/test/saeeol/indexing-worktree.test.ts +73 -0
- package/test/saeeol/instruction.test.ts +136 -0
- package/test/saeeol/lancedb-runtime.test.ts +116 -0
- package/test/saeeol/loader-auth.test.ts +168 -0
- package/test/saeeol/local-model.test.ts +621 -0
- package/test/saeeol/logo.test.ts +31 -0
- package/test/saeeol/lsp-typescript-lightweight.test.ts +89 -0
- package/test/saeeol/mcp-branding.test.ts +33 -0
- package/test/saeeol/mcp-docker-rm.test.ts +32 -0
- package/test/saeeol/mcp-migrator.test.ts +736 -0
- package/test/saeeol/mcp-oauth-callback.test.ts +33 -0
- package/test/saeeol/memory-io.test.ts +198 -0
- package/test/saeeol/memory-paths.test.ts +87 -0
- package/test/saeeol/memory-security.test.ts +166 -0
- package/test/saeeol/model-cache-org.test.ts +164 -0
- package/test/saeeol/model-info-panel-utils.test.ts +52 -0
- package/test/saeeol/model-info-panel.types.test.ts +7 -0
- package/test/saeeol/models-401-fallback.test.ts +52 -0
- package/test/saeeol/modes-migrator.test.ts +320 -0
- package/test/saeeol/nvidia-headers.test.ts +74 -0
- package/test/saeeol/patch-jsonc.test.ts +73 -0
- package/test/saeeol/patch.test.ts +172 -0
- package/test/saeeol/paths.test.ts +265 -0
- package/test/saeeol/permission/config-paths.test.ts +174 -0
- package/test/saeeol/permission/env-read.test.ts +149 -0
- package/test/saeeol/permission/external-directory-allow.test.ts +327 -0
- package/test/saeeol/permission/next.always-rules.test.ts +882 -0
- package/test/saeeol/permission/next.reply-http.test.ts +205 -0
- package/test/saeeol/permission/next.reply-routing.test.ts +184 -0
- package/test/saeeol/plan-exit-detection.test.ts +494 -0
- package/test/saeeol/plan-followup.test.ts +1376 -0
- package/test/saeeol/project-config-update.test.ts +120 -0
- package/test/saeeol/project-id.test.ts +455 -0
- package/test/saeeol/provider-cost.test.ts +171 -0
- package/test/saeeol/provider-list-failed-state.test.ts +100 -0
- package/test/saeeol/question-dismiss-all.test.ts +174 -0
- package/test/saeeol/read-directory.test.ts +116 -0
- package/test/saeeol/rules-migrator.test.ts +257 -0
- package/test/saeeol/run-auto.test.ts +176 -0
- package/test/saeeol/run-network.test.ts +224 -0
- package/test/saeeol/semantic-search.test.ts +186 -0
- package/test/saeeol/server/permission-allow-everything.test.ts +125 -0
- package/test/saeeol/session/instruction-substitution.test.ts +72 -0
- package/test/saeeol/session/platform-attribution.test.ts +118 -0
- package/test/saeeol/session/session.test.ts +105 -0
- package/test/saeeol/session-compaction-cap.test.ts +399 -0
- package/test/saeeol/session-compaction-chunks.test.ts +501 -0
- package/test/saeeol/session-compaction-safety.test.ts +481 -0
- package/test/saeeol/session-fork-remap.test.ts +251 -0
- package/test/saeeol/session-import-service.test.ts +114 -0
- package/test/saeeol/session-list.test.ts +47 -0
- package/test/saeeol/session-message-metadata.test.ts +128 -0
- package/test/saeeol/session-overflow.test.ts +78 -0
- package/test/saeeol/session-processor-empty-tool-calls.test.ts +571 -0
- package/test/saeeol/session-processor-network-offline.test.ts +204 -0
- package/test/saeeol/session-processor-retry-limit.test.ts +238 -0
- package/test/saeeol/session-processor-review-telemetry.test.ts +82 -0
- package/test/saeeol/session-prompt-compaction-safety.test.ts +517 -0
- package/test/saeeol/session-prompt-queue.test.ts +815 -0
- package/test/saeeol/sessions/inflight-cache.test.ts +157 -0
- package/test/saeeol/sessions/ingest-queue.test.ts +402 -0
- package/test/saeeol/sessions/remote-protocol.test.ts +258 -0
- package/test/saeeol/sessions/remote-sender.test.ts +1036 -0
- package/test/saeeol/sessions/remote-ws.test.ts +367 -0
- package/test/saeeol/sessions/sessions-enable-remote.test.disable +181 -0
- package/test/saeeol/slot-prop-reactivity.test.ts +142 -0
- package/test/saeeol/snapshot-cache.test.ts +84 -0
- package/test/saeeol/snapshot-freeze-repro.test.ts +100 -0
- package/test/saeeol/snapshot-track-timeout.test.ts +519 -0
- package/test/saeeol/stats-subagent-cost.test.ts +123 -0
- package/test/saeeol/suggestion/auto-dismiss.test.ts +65 -0
- package/test/saeeol/suggestion/suggestion.test.ts +145 -0
- package/test/saeeol/suggestion/tool.test.ts +298 -0
- package/test/saeeol/summary-file-diff.test.ts +28 -0
- package/test/saeeol/system-prompt.test.ts +142 -0
- package/test/saeeol/task-nesting.test.ts +193 -0
- package/test/saeeol/telemetry/feedback.test.ts +8 -0
- package/test/saeeol/todo-view.test.ts +57 -0
- package/test/saeeol/tool-encoding.test.ts +455 -0
- package/test/saeeol/tool-registry-indexing-import-failure.test.ts +49 -0
- package/test/saeeol/tool-registry-indexing.test.ts +236 -0
- package/test/saeeol/tool-registry-semantic-import-failure.test.ts +55 -0
- package/test/saeeol/tool-task-model.test.ts +352 -0
- package/test/saeeol/transform-opus-4.7.test.ts +89 -0
- package/test/saeeol/tui-diff.test.ts +91 -0
- package/test/saeeol/tui-sync.test.ts +80 -0
- package/test/saeeol/util/url.test.ts +141 -0
- package/test/saeeol/workflows-migrator.test.ts +261 -0
- package/test/saeeol/worktree-diff-summary.test.ts +64 -0
- package/test/saeeol/worktree-diff.test.ts +223 -0
- package/test/saeeol/worktree-remove-lock.test.ts +82 -0
- package/test/server/AGENTS.md +15 -0
- package/test/server/contract.test.ts +357 -0
- package/test/server/experimental-session-list.test.ts +157 -0
- package/test/server/global-session-list.test.ts +155 -0
- package/test/server/httpapi-authorization.test.ts +103 -0
- package/test/server/httpapi-bridge.test.ts +440 -0
- package/test/server/httpapi-config.test.ts +67 -0
- package/test/server/httpapi-cors.test.ts +89 -0
- package/test/server/httpapi-event.test.ts +57 -0
- package/test/server/httpapi-experimental.test.ts +219 -0
- package/test/server/httpapi-file.test.ts +79 -0
- package/test/server/httpapi-instance-context.test.ts +237 -0
- package/test/server/httpapi-instance.legacy.test.ts +140 -0
- package/test/server/httpapi-instance.test.ts +83 -0
- package/test/server/httpapi-json-parity.test.ts +263 -0
- package/test/server/httpapi-mcp-oauth.test.ts +76 -0
- package/test/server/httpapi-mcp.test.ts +189 -0
- package/test/server/httpapi-provider.test.ts +153 -0
- package/test/server/httpapi-pty-websocket.test.ts +16 -0
- package/test/server/httpapi-pty.test.ts +175 -0
- package/test/server/httpapi-raw-route-auth.test.ts +89 -0
- package/test/server/httpapi-sdk.test.ts +681 -0
- package/test/server/httpapi-session.test.ts +464 -0
- package/test/server/httpapi-sync.test.ts +130 -0
- package/test/server/httpapi-tui.test.ts +121 -0
- package/test/server/httpapi-workspace-routing.test.ts +471 -0
- package/test/server/httpapi-workspace.test.ts +427 -0
- package/test/server/lib/conformance.ts +88 -0
- package/test/server/lib/stateful.ts +112 -0
- package/test/server/project-init-git.test.ts +113 -0
- package/test/server/proxy-util.test.ts +113 -0
- package/test/server/session-actions.test.ts +49 -0
- package/test/server/session-list.test.ts +238 -0
- package/test/server/session-messages.test.ts +167 -0
- package/test/server/session-select.test.ts +100 -0
- package/test/server/trace-attributes.test.ts +76 -0
- package/test/server/workspace-proxy.test.ts +165 -0
- package/test/server/workspace-routing.test.ts +85 -0
- package/test/session/compaction.test.ts +2420 -0
- package/test/session/instruction.test.ts +247 -0
- package/test/session/llm.test.ts +1273 -0
- package/test/session/message-v2.test.ts +1291 -0
- package/test/session/messages-pagination.test.ts +1173 -0
- package/test/session/network.test.ts +249 -0
- package/test/session/processor-effect.test.ts +847 -0
- package/test/session/prompt.test.ts +2131 -0
- package/test/session/retry.test.ts +340 -0
- package/test/session/revert-compact.test.ts +639 -0
- package/test/session/schema-decoding.test.ts +311 -0
- package/test/session/session-entry-stepper.test.ts +917 -0
- package/test/session/session-schema.test.ts +76 -0
- package/test/session/snapshot-tool-race.test.ts +257 -0
- package/test/session/structured-output-integration.test.ts +265 -0
- package/test/session/structured-output.test.ts +381 -0
- package/test/session/system.test.ts +73 -0
- package/test/share/share-next.test.ts +333 -0
- package/test/shell/shell.test.ts +99 -0
- package/test/skill/discovery.test.ts +116 -0
- package/test/skill/skill.test.ts +393 -0
- package/test/snapshot/snapshot.test.ts +1531 -0
- package/test/storage/db.test.ts +23 -0
- package/test/storage/json-migration.test.ts +832 -0
- package/test/storage/storage.test.ts +293 -0
- package/test/suggestion/suggestion.test.ts +1 -0
- package/test/sync/index.test.ts +256 -0
- package/test/tool/__snapshots__/parameters.test.ts.snap +500 -0
- package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
- package/test/tool/apply_patch.test.ts +614 -0
- package/test/tool/bash.test.ts +1225 -0
- package/test/tool/diagnostics-filter.test.ts +55 -0
- package/test/tool/edit.test.ts +754 -0
- package/test/tool/external-directory.test.ts +169 -0
- package/test/tool/fixtures/large-image.png +0 -0
- package/test/tool/fixtures/models-api.json +65179 -0
- package/test/tool/glob.test.ts +107 -0
- package/test/tool/grep.test.ts +114 -0
- package/test/tool/lsp.test.ts +187 -0
- package/test/tool/parameters.test.ts +243 -0
- package/test/tool/question.test.ts +129 -0
- package/test/tool/read.test.ts +500 -0
- package/test/tool/recall.test.ts +151 -0
- package/test/tool/registry.test.ts +203 -0
- package/test/tool/skill.test.ts +135 -0
- package/test/tool/suggest.test.ts +1 -0
- package/test/tool/task.test.ts +612 -0
- package/test/tool/tool-define.test.ts +99 -0
- package/test/tool/truncation.test.ts +260 -0
- package/test/tool/webfetch.test.ts +103 -0
- package/test/tool/write.test.ts +291 -0
- package/test/util/data-url.test.ts +14 -0
- package/test/util/effect-zod.test.ts +754 -0
- package/test/util/error.test.ts +38 -0
- package/test/util/filesystem.test.ts +656 -0
- package/test/util/format.test.ts +59 -0
- package/test/util/glob.test.ts +164 -0
- package/test/util/iife.test.ts +36 -0
- package/test/util/lazy.test.ts +50 -0
- package/test/util/lock.test.ts +72 -0
- package/test/util/log.test.ts +86 -0
- package/test/util/module.test.ts +59 -0
- package/test/util/process.test.ts +128 -0
- package/test/util/timeout.test.ts +21 -0
- package/test/util/which.test.ts +100 -0
- package/test/util/wildcard.test.ts +90 -0
- package/test/workspace/workspace-restore.test.ts +296 -0
- package/src/provider/models-snapshot.d.ts +0 -2
- package/src/provider/models-snapshot.js +0 -3
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* diff-apply.ts — Parse and apply unified diffs
|
|
3
|
+
* Inspired by Continue.dev edit pipeline (Apache-2.0)
|
|
4
|
+
* Deps: none
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface DiffHunk {
|
|
8
|
+
oldStart: number; oldCount: number
|
|
9
|
+
newStart: number; newCount: number
|
|
10
|
+
lines: string[]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function parseUnifiedDiff(diff: string): DiffHunk[] {
|
|
14
|
+
const hunks: DiffHunk[] = []
|
|
15
|
+
let current: DiffHunk | null = null
|
|
16
|
+
for (const line of diff.split("\n")) {
|
|
17
|
+
const m = line.match(/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/)
|
|
18
|
+
if (m) {
|
|
19
|
+
if (current) hunks.push(current)
|
|
20
|
+
current = {
|
|
21
|
+
oldStart: parseInt(m[1]), oldCount: parseInt(m[2] ?? "1"),
|
|
22
|
+
newStart: parseInt(m[3]), newCount: parseInt(m[4] ?? "1"),
|
|
23
|
+
lines: [],
|
|
24
|
+
}
|
|
25
|
+
} else if (current && (line.startsWith("+") || line.startsWith("-") || line.startsWith(" ") || line === "")) {
|
|
26
|
+
current.lines.push(line)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (current) hunks.push(current)
|
|
30
|
+
return hunks
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function applyDiff(source: string, hunks: DiffHunk[]): string {
|
|
34
|
+
const lines = source.split("\n")
|
|
35
|
+
let offset = 0
|
|
36
|
+
for (const h of hunks) {
|
|
37
|
+
const start = h.oldStart - 1 + offset
|
|
38
|
+
const oldLines: string[] = []
|
|
39
|
+
const newLines: string[] = []
|
|
40
|
+
for (const l of h.lines) {
|
|
41
|
+
if (l.startsWith("-")) oldLines.push(l.slice(1))
|
|
42
|
+
else if (l.startsWith("+")) newLines.push(l.slice(1))
|
|
43
|
+
else { oldLines.push(l.slice(1)); newLines.push(l.slice(1)) }
|
|
44
|
+
}
|
|
45
|
+
lines.splice(start, oldLines.length, ...newLines)
|
|
46
|
+
offset += newLines.length - oldLines.length
|
|
47
|
+
}
|
|
48
|
+
return lines.join("\n")
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function applyUnifiedDiff(source: string, diff: string): string {
|
|
52
|
+
return applyDiff(source, parseUnifiedDiff(diff))
|
|
53
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* disposable.ts — Wrap cleanup fn as Disposable + AsyncDisposable
|
|
3
|
+
* Zero deps.
|
|
4
|
+
*
|
|
5
|
+
* const d = disposable(() => console.log("cleaned"))
|
|
6
|
+
* using d = disposable(() => cleanup())
|
|
7
|
+
*/
|
|
8
|
+
export function disposable(fn: () => void | Promise<void>): AsyncDisposable & Disposable {
|
|
9
|
+
return {
|
|
10
|
+
[Symbol.dispose]() { void fn() },
|
|
11
|
+
[Symbol.asyncDispose]() { return Promise.resolve(fn()) },
|
|
12
|
+
}
|
|
13
|
+
}
|
package/src/boxes/err.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* err.ts — Universal error message extraction
|
|
3
|
+
* Zero deps.
|
|
4
|
+
*
|
|
5
|
+
* msg(new Error("fail")) → "fail"
|
|
6
|
+
* msg({ message: "bad" }) → "bad"
|
|
7
|
+
*/
|
|
8
|
+
export function isObj(v: unknown): v is Record<string, unknown> {
|
|
9
|
+
return !!v && typeof v === "object" && !Array.isArray(v)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function msg(e: unknown): string {
|
|
13
|
+
if (e instanceof Error) {
|
|
14
|
+
if (e.message) return e.message
|
|
15
|
+
if (e.name) return e.name
|
|
16
|
+
}
|
|
17
|
+
if (isObj(e)) {
|
|
18
|
+
if (typeof e.message === "string" && e.message) return e.message
|
|
19
|
+
if (isObj(e.data) && typeof e.data.message === "string" && e.data.message) return e.data.message
|
|
20
|
+
}
|
|
21
|
+
const s = String(e)
|
|
22
|
+
if (s && s !== "[object Object]") return s
|
|
23
|
+
const f = fmt(e)
|
|
24
|
+
if (f && f !== "{}") return f
|
|
25
|
+
return "unknown error"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function fmt(e: unknown): string {
|
|
29
|
+
if (e instanceof Error) return e.stack ?? `${e.name}: ${e.message}`
|
|
30
|
+
if (typeof e === "object" && e !== null) {
|
|
31
|
+
try { return JSON.stringify(e, null, 2) } catch { return "Unexpected error (unserializable)" }
|
|
32
|
+
}
|
|
33
|
+
return String(e)
|
|
34
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* human.ts — Human-readable formatting: duration, numbers, dates, truncate, plural
|
|
3
|
+
* Zero deps.
|
|
4
|
+
*
|
|
5
|
+
* duration(150000) → "2m 30s"
|
|
6
|
+
* compact(1500) → "1.5K"
|
|
7
|
+
* truncate("hello world", 8) → "hello w…"
|
|
8
|
+
*/
|
|
9
|
+
export function title(s: string) { return s.replace(/\b\w/g, c => c.toUpperCase()) }
|
|
10
|
+
|
|
11
|
+
export function time(ms: number) { return new Date(ms).toLocaleTimeString(undefined, { timeStyle: "short" }) }
|
|
12
|
+
|
|
13
|
+
export function datetime(ms: number) { return `${time(ms)} · ${new Date(ms).toLocaleDateString()}` }
|
|
14
|
+
|
|
15
|
+
export function compact(n: number): string {
|
|
16
|
+
if (n >= 1_000_000) return (n / 1_000_000).toFixed(1) + "M"
|
|
17
|
+
if (n >= 1000) return (n / 1000).toFixed(1) + "K"
|
|
18
|
+
return n.toString()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function duration(ms: number): string {
|
|
22
|
+
if (ms < 1000) return `${ms}ms`
|
|
23
|
+
if (ms < 60_000) return `${(ms / 1000).toFixed(1)}s`
|
|
24
|
+
if (ms < 3_600_000) return `${Math.floor(ms / 60_000)}m ${Math.floor((ms % 60_000) / 1000)}s`
|
|
25
|
+
if (ms < 86_400_000) return `${Math.floor(ms / 3_600_000)}h ${Math.floor((ms % 3_600_000) / 60_000)}m`
|
|
26
|
+
return `${Math.floor(ms / 3_600_000)}h`
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function truncate(s: string, len: number): string { return s.length <= len ? s : s.slice(0, len - 1) + "…" }
|
|
30
|
+
|
|
31
|
+
export function truncateMid(s: string, max = 35): string {
|
|
32
|
+
if (s.length <= max) return s
|
|
33
|
+
const pre = Math.ceil((max - 1) / 2)
|
|
34
|
+
const suf = Math.floor((max - 1) / 2)
|
|
35
|
+
return s.slice(0, pre) + "…" + s.slice(-suf)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function plural(n: number, one: string, many: string): string { return (n === 1 ? one : many).replace("{}", String(n)) }
|
|
39
|
+
|
|
40
|
+
export function secsDuration(s: number): string {
|
|
41
|
+
if (s <= 0) return ""
|
|
42
|
+
if (s < 60) return `${s}s`
|
|
43
|
+
if (s < 3600) { const m = Math.floor(s / 60); const r = s % 60; return r > 0 ? `${m}m ${r}s` : `${m}m` }
|
|
44
|
+
if (s < 86400) { const h = Math.floor(s / 3600); const r = Math.floor((s % 3600) / 60); return r > 0 ? `${h}h ${r}m` : `${h}h` }
|
|
45
|
+
if (s < 604800) { const d = Math.floor(s / 86400); return d === 1 ? "~1 day" : `~${d} days` }
|
|
46
|
+
const w = Math.floor(s / 604800); return w === 1 ? "~1 week" : `~${w} weeks`
|
|
47
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* memory.ts — File-based persistent memory with typed taxonomy
|
|
3
|
+
* Deps: Node built-ins (os, path, fs/promises)
|
|
4
|
+
* Ported from Claude Code memdir/
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { homedir } from "os"
|
|
8
|
+
import { join, normalize, sep, basename } from "path"
|
|
9
|
+
import { mkdir, readFile, writeFile, readdir, stat } from "fs/promises"
|
|
10
|
+
|
|
11
|
+
// ── Types ──────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
export const MEMORY_TYPES = ["user", "feedback", "project", "reference"] as const
|
|
14
|
+
export type MemoryType = (typeof MEMORY_TYPES)[number]
|
|
15
|
+
|
|
16
|
+
export function parseMemoryType(raw: unknown): MemoryType | undefined {
|
|
17
|
+
if (typeof raw !== "string") return undefined
|
|
18
|
+
return MEMORY_TYPES.find(t => t === raw)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface MemoryHeader {
|
|
22
|
+
filename: string
|
|
23
|
+
filePath: string
|
|
24
|
+
mtimeMs: number
|
|
25
|
+
description: string | null
|
|
26
|
+
type: MemoryType | undefined
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ── Age ────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
export function memoryAgeDays(mtimeMs: number): number {
|
|
32
|
+
return Math.max(0, Math.floor((Date.now() - mtimeMs) / 86_400_000))
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function memoryAge(mtimeMs: number): string {
|
|
36
|
+
const d = memoryAgeDays(mtimeMs)
|
|
37
|
+
if (d === 0) return "today"
|
|
38
|
+
if (d === 1) return "yesterday"
|
|
39
|
+
return `${d} days ago`
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function memoryFreshnessText(mtimeMs: number): string {
|
|
43
|
+
const d = memoryAgeDays(mtimeMs)
|
|
44
|
+
if (d <= 1) return ""
|
|
45
|
+
return `This memory is ${d} days old. Memories are point-in-time observations, not live state — verify against current code before asserting as fact.`
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ── Paths ──────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
export const MAX_ENTRYPOINT_LINES = 200
|
|
51
|
+
export const MAX_ENTRYPOINT_BYTES = 25_000
|
|
52
|
+
|
|
53
|
+
export function getMemoryBaseDir(): string {
|
|
54
|
+
return process.env.KILO_MEMORY_DIR ?? join(homedir(), ".config", "saeeol")
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function getMemoryDir(projectRoot: string): string {
|
|
58
|
+
if (process.env.KILO_MEMORY_DIR) return process.env.KILO_MEMORY_DIR
|
|
59
|
+
return join(getMemoryBaseDir(), "projects", sanitize(projectRoot), "memory") + sep
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function getMemoryEntrypoint(projectRoot: string): string {
|
|
63
|
+
return join(getMemoryDir(projectRoot), "MEMORY.md")
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function isMemoryPath(absolutePath: string, projectRoot: string): boolean {
|
|
67
|
+
return normalize(absolutePath).startsWith(getMemoryDir(projectRoot))
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function sanitize(p: string): string {
|
|
71
|
+
return p.replace(/[<>:"|?*]/g, "_").replace(/[/\\]+/g, "_")
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ── Scan ───────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
const MAX_MEMORY_FILES = 200
|
|
77
|
+
const FM_MAX_LINES = 30
|
|
78
|
+
|
|
79
|
+
export async function scanMemoryFiles(dir: string): Promise<MemoryHeader[]> {
|
|
80
|
+
try {
|
|
81
|
+
const entries = await readdir(dir, { recursive: true })
|
|
82
|
+
const mds = entries.filter(
|
|
83
|
+
(f): f is string => typeof f === "string" && f.endsWith(".md") && basename(f) !== "MEMORY.md",
|
|
84
|
+
)
|
|
85
|
+
if (mds.length === 0) return []
|
|
86
|
+
const results = await Promise.allSettled(mds.map(async (rel): Promise<MemoryHeader> => {
|
|
87
|
+
const fp = join(dir, rel)
|
|
88
|
+
const s = await stat(fp)
|
|
89
|
+
const { description, type } = await parseFrontmatter(fp)
|
|
90
|
+
return { filename: rel, filePath: fp, mtimeMs: s.mtimeMs, description, type }
|
|
91
|
+
}))
|
|
92
|
+
return results
|
|
93
|
+
.filter((r): r is PromiseFulfilledResult<MemoryHeader> => r.status === "fulfilled")
|
|
94
|
+
.map(r => r.value)
|
|
95
|
+
.sort((a, b) => b.mtimeMs - a.mtimeMs)
|
|
96
|
+
.slice(0, MAX_MEMORY_FILES)
|
|
97
|
+
} catch { return [] }
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function formatMemoryManifest(memories: MemoryHeader[]): string {
|
|
101
|
+
return memories.map(m => {
|
|
102
|
+
const tag = m.type ? `[${m.type}] ` : ""
|
|
103
|
+
const ts = new Date(m.mtimeMs).toISOString()
|
|
104
|
+
return m.description ? `- ${tag}${m.filename} (${ts}): ${m.description}` : `- ${tag}${m.filename} (${ts})`
|
|
105
|
+
}).join("\n")
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function parseFrontmatter(
|
|
109
|
+
filePath: string,
|
|
110
|
+
): Promise<{ description: string | null; type: MemoryType | undefined }> {
|
|
111
|
+
try {
|
|
112
|
+
const raw = await readFile(filePath, "utf-8")
|
|
113
|
+
const lines = raw.split("\n").slice(0, FM_MAX_LINES)
|
|
114
|
+
const acc = lines.reduce(
|
|
115
|
+
(s, line) => {
|
|
116
|
+
if (s.done) return s
|
|
117
|
+
if (line.trim() === "---") { if (s.inFm) return { ...s, done: true }; return { ...s, inFm: true } }
|
|
118
|
+
if (!s.inFm) return s
|
|
119
|
+
const desc = line.match(/^description:\s*(.+)$/i)?.[1]
|
|
120
|
+
const typ = line.match(/^type:\s*(.+)$/i)?.[1]
|
|
121
|
+
return { ...s, desc: desc ?? s.desc, typeRaw: typ ?? s.typeRaw }
|
|
122
|
+
},
|
|
123
|
+
{ inFm: false, done: false, desc: null as string | null, typeRaw: undefined as string | undefined },
|
|
124
|
+
)
|
|
125
|
+
return { description: acc.desc, type: parseMemoryType(acc.typeRaw) }
|
|
126
|
+
} catch { return { description: null, type: undefined } }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ── Entrypoint I/O ─────────────────────────────────
|
|
130
|
+
|
|
131
|
+
export function truncateEntrypoint(raw: string): { content: string; wasTruncated: boolean } {
|
|
132
|
+
const trimmed = raw.trim()
|
|
133
|
+
const lines = trimmed.split("\n")
|
|
134
|
+
const wasLine = lines.length > MAX_ENTRYPOINT_LINES
|
|
135
|
+
const wasByte = trimmed.length > MAX_ENTRYPOINT_BYTES
|
|
136
|
+
if (!wasLine && !wasByte) return { content: trimmed, wasTruncated: false }
|
|
137
|
+
const afterLine = wasLine ? lines.slice(0, MAX_ENTRYPOINT_LINES).join("\n") : trimmed
|
|
138
|
+
const final = afterLine.length > MAX_ENTRYPOINT_BYTES
|
|
139
|
+
? (() => { const cut = afterLine.lastIndexOf("\n", MAX_ENTRYPOINT_BYTES); return afterLine.slice(0, cut > 0 ? cut : MAX_ENTRYPOINT_BYTES) })()
|
|
140
|
+
: afterLine
|
|
141
|
+
return { content: final + "\n\n> WARNING: MEMORY.md was truncated. Keep entries concise.", wasTruncated: true }
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export async function readEntrypoint(projectRoot: string): Promise<string> {
|
|
145
|
+
try { return await readFile(getMemoryEntrypoint(projectRoot), "utf-8") }
|
|
146
|
+
catch { return "" }
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export async function writeEntrypoint(projectRoot: string, content: string): Promise<void> {
|
|
150
|
+
const { content: final } = truncateEntrypoint(content)
|
|
151
|
+
await writeFile(getMemoryEntrypoint(projectRoot), final, "utf-8")
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export async function ensureDir(projectRoot: string): Promise<string> {
|
|
155
|
+
const dir = getMemoryDir(projectRoot)
|
|
156
|
+
try { await mkdir(dir, { recursive: true }) } catch { /* exists */ }
|
|
157
|
+
return dir
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ── Prompt builder ─────────────────────────────────
|
|
161
|
+
|
|
162
|
+
const TYPES_SECTION = [
|
|
163
|
+
"## Types of memory", "",
|
|
164
|
+
"There are several discrete types of memory you can store:", "",
|
|
165
|
+
"<types>",
|
|
166
|
+
'<type><name>user</name><description>User preferences, role, and knowledge.</description><when_to_save>When you learn about the user\'s role, preferences, or knowledge.</when_to_save></type>',
|
|
167
|
+
'<type><name>feedback</name><description>Guidance about how to approach work — both corrections and confirmed approaches.</description><when_to_save>When the user corrects or confirms your approach.</when_to_save></type>',
|
|
168
|
+
'<type><name>project</name><description>Ongoing work, goals, bugs, or incidents not derivable from code.</description><when_to_save>When you learn who is doing what, why, or by when.</when_to_save></type>',
|
|
169
|
+
'<type><name>reference</name><description>Pointers to external systems and resources.</description><when_to_save>When you learn about external resources and their purpose.</when_to_save></type>',
|
|
170
|
+
"</types>", "",
|
|
171
|
+
]
|
|
172
|
+
|
|
173
|
+
export async function buildPrompt(projectRoot: string): Promise<string> {
|
|
174
|
+
const dir = getMemoryDir(projectRoot)
|
|
175
|
+
const ep = await readEntrypoint(projectRoot)
|
|
176
|
+
const lines = [
|
|
177
|
+
"# auto memory", "",
|
|
178
|
+
`You have a persistent memory system at \`${dir}\`. Write to it directly.`,
|
|
179
|
+
"", "Build up this memory so future conversations have context about the user, their preferences, and project decisions.",
|
|
180
|
+
"", ...TYPES_SECTION,
|
|
181
|
+
"## What NOT to save in memory", "",
|
|
182
|
+
"- Code patterns, architecture, file paths — derivable from current project state.",
|
|
183
|
+
"- Git history — `git log` / `git blame` are authoritative.",
|
|
184
|
+
"- Debugging solutions — the fix is in the code.",
|
|
185
|
+
"- Ephemeral task details: in-progress work, temporary state.", "",
|
|
186
|
+
"## When to access memories",
|
|
187
|
+
"- When memories seem relevant to the user's request.",
|
|
188
|
+
"- When the user explicitly asks you to recall something.",
|
|
189
|
+
"- Memory records can become stale. Verify against current state before asserting as fact.", "",
|
|
190
|
+
]
|
|
191
|
+
const epContent = ep.trim() ? truncateEntrypoint(ep).content : "Your MEMORY.md is currently empty."
|
|
192
|
+
lines.push("## MEMORY.md", "", epContent)
|
|
193
|
+
return lines.join("\n")
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export async function scan(projectRoot: string) {
|
|
197
|
+
return scanMemoryFiles(getMemoryDir(projectRoot))
|
|
198
|
+
}
|
package/src/boxes/net.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* net.ts — Network status checks
|
|
3
|
+
* Zero deps.
|
|
4
|
+
*
|
|
5
|
+
* online() → true/false
|
|
6
|
+
* proxied() → true/false
|
|
7
|
+
*/
|
|
8
|
+
export function online(): boolean {
|
|
9
|
+
const n = globalThis.navigator
|
|
10
|
+
if (!n || typeof n.onLine !== "boolean") return true
|
|
11
|
+
return n.onLine
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function proxied(): boolean {
|
|
15
|
+
return !!(process.env.HTTP_PROXY || process.env.HTTPS_PROXY || process.env.http_proxy || process.env.https_proxy)
|
|
16
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* puny.ts — Normalize IDN/Unicode URLs to punycode ASCII (anti-homograph)
|
|
3
|
+
* Zero deps. Uses URL built-in.
|
|
4
|
+
*
|
|
5
|
+
* norm("https://аpitest.com/path") → "https://xn--pitest-2nf.com/path"
|
|
6
|
+
*/
|
|
7
|
+
export function norm(text: string): string {
|
|
8
|
+
return text.replace(/https?:\/\/\S+/g, (match) => {
|
|
9
|
+
const stripped = match.replace(/[.,!?;:)"'\]>]+$/, "")
|
|
10
|
+
const tail = match.slice(stripped.length)
|
|
11
|
+
try {
|
|
12
|
+
const u = new URL(stripped)
|
|
13
|
+
const after = stripped.indexOf("//") + 2
|
|
14
|
+
const slash = stripped.indexOf("/", after)
|
|
15
|
+
const raw = slash === -1 ? stripped.slice(after) : stripped.slice(after, slash)
|
|
16
|
+
const host = raw.includes(":") ? raw.slice(0, raw.indexOf(":")) : raw
|
|
17
|
+
if (host === u.hostname) return match
|
|
18
|
+
return stripped.replace(host, u.hostname) + tail
|
|
19
|
+
} catch { return match }
|
|
20
|
+
})
|
|
21
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { expDelay } from "./schedule"
|
|
2
|
+
|
|
3
|
+
const TRANSIENT = [
|
|
4
|
+
"load failed",
|
|
5
|
+
"network connection was lost",
|
|
6
|
+
"network request failed",
|
|
7
|
+
"failed to fetch",
|
|
8
|
+
"econnreset",
|
|
9
|
+
"econnrefused",
|
|
10
|
+
"etimedout",
|
|
11
|
+
"socket hang up",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
function isTransient(err: unknown): boolean {
|
|
15
|
+
if (!err) return false
|
|
16
|
+
const msg = String(err instanceof Error ? err.message : err).toLowerCase()
|
|
17
|
+
return TRANSIENT.some((t) => msg.includes(t))
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface RetryOpts {
|
|
21
|
+
attempts?: number
|
|
22
|
+
delay?: number
|
|
23
|
+
factor?: number
|
|
24
|
+
maxDelay?: number
|
|
25
|
+
retryIf?: (err: unknown) => boolean
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function retry<T>(
|
|
29
|
+
fn: () => Promise<T>,
|
|
30
|
+
opts: RetryOpts = {},
|
|
31
|
+
): Promise<T> {
|
|
32
|
+
const {
|
|
33
|
+
attempts = 3,
|
|
34
|
+
delay = 500,
|
|
35
|
+
factor = 2,
|
|
36
|
+
maxDelay = 10_000,
|
|
37
|
+
retryIf = isTransient,
|
|
38
|
+
} = opts
|
|
39
|
+
let last: unknown
|
|
40
|
+
for (let i = 0; i < attempts; i++) {
|
|
41
|
+
try { return await fn() } catch (err) {
|
|
42
|
+
last = err
|
|
43
|
+
if (i === attempts - 1 || !retryIf(err)) throw err
|
|
44
|
+
const wait = Math.min(expDelay(delay, i, factor), maxDelay)
|
|
45
|
+
await new Promise((r) => setTimeout(r, wait))
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
throw last
|
|
49
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rwlock.ts — Readers-writer lock map (writer priority, no starvation)
|
|
3
|
+
* Zero deps.
|
|
4
|
+
*
|
|
5
|
+
* using r = await rwlock.read("key")
|
|
6
|
+
* using w = await rwlock.write("key")
|
|
7
|
+
*/
|
|
8
|
+
const map = new Map<string, { r: number; w: boolean; wr: (() => void)[]; ww: (() => void)[] }>()
|
|
9
|
+
|
|
10
|
+
function get(k: string) {
|
|
11
|
+
if (!map.has(k)) map.set(k, { r: 0, w: false, wr: [], ww: [] })
|
|
12
|
+
return map.get(k)!
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function drain(k: string) {
|
|
16
|
+
const l = map.get(k)
|
|
17
|
+
if (!l || l.w || l.r > 0) return
|
|
18
|
+
if (l.ww.length > 0) { l.ww.shift()!(); return }
|
|
19
|
+
while (l.wr.length > 0) l.wr.shift()!()
|
|
20
|
+
if (l.r === 0 && !l.w && l.wr.length === 0 && l.ww.length === 0) map.delete(k)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function mkRelease(k: string, mode: "r" | "w"): Disposable {
|
|
24
|
+
return { [Symbol.dispose]: () => { const l = map.get(k); if (!l) return; if (mode === "w") l.w = false; else l.r--; drain(k) } }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function read(k: string): Promise<Disposable> {
|
|
28
|
+
const l = get(k)
|
|
29
|
+
return new Promise<Disposable>((res) => {
|
|
30
|
+
if (!l.w && l.ww.length === 0) { l.r++; res(mkRelease(k, "r")) }
|
|
31
|
+
else l.wr.push(() => { l.r++; res(mkRelease(k, "r")) })
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function write(k: string): Promise<Disposable> {
|
|
36
|
+
const l = get(k)
|
|
37
|
+
return new Promise<Disposable>((res) => {
|
|
38
|
+
if (!l.w && l.r === 0) { l.w = true; res(mkRelease(k, "w")) }
|
|
39
|
+
else l.ww.push(() => { l.w = true; res(mkRelease(k, "w")) })
|
|
40
|
+
})
|
|
41
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* schedule.ts — Retry schedule patterns (exponential/fibonacci)
|
|
3
|
+
* Inspired by Effect-TS Schedule (MIT)
|
|
4
|
+
* Deps: none
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// ── Pure functions (stateless, safe for concurrent use) ──
|
|
8
|
+
|
|
9
|
+
export function expDelay(baseMs: number, attempt: number, factor = 2): number {
|
|
10
|
+
return baseMs * Math.pow(factor, attempt)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function fibDelay(oneMs: number, attempt: number): number {
|
|
14
|
+
if (attempt <= 0) return 0
|
|
15
|
+
let a = 0, b = 1
|
|
16
|
+
for (let i = 1; i < attempt; i++) { [a, b] = [b, a + b] }
|
|
17
|
+
return b * oneMs
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// ── Stateful iterators (for retryWith / single-owner retry loops) ──
|
|
21
|
+
|
|
22
|
+
export interface ScheduleStep {
|
|
23
|
+
delay: number
|
|
24
|
+
attempt: number
|
|
25
|
+
done: boolean
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ScheduleBuilder {
|
|
29
|
+
next(): ScheduleStep
|
|
30
|
+
reset(): void
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function exponential(baseMs: number, factor = 2, maxRetries = Infinity): ScheduleBuilder {
|
|
34
|
+
let attempt = 0
|
|
35
|
+
return {
|
|
36
|
+
next: () => {
|
|
37
|
+
if (attempt >= maxRetries) return { delay: 0, attempt, done: true }
|
|
38
|
+
const delay = expDelay(baseMs, attempt, factor)
|
|
39
|
+
return { delay, attempt: attempt++, done: false }
|
|
40
|
+
},
|
|
41
|
+
reset: () => { attempt = 0 },
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function fibonacci(oneMs: number, maxRetries = Infinity): ScheduleBuilder {
|
|
46
|
+
let a = 0, b = 1, attempt = 0
|
|
47
|
+
return {
|
|
48
|
+
next: () => {
|
|
49
|
+
if (attempt >= maxRetries) return { delay: 0, attempt, done: true }
|
|
50
|
+
const delay = a * oneMs; [a, b] = [b, a + b]
|
|
51
|
+
return { delay, attempt: attempt++, done: false }
|
|
52
|
+
},
|
|
53
|
+
reset: () => { a = 0; b = 1; attempt = 0 },
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function capDelay(maxMs: number, s: ScheduleBuilder): ScheduleBuilder {
|
|
58
|
+
return {
|
|
59
|
+
next: () => { const r = s.next(); return { ...r, delay: Math.min(r.delay, maxMs) } },
|
|
60
|
+
reset: () => { s.reset() },
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function retryWith<T>(fn: (attempt: number) => Promise<T>, s: ScheduleBuilder): Promise<T> {
|
|
65
|
+
for (;;) {
|
|
66
|
+
const step = s.next()
|
|
67
|
+
if (step.done) throw new Error(`Retry exhausted after ${step.attempt} attempts`)
|
|
68
|
+
if (step.attempt > 0) await new Promise(r => setTimeout(r, step.delay))
|
|
69
|
+
try { const v = await fn(step.attempt); s.reset(); return v } catch { continue }
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "async_hooks"
|
|
2
|
+
|
|
3
|
+
export class NotFound extends Error {
|
|
4
|
+
constructor(public override readonly name: string) {
|
|
5
|
+
super(`no context for ${name}`)
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function scope<T>(label: string) {
|
|
10
|
+
const store = new AsyncLocalStorage<T>()
|
|
11
|
+
return {
|
|
12
|
+
use(): T {
|
|
13
|
+
const v = store.getStore()
|
|
14
|
+
if (!v) throw new NotFound(label)
|
|
15
|
+
return v
|
|
16
|
+
},
|
|
17
|
+
provide<R>(val: T, fn: () => R): R {
|
|
18
|
+
return store.run(val, fn)
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ttl-cache.ts — Generic TTL cache with Map/WeakMap storage
|
|
3
|
+
* Ported from gemini-cli CacheService (Apache-2.0)
|
|
4
|
+
* Deps: none
|
|
5
|
+
*/
|
|
6
|
+
export interface CacheOpts {
|
|
7
|
+
defaultTtl?: number
|
|
8
|
+
storage?: "map" | "weakmap"
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface Entry<V> { value: V; ts: number; ttl?: number }
|
|
12
|
+
|
|
13
|
+
export class TTLCache<K extends object | string | undefined, V> {
|
|
14
|
+
private store: Map<K, Entry<V>> | WeakMap<WeakKey, Entry<V>>
|
|
15
|
+
private defaultTtl?: number
|
|
16
|
+
|
|
17
|
+
constructor(opts: CacheOpts = {}) {
|
|
18
|
+
this.store = opts.storage === "weakmap"
|
|
19
|
+
? new WeakMap<WeakKey, Entry<V>>()
|
|
20
|
+
: new Map<K, Entry<V>>()
|
|
21
|
+
this.defaultTtl = opts.defaultTtl
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get(key: K): V | undefined {
|
|
25
|
+
const e = (this.store as any).get(key) as Entry<V> | undefined
|
|
26
|
+
if (!e) return undefined
|
|
27
|
+
const ttl = e.ttl ?? this.defaultTtl
|
|
28
|
+
if (ttl !== undefined && Date.now() - e.ts > ttl) {
|
|
29
|
+
this.delete(key)
|
|
30
|
+
return undefined
|
|
31
|
+
}
|
|
32
|
+
return e.value
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
set(key: K, value: V, ttl?: number): void {
|
|
36
|
+
(this.store as any).set(key, { value, ts: Date.now(), ttl })
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
getOrCreate(key: K, creator: () => V, ttl?: number): V {
|
|
40
|
+
const v = this.get(key)
|
|
41
|
+
if (v !== undefined) return v
|
|
42
|
+
const created = creator()
|
|
43
|
+
this.set(key, created, ttl)
|
|
44
|
+
return created
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
delete(key: K): void { (this.store as any).delete(key) }
|
|
48
|
+
|
|
49
|
+
clear(): void {
|
|
50
|
+
if (this.store instanceof Map) this.store.clear()
|
|
51
|
+
else throw new Error("clear() not supported on WeakMap")
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function createCache<K extends string | undefined, V>(
|
|
56
|
+
opts: CacheOpts & { storage: "map" },
|
|
57
|
+
): TTLCache<K, V>
|
|
58
|
+
export function createCache<K extends object, V>(opts?: CacheOpts): TTLCache<K, V>
|
|
59
|
+
export function createCache<K extends object | string | undefined, V>(
|
|
60
|
+
opts: CacheOpts = {},
|
|
61
|
+
): TTLCache<K, V> {
|
|
62
|
+
return new TTLCache<K, V>(opts)
|
|
63
|
+
}
|