@code-yeongyu/senpi 2026.5.29 → 2026.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +131 -1
- package/README.md +12 -2
- package/dist/cli/args.d.ts +3 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +28 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +9 -1
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session-services.d.ts +1 -0
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +1 -0
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +9 -2
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +36 -13
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/compaction/branch-summarization.d.ts +3 -1
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js +9 -3
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +5 -3
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +21 -3
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +14 -6
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts +2 -0
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +29 -1
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +82 -21
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +2 -1
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/provider-attribution.d.ts +4 -0
- package/dist/core/provider-attribution.d.ts.map +1 -0
- package/dist/core/provider-attribution.js +73 -0
- package/dist/core/provider-attribution.js.map +1 -0
- package/dist/core/provider-display-names.d.ts.map +1 -1
- package/dist/core/provider-display-names.js +1 -0
- package/dist/core/provider-display-names.js.map +1 -1
- package/dist/core/resolve-config-value.d.ts +9 -1
- package/dist/core/resolve-config-value.d.ts.map +1 -1
- package/dist/core/resolve-config-value.js +134 -11
- package/dist/core/resolve-config-value.js.map +1 -1
- package/dist/core/sdk.d.ts +2 -0
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +18 -40
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts +6 -7
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +167 -96
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +3 -1
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +15 -11
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +0 -3
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/thinking-levels.d.ts.map +1 -1
- package/dist/core/thinking-levels.js +6 -2
- package/dist/core/thinking-levels.js.map +1 -1
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +7 -10
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js +5 -7
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +6 -7
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/render-utils.d.ts +5 -2
- package/dist/core/tools/render-utils.d.ts.map +1 -1
- package/dist/core/tools/render-utils.js +17 -1
- package/dist/core/tools/render-utils.js.map +1 -1
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +5 -6
- package/dist/core/tools/write.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +76 -16
- package/dist/main.js.map +1 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +118 -1
- package/dist/migrations.js.map +1 -1
- package/dist/modes/interactive/components/login-dialog.d.ts +1 -3
- package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/dist/modes/interactive/components/login-dialog.js +2 -4
- package/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +25 -1
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +3 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +64 -6
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +10 -0
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +1 -0
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +4 -1
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +1 -0
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/utils/deprecation.d.ts +4 -0
- package/dist/utils/deprecation.d.ts.map +1 -0
- package/dist/utils/deprecation.js +13 -0
- package/dist/utils/deprecation.js.map +1 -0
- package/dist/utils/json.d.ts +3 -0
- package/dist/utils/json.d.ts.map +1 -0
- package/dist/utils/json.js +7 -0
- package/dist/utils/json.js.map +1 -0
- package/docs/custom-provider.md +13 -10
- package/docs/extensions.md +47 -17
- package/docs/models.md +25 -12
- package/docs/providers.md +15 -5
- package/docs/quickstart.md +1 -0
- package/docs/rpc.md +3 -2
- package/docs/sdk.md +6 -0
- package/docs/session-format.md +1 -1
- package/docs/sessions.md +8 -0
- package/docs/settings.md +4 -2
- package/docs/terminal-setup.md +2 -0
- package/docs/tui.md +12 -3
- package/docs/usage.md +10 -1
- package/examples/extensions/README.md +1 -0
- package/examples/extensions/custom-header.ts +1 -1
- package/examples/extensions/custom-provider-anthropic/index.ts +1 -1
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/index.ts +54 -3
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/doom-overlay/index.ts +1 -1
- package/examples/extensions/git-merge-and-resolve.ts +115 -0
- package/examples/extensions/handoff.ts +1 -1
- package/examples/extensions/input-transform-streaming.ts +39 -0
- package/examples/extensions/interactive-shell.ts +1 -1
- package/examples/extensions/overlay-qa-tests.ts +152 -81
- package/examples/extensions/qna.ts +1 -1
- package/examples/extensions/question.ts +1 -1
- package/examples/extensions/questionnaire.ts +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/snake.ts +1 -1
- package/examples/extensions/space-invaders.ts +1 -1
- package/examples/extensions/summarize.ts +1 -1
- package/examples/extensions/tic-tac-toe.ts +1 -1
- package/examples/extensions/todo.ts +1 -1
- package/examples/extensions/tools.ts +5 -0
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/agent.js +15 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/agent.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/agent-harness.d.ts +5 -2
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/agent-harness.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/agent-harness.js +81 -18
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/agent-harness.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/branch-summarization.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/branch-summarization.js +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/branch-summarization.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.d.ts +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.js +14 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.d.ts +22 -8
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
- package/node_modules/@earendil-works/pi-ai/README.md +5 -3
- package/node_modules/@earendil-works/pi-ai/dist/cli.js +0 -0
- package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.js +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.d.ts +15 -0
- package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js +15 -0
- package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/models.d.ts +2 -2
- package/node_modules/@earendil-works/pi-ai/dist/models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts +1294 -412
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.js +1278 -652
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/models.js +9 -4
- package/node_modules/@earendil-works/pi-ai/dist/models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.d.ts +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.js +89 -21
- package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.js +27 -14
- package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.js +5 -9
- package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/google-vertex.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/google-vertex.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/google-vertex.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/google.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/google.js +5 -3
- package/node_modules/@earendil-works/pi-ai/dist/providers/google.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/images/openrouter.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/images/openrouter.js +2 -3
- package/node_modules/@earendil-works/pi-ai/dist/providers/images/openrouter.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/mistral.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/mistral.js +2 -3
- package/node_modules/@earendil-works/pi-ai/dist/providers/mistral.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex-responses.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex-responses.js +118 -52
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.js +27 -17
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses-shared.d.ts +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses-shared.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses-shared.js +5 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses-shared.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.js +5 -9
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/simple-options.js +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/simple-options.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/transform-messages.d.ts +7 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/transform-messages.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/transform-messages.js +8 -4
- package/node_modules/@earendil-works/pi-ai/dist/providers/transform-messages.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/stream.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/stream.js +18 -4
- package/node_modules/@earendil-works/pi-ai/dist/stream.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/types.d.ts +21 -5
- package/node_modules/@earendil-works/pi-ai/dist/types.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/types.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/abort-signals.d.ts +6 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/abort-signals.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/abort-signals.js +34 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/abort-signals.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/device-code.d.ts +9 -7
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/device-code.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/device-code.js +8 -7
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/device-code.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/github-copilot.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/github-copilot.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/index.d.ts +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/index.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/index.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/index.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/openai-codex.d.ts +10 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/openai-codex.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/openai-codex.js +179 -79
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/openai-codex.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/package.json +2 -2
- package/node_modules/@earendil-works/pi-tui/README.md +15 -3
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +9 -53
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/input.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/input.js +6 -54
- package/node_modules/@earendil-works/pi-tui/dist/components/input.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/index.d.ts +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/index.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/index.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/terminal-image.d.ts +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/terminal-image.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/terminal-image.js +34 -7
- package/node_modules/@earendil-works/pi-tui/dist/terminal-image.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts +33 -10
- package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/terminal.js +173 -39
- package/node_modules/@earendil-works/pi-tui/dist/terminal.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/tui.d.ts +18 -3
- package/node_modules/@earendil-works/pi-tui/dist/tui.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/tui.js +166 -22
- package/node_modules/@earendil-works/pi-tui/dist/tui.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/utils.d.ts +1 -0
- package/node_modules/@earendil-works/pi-tui/dist/utils.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/utils.js +11 -3
- package/node_modules/@earendil-works/pi-tui/dist/utils.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/word-navigation.d.ts +25 -0
- package/node_modules/@earendil-works/pi-tui/dist/word-navigation.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-tui/dist/word-navigation.js +96 -0
- package/node_modules/@earendil-works/pi-tui/dist/word-navigation.js.map +1 -0
- package/node_modules/@earendil-works/pi-tui/package.json +2 -2
- package/npm-shrinkwrap.json +56 -56
- package/package.json +5 -5
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { randomBytes, randomUUID } from "crypto";
|
|
2
|
-
import { appendFileSync, closeSync, existsSync, mkdirSync, openSync, readdirSync,
|
|
3
|
-
import { readdir,
|
|
2
|
+
import { appendFileSync, closeSync, createReadStream, existsSync, mkdirSync, openSync, readdirSync, readSync, statSync, writeFileSync, } from "fs";
|
|
3
|
+
import { readdir, stat } from "fs/promises";
|
|
4
4
|
import { join, resolve } from "path";
|
|
5
|
+
import { createInterface } from "readline";
|
|
6
|
+
import { StringDecoder } from "string_decoder";
|
|
5
7
|
import { getAgentDir as getDefaultAgentDir, getSessionsDir } from "../config.js";
|
|
6
8
|
import { normalizePath, resolvePath } from "../utils/paths.js";
|
|
7
9
|
// Fork change: inlined UUIDv7 (upstream uses the `uuid` npm package). Keeps this
|
|
@@ -29,6 +31,11 @@ export const CURRENT_SESSION_VERSION = 3;
|
|
|
29
31
|
function createSessionId() {
|
|
30
32
|
return uuidv7();
|
|
31
33
|
}
|
|
34
|
+
export function assertValidSessionId(id) {
|
|
35
|
+
if (!/^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/.test(id)) {
|
|
36
|
+
throw new Error("Session id must be non-empty, contain only alphanumeric characters, '-', '_', and '.', and start and end with an alphanumeric character");
|
|
37
|
+
}
|
|
38
|
+
}
|
|
32
39
|
/** Generate a unique short ID (8 hex chars, collision-checked) */
|
|
33
40
|
function generateId(byId) {
|
|
34
41
|
for (let i = 0; i < 100; i++) {
|
|
@@ -229,34 +236,65 @@ export function buildSessionContext(entries, leafId, byId) {
|
|
|
229
236
|
* Compute the default session directory for a cwd.
|
|
230
237
|
* Encodes cwd into a safe directory name under ~/.senpi/agent/sessions/.
|
|
231
238
|
*/
|
|
232
|
-
|
|
239
|
+
function getDefaultSessionDirPath(cwd, agentDir = getDefaultAgentDir()) {
|
|
233
240
|
const resolvedCwd = resolvePath(cwd);
|
|
234
241
|
const resolvedAgentDir = resolvePath(agentDir);
|
|
235
242
|
const safePath = `--${resolvedCwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
|
|
236
|
-
|
|
243
|
+
return join(resolvedAgentDir, "sessions", safePath);
|
|
244
|
+
}
|
|
245
|
+
export function getDefaultSessionDir(cwd, agentDir = getDefaultAgentDir()) {
|
|
246
|
+
const sessionDir = getDefaultSessionDirPath(cwd, agentDir);
|
|
237
247
|
if (!existsSync(sessionDir)) {
|
|
238
248
|
mkdirSync(sessionDir, { recursive: true });
|
|
239
249
|
}
|
|
240
250
|
return sessionDir;
|
|
241
251
|
}
|
|
252
|
+
const SESSION_READ_BUFFER_SIZE = 1024 * 1024;
|
|
253
|
+
function parseSessionEntryLine(line) {
|
|
254
|
+
if (!line.trim())
|
|
255
|
+
return null;
|
|
256
|
+
try {
|
|
257
|
+
return JSON.parse(line);
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
// Skip malformed lines
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
242
264
|
/** Exported for testing */
|
|
243
265
|
export function loadEntriesFromFile(filePath) {
|
|
244
266
|
const resolvedFilePath = normalizePath(filePath);
|
|
245
267
|
if (!existsSync(resolvedFilePath))
|
|
246
268
|
return [];
|
|
247
|
-
const content = readFileSync(resolvedFilePath, "utf8");
|
|
248
269
|
const entries = [];
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
270
|
+
const fd = openSync(resolvedFilePath, "r");
|
|
271
|
+
try {
|
|
272
|
+
const decoder = new StringDecoder("utf8");
|
|
273
|
+
const buffer = Buffer.allocUnsafe(SESSION_READ_BUFFER_SIZE);
|
|
274
|
+
let pending = "";
|
|
275
|
+
while (true) {
|
|
276
|
+
const bytesRead = readSync(fd, buffer, 0, buffer.length, null);
|
|
277
|
+
if (bytesRead === 0)
|
|
278
|
+
break;
|
|
279
|
+
pending += decoder.write(buffer.subarray(0, bytesRead));
|
|
280
|
+
let lineStart = 0;
|
|
281
|
+
let newlineIndex = pending.indexOf("\n", lineStart);
|
|
282
|
+
while (newlineIndex !== -1) {
|
|
283
|
+
const entry = parseSessionEntryLine(pending.slice(lineStart, newlineIndex));
|
|
284
|
+
if (entry)
|
|
285
|
+
entries.push(entry);
|
|
286
|
+
lineStart = newlineIndex + 1;
|
|
287
|
+
newlineIndex = pending.indexOf("\n", lineStart);
|
|
288
|
+
}
|
|
289
|
+
pending = pending.slice(lineStart);
|
|
259
290
|
}
|
|
291
|
+
pending += decoder.end();
|
|
292
|
+
const finalEntry = parseSessionEntryLine(pending);
|
|
293
|
+
if (finalEntry)
|
|
294
|
+
entries.push(finalEntry);
|
|
295
|
+
}
|
|
296
|
+
finally {
|
|
297
|
+
closeSync(fd);
|
|
260
298
|
}
|
|
261
299
|
// Validate session header
|
|
262
300
|
if (entries.length === 0)
|
|
@@ -267,7 +305,7 @@ export function loadEntriesFromFile(filePath) {
|
|
|
267
305
|
}
|
|
268
306
|
return entries;
|
|
269
307
|
}
|
|
270
|
-
function
|
|
308
|
+
function readSessionHeader(filePath) {
|
|
271
309
|
try {
|
|
272
310
|
const fd = openSync(filePath, "r");
|
|
273
311
|
const buffer = Buffer.alloc(512);
|
|
@@ -275,23 +313,36 @@ function isValidSessionFile(filePath) {
|
|
|
275
313
|
closeSync(fd);
|
|
276
314
|
const firstLine = buffer.toString("utf8", 0, bytesRead).split("\n")[0];
|
|
277
315
|
if (!firstLine)
|
|
278
|
-
return
|
|
316
|
+
return null;
|
|
279
317
|
const header = JSON.parse(firstLine);
|
|
280
|
-
|
|
318
|
+
if (header.type !== "session" || typeof header.id !== "string") {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
return header;
|
|
281
322
|
}
|
|
282
323
|
catch {
|
|
283
|
-
return
|
|
324
|
+
return null;
|
|
284
325
|
}
|
|
285
326
|
}
|
|
327
|
+
function getSessionHeaderCwd(header) {
|
|
328
|
+
const cwd = header.cwd;
|
|
329
|
+
return typeof cwd === "string" ? cwd : undefined;
|
|
330
|
+
}
|
|
331
|
+
function sessionCwdMatches(cwd, resolvedCwd) {
|
|
332
|
+
return cwd !== undefined && cwd !== "" && resolvePath(cwd) === resolvedCwd;
|
|
333
|
+
}
|
|
286
334
|
/** Exported for testing */
|
|
287
|
-
export function findMostRecentSession(sessionDir) {
|
|
335
|
+
export function findMostRecentSession(sessionDir, cwd) {
|
|
288
336
|
const resolvedSessionDir = normalizePath(sessionDir);
|
|
337
|
+
const resolvedCwd = cwd ? resolvePath(cwd) : undefined;
|
|
289
338
|
try {
|
|
290
339
|
const files = readdirSync(resolvedSessionDir)
|
|
291
340
|
.filter((f) => f.endsWith(".jsonl"))
|
|
292
341
|
.map((f) => join(resolvedSessionDir, f))
|
|
293
|
-
.
|
|
294
|
-
.
|
|
342
|
+
.map((path) => ({ path, header: readSessionHeader(path) }))
|
|
343
|
+
.filter((file) => file.header !== null &&
|
|
344
|
+
(!resolvedCwd || sessionCwdMatches(getSessionHeaderCwd(file.header), resolvedCwd)))
|
|
345
|
+
.map(({ path }) => ({ path, mtime: statSync(path).mtime }))
|
|
295
346
|
.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
296
347
|
return files[0]?.path || null;
|
|
297
348
|
}
|
|
@@ -312,73 +363,53 @@ function extractTextContent(message) {
|
|
|
312
363
|
.map((block) => block.text)
|
|
313
364
|
.join(" ");
|
|
314
365
|
}
|
|
315
|
-
function
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
continue;
|
|
325
|
-
const msgTimestamp = message.timestamp;
|
|
326
|
-
if (typeof msgTimestamp === "number") {
|
|
327
|
-
lastActivityTime = Math.max(lastActivityTime ?? 0, msgTimestamp);
|
|
328
|
-
continue;
|
|
329
|
-
}
|
|
330
|
-
const entryTimestamp = entry.timestamp;
|
|
331
|
-
if (typeof entryTimestamp === "string") {
|
|
332
|
-
const t = new Date(entryTimestamp).getTime();
|
|
333
|
-
if (!Number.isNaN(t)) {
|
|
334
|
-
lastActivityTime = Math.max(lastActivityTime ?? 0, t);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
return lastActivityTime;
|
|
339
|
-
}
|
|
340
|
-
function getSessionModifiedDate(entries, header, statsMtime) {
|
|
341
|
-
const lastActivityTime = getLastActivityTime(entries);
|
|
342
|
-
if (typeof lastActivityTime === "number" && lastActivityTime > 0) {
|
|
343
|
-
return new Date(lastActivityTime);
|
|
366
|
+
function getMessageActivityTime(entry) {
|
|
367
|
+
const message = entry.message;
|
|
368
|
+
if (!isMessageWithContent(message))
|
|
369
|
+
return undefined;
|
|
370
|
+
if (message.role !== "user" && message.role !== "assistant")
|
|
371
|
+
return undefined;
|
|
372
|
+
const msgTimestamp = message.timestamp;
|
|
373
|
+
if (typeof msgTimestamp === "number") {
|
|
374
|
+
return msgTimestamp;
|
|
344
375
|
}
|
|
345
|
-
const
|
|
346
|
-
return
|
|
376
|
+
const t = new Date(entry.timestamp).getTime();
|
|
377
|
+
return Number.isNaN(t) ? undefined : t;
|
|
347
378
|
}
|
|
348
379
|
async function buildSessionInfo(filePath) {
|
|
349
380
|
try {
|
|
350
|
-
const content = await readFile(filePath, "utf8");
|
|
351
|
-
const entries = [];
|
|
352
|
-
const lines = content.trim().split("\n");
|
|
353
|
-
for (const line of lines) {
|
|
354
|
-
if (!line.trim())
|
|
355
|
-
continue;
|
|
356
|
-
try {
|
|
357
|
-
entries.push(JSON.parse(line));
|
|
358
|
-
}
|
|
359
|
-
catch {
|
|
360
|
-
// Skip malformed lines
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
if (entries.length === 0)
|
|
364
|
-
return null;
|
|
365
|
-
const header = entries[0];
|
|
366
|
-
if (header.type !== "session")
|
|
367
|
-
return null;
|
|
368
381
|
const stats = await stat(filePath);
|
|
382
|
+
let header = null;
|
|
369
383
|
let messageCount = 0;
|
|
370
384
|
let firstMessage = "";
|
|
371
385
|
const allMessages = [];
|
|
372
386
|
let name;
|
|
373
|
-
|
|
387
|
+
let lastActivityTime;
|
|
388
|
+
const rl = createInterface({
|
|
389
|
+
input: createReadStream(filePath, { encoding: "utf8" }),
|
|
390
|
+
crlfDelay: Infinity,
|
|
391
|
+
});
|
|
392
|
+
for await (const line of rl) {
|
|
393
|
+
const entry = parseSessionEntryLine(line);
|
|
394
|
+
if (!entry)
|
|
395
|
+
continue;
|
|
396
|
+
if (!header) {
|
|
397
|
+
if (entry.type !== "session")
|
|
398
|
+
return null;
|
|
399
|
+
header = entry;
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
374
402
|
// Extract session name (use latest, including explicit clears)
|
|
375
403
|
if (entry.type === "session_info") {
|
|
376
|
-
|
|
377
|
-
name = infoEntry.name?.trim() || undefined;
|
|
404
|
+
name = entry.name?.trim() || undefined;
|
|
378
405
|
}
|
|
379
406
|
if (entry.type !== "message")
|
|
380
407
|
continue;
|
|
381
408
|
messageCount++;
|
|
409
|
+
const activityTime = getMessageActivityTime(entry);
|
|
410
|
+
if (typeof activityTime === "number") {
|
|
411
|
+
lastActivityTime = Math.max(lastActivityTime ?? 0, activityTime);
|
|
412
|
+
}
|
|
382
413
|
const message = entry.message;
|
|
383
414
|
if (!isMessageWithContent(message))
|
|
384
415
|
continue;
|
|
@@ -392,9 +423,16 @@ async function buildSessionInfo(filePath) {
|
|
|
392
423
|
firstMessage = textContent;
|
|
393
424
|
}
|
|
394
425
|
}
|
|
426
|
+
if (!header)
|
|
427
|
+
return null;
|
|
395
428
|
const cwd = typeof header.cwd === "string" ? header.cwd : "";
|
|
396
429
|
const parentSessionPath = header.parentSession;
|
|
397
|
-
const
|
|
430
|
+
const headerTime = typeof header.timestamp === "string" ? new Date(header.timestamp).getTime() : NaN;
|
|
431
|
+
const modified = typeof lastActivityTime === "number" && lastActivityTime > 0
|
|
432
|
+
? new Date(lastActivityTime)
|
|
433
|
+
: !Number.isNaN(headerTime)
|
|
434
|
+
? new Date(headerTime)
|
|
435
|
+
: stats.mtime;
|
|
398
436
|
return {
|
|
399
437
|
path: filePath,
|
|
400
438
|
id: header.id,
|
|
@@ -494,7 +532,7 @@ export class SessionManager {
|
|
|
494
532
|
labelsById = new Map();
|
|
495
533
|
labelTimestampsById = new Map();
|
|
496
534
|
leafId = null;
|
|
497
|
-
constructor(cwd, sessionDir, sessionFile, persist) {
|
|
535
|
+
constructor(cwd, sessionDir, sessionFile, persist, newSessionOptions) {
|
|
498
536
|
this.cwd = resolvePath(cwd);
|
|
499
537
|
this.sessionDir = normalizePath(sessionDir);
|
|
500
538
|
this.persist = persist;
|
|
@@ -505,7 +543,7 @@ export class SessionManager {
|
|
|
505
543
|
this.setSessionFile(sessionFile);
|
|
506
544
|
}
|
|
507
545
|
else {
|
|
508
|
-
this.newSession();
|
|
546
|
+
this.newSession(newSessionOptions);
|
|
509
547
|
}
|
|
510
548
|
}
|
|
511
549
|
/** Switch to a different session file (used for resume and branching) */
|
|
@@ -538,6 +576,9 @@ export class SessionManager {
|
|
|
538
576
|
}
|
|
539
577
|
}
|
|
540
578
|
newSession(options) {
|
|
579
|
+
if (options?.id !== undefined) {
|
|
580
|
+
assertValidSessionId(options.id);
|
|
581
|
+
}
|
|
541
582
|
this.sessionId = options?.id ?? createSessionId();
|
|
542
583
|
const timestamp = new Date().toISOString();
|
|
543
584
|
const header = {
|
|
@@ -584,8 +625,15 @@ export class SessionManager {
|
|
|
584
625
|
_rewriteFile() {
|
|
585
626
|
if (!this.persist || !this.sessionFile)
|
|
586
627
|
return;
|
|
587
|
-
const
|
|
588
|
-
|
|
628
|
+
const fd = openSync(this.sessionFile, "w");
|
|
629
|
+
try {
|
|
630
|
+
for (const entry of this.fileEntries) {
|
|
631
|
+
writeFileSync(fd, `${JSON.stringify(entry)}\n`);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
finally {
|
|
635
|
+
closeSync(fd);
|
|
636
|
+
}
|
|
589
637
|
}
|
|
590
638
|
isPersisted() {
|
|
591
639
|
return this.persist;
|
|
@@ -596,6 +644,9 @@ export class SessionManager {
|
|
|
596
644
|
getSessionDir() {
|
|
597
645
|
return this.sessionDir;
|
|
598
646
|
}
|
|
647
|
+
usesDefaultSessionDir() {
|
|
648
|
+
return this.sessionDir === getDefaultSessionDirPath(this.cwd);
|
|
649
|
+
}
|
|
599
650
|
getSessionId() {
|
|
600
651
|
return this.sessionId;
|
|
601
652
|
}
|
|
@@ -607,13 +658,24 @@ export class SessionManager {
|
|
|
607
658
|
return;
|
|
608
659
|
const hasAssistant = this.fileEntries.some((e) => e.type === "message" && e.message.role === "assistant");
|
|
609
660
|
if (!hasAssistant) {
|
|
610
|
-
|
|
611
|
-
|
|
661
|
+
if (this.flushed) {
|
|
662
|
+
appendFileSync(this.sessionFile, `${JSON.stringify(entry)}\n`);
|
|
663
|
+
}
|
|
664
|
+
else {
|
|
665
|
+
// Mark as not flushed so when assistant arrives, all entries get written
|
|
666
|
+
this.flushed = false;
|
|
667
|
+
}
|
|
612
668
|
return;
|
|
613
669
|
}
|
|
614
670
|
if (!this.flushed) {
|
|
615
|
-
|
|
616
|
-
|
|
671
|
+
const fd = openSync(this.sessionFile, "wx");
|
|
672
|
+
try {
|
|
673
|
+
for (const e of this.fileEntries) {
|
|
674
|
+
writeFileSync(fd, `${JSON.stringify(e)}\n`);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
finally {
|
|
678
|
+
closeSync(fd);
|
|
617
679
|
}
|
|
618
680
|
this.flushed = true;
|
|
619
681
|
}
|
|
@@ -1023,9 +1085,9 @@ export class SessionManager {
|
|
|
1023
1085
|
* @param cwd Working directory (stored in session header)
|
|
1024
1086
|
* @param sessionDir Optional session directory. If omitted, uses default (~/.senpi/agent/sessions/<encoded-cwd>/).
|
|
1025
1087
|
*/
|
|
1026
|
-
static create(cwd, sessionDir) {
|
|
1088
|
+
static create(cwd, sessionDir, options) {
|
|
1027
1089
|
const dir = sessionDir ? normalizePath(sessionDir) : getDefaultSessionDir(cwd);
|
|
1028
|
-
return new SessionManager(cwd, dir, undefined, true);
|
|
1090
|
+
return new SessionManager(cwd, dir, undefined, true, options);
|
|
1029
1091
|
}
|
|
1030
1092
|
/**
|
|
1031
1093
|
* Open a specific session file.
|
|
@@ -1050,7 +1112,8 @@ export class SessionManager {
|
|
|
1050
1112
|
*/
|
|
1051
1113
|
static continueRecent(cwd, sessionDir) {
|
|
1052
1114
|
const dir = sessionDir ? normalizePath(sessionDir) : getDefaultSessionDir(cwd);
|
|
1053
|
-
const
|
|
1115
|
+
const filterCwd = sessionDir !== undefined && dir !== getDefaultSessionDirPath(cwd);
|
|
1116
|
+
const mostRecent = findMostRecentSession(dir, filterCwd ? cwd : undefined);
|
|
1054
1117
|
if (mostRecent) {
|
|
1055
1118
|
return new SessionManager(cwd, dir, mostRecent, true);
|
|
1056
1119
|
}
|
|
@@ -1067,7 +1130,7 @@ export class SessionManager {
|
|
|
1067
1130
|
* @param targetCwd Target working directory (where the new session will be stored)
|
|
1068
1131
|
* @param sessionDir Optional session directory. If omitted, uses default for targetCwd.
|
|
1069
1132
|
*/
|
|
1070
|
-
static forkFrom(sourcePath, targetCwd, sessionDir) {
|
|
1133
|
+
static forkFrom(sourcePath, targetCwd, sessionDir, options) {
|
|
1071
1134
|
const resolvedSourcePath = resolvePath(sourcePath);
|
|
1072
1135
|
const resolvedTargetCwd = resolvePath(targetCwd);
|
|
1073
1136
|
const sourceEntries = loadEntriesFromFile(resolvedSourcePath);
|
|
@@ -1083,7 +1146,10 @@ export class SessionManager {
|
|
|
1083
1146
|
mkdirSync(dir, { recursive: true });
|
|
1084
1147
|
}
|
|
1085
1148
|
// Create new session file with new ID but forked content
|
|
1086
|
-
|
|
1149
|
+
if (options?.id !== undefined) {
|
|
1150
|
+
assertValidSessionId(options.id);
|
|
1151
|
+
}
|
|
1152
|
+
const newSessionId = options?.id ?? createSessionId();
|
|
1087
1153
|
const timestamp = new Date().toISOString();
|
|
1088
1154
|
const fileTimestamp = timestamp.replace(/[:.]/g, "-");
|
|
1089
1155
|
const newSessionFile = join(dir, `${fileTimestamp}_${newSessionId}.jsonl`);
|
|
@@ -1096,7 +1162,7 @@ export class SessionManager {
|
|
|
1096
1162
|
cwd: resolvedTargetCwd,
|
|
1097
1163
|
parentSession: resolvedSourcePath,
|
|
1098
1164
|
};
|
|
1099
|
-
|
|
1165
|
+
writeFileSync(newSessionFile, `${JSON.stringify(newHeader)}\n`, { flag: "wx" });
|
|
1100
1166
|
// Copy all non-header entries from source
|
|
1101
1167
|
for (const entry of sourceEntries) {
|
|
1102
1168
|
if (entry.type !== "session") {
|
|
@@ -1113,15 +1179,20 @@ export class SessionManager {
|
|
|
1113
1179
|
*/
|
|
1114
1180
|
static async list(cwd, sessionDir, onProgress) {
|
|
1115
1181
|
const dir = sessionDir ? normalizePath(sessionDir) : getDefaultSessionDir(cwd);
|
|
1116
|
-
const
|
|
1182
|
+
const filterCwd = sessionDir !== undefined && dir !== getDefaultSessionDirPath(cwd);
|
|
1183
|
+
const resolvedCwd = resolvePath(cwd);
|
|
1184
|
+
const sessions = (await listSessionsFromDir(dir, onProgress)).filter((session) => !filterCwd || sessionCwdMatches(session.cwd, resolvedCwd));
|
|
1117
1185
|
sessions.sort((a, b) => b.modified.getTime() - a.modified.getTime());
|
|
1118
1186
|
return sessions;
|
|
1119
1187
|
}
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1188
|
+
static async listAll(sessionDirOrOnProgress, onProgress) {
|
|
1189
|
+
const customSessionDir = typeof sessionDirOrOnProgress === "string" ? normalizePath(sessionDirOrOnProgress) : undefined;
|
|
1190
|
+
const progress = typeof sessionDirOrOnProgress === "function" ? sessionDirOrOnProgress : onProgress;
|
|
1191
|
+
if (customSessionDir) {
|
|
1192
|
+
const sessions = await listSessionsFromDir(customSessionDir, progress);
|
|
1193
|
+
sessions.sort((a, b) => b.modified.getTime() - a.modified.getTime());
|
|
1194
|
+
return sessions;
|
|
1195
|
+
}
|
|
1125
1196
|
const sessionsDir = getSessionsDir();
|
|
1126
1197
|
try {
|
|
1127
1198
|
if (!existsSync(sessionsDir)) {
|
|
@@ -1148,7 +1219,7 @@ export class SessionManager {
|
|
|
1148
1219
|
const allFiles = dirFiles.flat();
|
|
1149
1220
|
const results = await buildSessionInfosWithConcurrency(allFiles, () => {
|
|
1150
1221
|
loaded++;
|
|
1151
|
-
|
|
1222
|
+
progress?.(loaded, totalFiles);
|
|
1152
1223
|
});
|
|
1153
1224
|
for (const info of results) {
|
|
1154
1225
|
if (info) {
|