@downcity/agent 1.1.207 → 1.1.210
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/agent/local/Agent.d.ts +3 -3
- package/bin/agent/local/Agent.d.ts.map +1 -1
- package/bin/agent/local/Agent.js +0 -3
- package/bin/agent/local/Agent.js.map +1 -1
- package/bin/agent/local/AgentInstructions.d.ts +2 -1
- package/bin/agent/local/AgentInstructions.d.ts.map +1 -1
- package/bin/agent/local/AgentInstructions.js +10 -10
- package/bin/agent/local/AgentInstructions.js.map +1 -1
- package/bin/agent/local/services/AgentAssemblyService.d.ts +3 -8
- package/bin/agent/local/services/AgentAssemblyService.d.ts.map +1 -1
- package/bin/agent/local/services/AgentAssemblyService.js +0 -1
- package/bin/agent/local/services/AgentAssemblyService.js.map +1 -1
- package/bin/agent/local/services/AgentSessions.d.ts +5 -7
- package/bin/agent/local/services/AgentSessions.d.ts.map +1 -1
- package/bin/agent/local/services/AgentSessions.js +2 -3
- package/bin/agent/local/services/AgentSessions.js.map +1 -1
- package/bin/agent/remote/RemoteAgent.d.ts +3 -1
- package/bin/agent/remote/RemoteAgent.d.ts.map +1 -1
- package/bin/agent/remote/RemoteAgent.js.map +1 -1
- package/bin/agent/remote/RemoteSession.d.ts +10 -1
- package/bin/agent/remote/RemoteSession.d.ts.map +1 -1
- package/bin/agent/remote/RemoteSession.js +18 -0
- package/bin/agent/remote/RemoteSession.js.map +1 -1
- package/bin/agent/remote/RemoteTransport.d.ts +5 -1
- package/bin/agent/remote/RemoteTransport.d.ts.map +1 -1
- package/bin/agent/remote/transports/HttpRemoteAgentTransport.d.ts +4 -1
- package/bin/agent/remote/transports/HttpRemoteAgentTransport.d.ts.map +1 -1
- package/bin/agent/remote/transports/HttpRemoteAgentTransport.js +7 -0
- package/bin/agent/remote/transports/HttpRemoteAgentTransport.js.map +1 -1
- package/bin/agent/remote/transports/RpcRemoteAgentTransport.d.ts +4 -1
- package/bin/agent/remote/transports/RpcRemoteAgentTransport.d.ts.map +1 -1
- package/bin/agent/remote/transports/RpcRemoteAgentTransport.js +1 -0
- package/bin/agent/remote/transports/RpcRemoteAgentTransport.js.map +1 -1
- package/bin/executor/Executor.d.ts +0 -16
- package/bin/executor/Executor.d.ts.map +1 -1
- package/bin/executor/Executor.js +0 -16
- package/bin/executor/Executor.js.map +1 -1
- package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.d.ts +1 -3
- package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.d.ts.map +1 -1
- package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.js +1 -5
- package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.js.map +1 -1
- package/bin/executor/composer/history/SessionHistoryWriter.d.ts +0 -10
- package/bin/executor/composer/history/SessionHistoryWriter.d.ts.map +1 -1
- package/bin/executor/composer/history/SessionHistoryWriter.js +31 -61
- package/bin/executor/composer/history/SessionHistoryWriter.js.map +1 -1
- package/bin/executor/composer/system/default/StaticPromptCatalog.d.ts +4 -0
- package/bin/executor/composer/system/default/StaticPromptCatalog.d.ts.map +1 -1
- package/bin/executor/composer/system/default/StaticPromptCatalog.js +4 -0
- package/bin/executor/composer/system/default/StaticPromptCatalog.js.map +1 -1
- package/bin/executor/composer/system/default/SystemDomain.d.ts +1 -1
- package/bin/executor/composer/system/default/SystemDomain.d.ts.map +1 -1
- package/bin/executor/composer/system/default/SystemDomain.js +2 -7
- package/bin/executor/composer/system/default/SystemDomain.js.map +1 -1
- package/bin/executor/composer/system/default/SystemPromptAssets.d.ts +0 -4
- package/bin/executor/composer/system/default/SystemPromptAssets.d.ts.map +1 -1
- package/bin/executor/composer/system/default/SystemPromptAssets.js +0 -5
- package/bin/executor/composer/system/default/SystemPromptAssets.js.map +1 -1
- package/bin/executor/composer/system/default/assets/core.prompt.d.ts +1 -1
- package/bin/executor/composer/system/default/assets/core.prompt.d.ts.map +1 -1
- package/bin/executor/composer/system/default/assets/core.prompt.js +1 -1
- package/bin/executor/composer/system/default/assets/core.prompt.js.map +1 -1
- package/bin/executor/messages/AssistantFileResource.d.ts.map +1 -1
- package/bin/executor/messages/AssistantFileResource.js +58 -15
- package/bin/executor/messages/AssistantFileResource.js.map +1 -1
- package/bin/executor/services/ExecutorInflightService.d.ts +0 -5
- package/bin/executor/services/ExecutorInflightService.d.ts.map +1 -1
- package/bin/executor/services/ExecutorInflightService.js +0 -6
- package/bin/executor/services/ExecutorInflightService.js.map +1 -1
- package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.d.ts +4 -0
- package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.d.ts.map +1 -1
- package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.js +115 -3
- package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.js.map +1 -1
- package/bin/executor/types/SessionHistoryMeta.d.ts +6 -0
- package/bin/executor/types/SessionHistoryMeta.d.ts.map +1 -1
- package/bin/index.d.ts +38 -6
- package/bin/index.d.ts.map +1 -1
- package/bin/index.js +14 -1
- package/bin/index.js.map +1 -1
- package/bin/plugin/core/ActionScheduleExecutor.js +1 -1
- package/bin/plugin/core/ActionScheduleExecutor.js.map +1 -1
- package/bin/plugin/core/BasePlugin.d.ts +7 -9
- package/bin/plugin/core/BasePlugin.d.ts.map +1 -1
- package/bin/plugin/core/BasePlugin.js +1 -14
- package/bin/plugin/core/BasePlugin.js.map +1 -1
- package/bin/plugin/core/HookRegistry.d.ts +1 -1
- package/bin/plugin/core/HookRegistry.d.ts.map +1 -1
- package/bin/plugin/core/PluginActionFactory.d.ts +1 -5
- package/bin/plugin/core/PluginActionFactory.d.ts.map +1 -1
- package/bin/plugin/core/PluginActionFactory.js +0 -3
- package/bin/plugin/core/PluginActionFactory.js.map +1 -1
- package/bin/plugin/core/PluginActionRunner.d.ts +6 -4
- package/bin/plugin/core/PluginActionRunner.d.ts.map +1 -1
- package/bin/plugin/core/PluginActionRunner.js +2 -2
- package/bin/plugin/core/PluginActionRunner.js.map +1 -1
- package/bin/plugin/core/PluginCatalog.d.ts +2 -1
- package/bin/plugin/core/PluginCatalog.d.ts.map +1 -1
- package/bin/plugin/core/PluginCatalog.js.map +1 -1
- package/bin/plugin/core/PluginCommand.d.ts +1 -1
- package/bin/plugin/core/PluginCommand.d.ts.map +1 -1
- package/bin/plugin/core/PluginCommand.js.map +1 -1
- package/bin/plugin/core/PluginHttpRoutes.d.ts +1 -1
- package/bin/plugin/core/PluginHttpRoutes.d.ts.map +1 -1
- package/bin/plugin/core/PluginLocalExecution.d.ts +4 -1
- package/bin/plugin/core/PluginLocalExecution.d.ts.map +1 -1
- package/bin/plugin/core/PluginLocalExecution.js +0 -1
- package/bin/plugin/core/PluginLocalExecution.js.map +1 -1
- package/bin/plugin/core/PluginRegistry.d.ts +4 -2
- package/bin/plugin/core/PluginRegistry.d.ts.map +1 -1
- package/bin/plugin/core/PluginRegistry.js +7 -6
- package/bin/plugin/core/PluginRegistry.js.map +1 -1
- package/bin/plugin/core/PluginStateController.d.ts +7 -27
- package/bin/plugin/core/PluginStateController.d.ts.map +1 -1
- package/bin/plugin/core/PluginStateController.js +5 -39
- package/bin/plugin/core/PluginStateController.js.map +1 -1
- package/bin/plugin/types/PluginApi.d.ts +3 -79
- package/bin/plugin/types/PluginApi.d.ts.map +1 -1
- package/bin/rpc/Client.d.ts +11 -6
- package/bin/rpc/Client.d.ts.map +1 -1
- package/bin/rpc/Client.js +2 -0
- package/bin/rpc/Client.js.map +1 -1
- package/bin/session/Session.d.ts +2 -5
- package/bin/session/Session.d.ts.map +1 -1
- package/bin/session/Session.js +3 -16
- package/bin/session/Session.js.map +1 -1
- package/bin/session/SessionSystemBuilder.d.ts +1 -1
- package/bin/session/SessionSystemBuilder.d.ts.map +1 -1
- package/bin/session/browse/Browse.d.ts +4 -6
- package/bin/session/browse/Browse.d.ts.map +1 -1
- package/bin/session/browse/Browse.js +71 -71
- package/bin/session/browse/Browse.js.map +1 -1
- package/bin/session/preview/SessionMessagePreview.d.ts +13 -0
- package/bin/session/preview/SessionMessagePreview.d.ts.map +1 -0
- package/bin/session/preview/SessionMessagePreview.js +61 -0
- package/bin/session/preview/SessionMessagePreview.js.map +1 -0
- package/bin/session/services/SessionStateService.d.ts +1 -1
- package/bin/session/services/SessionStateService.d.ts.map +1 -1
- package/bin/session/services/SessionStateService.js +2 -2
- package/bin/session/services/SessionStateService.js.map +1 -1
- package/bin/session/services/SessionViewService.d.ts +2 -1
- package/bin/session/services/SessionViewService.d.ts.map +1 -1
- package/bin/session/services/SessionViewService.js +4 -1
- package/bin/session/services/SessionViewService.js.map +1 -1
- package/bin/session/storage/Metadata.d.ts.map +1 -1
- package/bin/session/storage/Metadata.js +23 -0
- package/bin/session/storage/Metadata.js.map +1 -1
- package/bin/session/storage/Persistence.d.ts +1 -1
- package/bin/session/storage/Persistence.d.ts.map +1 -1
- package/bin/session/storage/RuntimeSessionPort.d.ts +0 -12
- package/bin/session/storage/RuntimeSessionPort.d.ts.map +1 -1
- package/bin/session/storage/RuntimeSessionPort.js +0 -8
- package/bin/session/storage/RuntimeSessionPort.js.map +1 -1
- package/bin/types/plugin/PluginAction.d.ts +0 -10
- package/bin/types/plugin/PluginAction.d.ts.map +1 -1
- package/bin/types/plugin/PluginCommand.d.ts +2 -3
- package/bin/types/plugin/PluginCommand.d.ts.map +1 -1
- package/bin/types/plugin/PluginControl.d.ts +74 -0
- package/bin/types/plugin/PluginControl.d.ts.map +1 -0
- package/bin/types/plugin/PluginControl.js +9 -0
- package/bin/types/plugin/PluginControl.js.map +1 -0
- package/bin/types/plugin/PluginDefinition.d.ts +1 -1
- package/bin/types/plugin/PluginDefinition.d.ts.map +1 -1
- package/bin/types/plugin/PluginRuntime.d.ts +1 -3
- package/bin/types/plugin/PluginRuntime.d.ts.map +1 -1
- package/bin/types/plugin/PluginState.d.ts +3 -16
- package/bin/types/plugin/PluginState.d.ts.map +1 -1
- package/bin/types/rpc/RpcProtocol.d.ts +3 -3
- package/bin/types/rpc/RpcProtocol.d.ts.map +1 -1
- package/bin/types/runtime/agent/AgentContext.d.ts +1 -9
- package/bin/types/runtime/agent/AgentContext.d.ts.map +1 -1
- package/bin/types/runtime/agent/AgentContext.js.map +1 -1
- package/bin/types/session/SessionLocalState.d.ts +1 -1
- package/bin/types/session/SessionLocalState.d.ts.map +1 -1
- package/package.json +4 -7
- package/scripts/agent-ready.test.mjs +38 -0
- package/scripts/assistant-file-resource.test.mjs +39 -0
- package/scripts/city-model-tool-loop.test.mjs +18 -30
- package/scripts/image-plugin-job.test.mjs +13 -13
- package/scripts/plugin-tool-bridge.test.mjs +2 -3
- package/scripts/remote-session-reconnect.test.mjs +67 -0
- package/scripts/session-history-writer-error.test.mjs +57 -0
- package/scripts/session-list-title.test.mjs +114 -23
- package/scripts/session-system-blocks.test.mjs +73 -0
- package/src/agent/local/Agent.ts +4 -10
- package/src/agent/local/AgentInstructions.ts +11 -11
- package/src/agent/local/services/AgentAssemblyService.ts +3 -9
- package/src/agent/local/services/AgentSessions.ts +8 -15
- package/src/agent/remote/RemoteAgent.ts +5 -4
- package/src/agent/remote/RemoteSession.ts +21 -2
- package/src/agent/remote/RemoteTransport.ts +6 -2
- package/src/agent/remote/transports/HttpRemoteAgentTransport.ts +13 -3
- package/src/agent/remote/transports/RpcRemoteAgentTransport.ts +6 -2
- package/src/executor/Executor.ts +0 -23
- package/src/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.ts +2 -8
- package/src/executor/composer/history/SessionHistoryWriter.ts +33 -64
- package/src/executor/composer/system/default/StaticPromptCatalog.ts +4 -0
- package/src/executor/composer/system/default/SystemDomain.ts +1 -9
- package/src/executor/composer/system/default/SystemPromptAssets.ts +0 -6
- package/src/executor/composer/system/default/assets/core.prompt.ts +1 -1
- package/src/executor/composer/system/default/assets/core.prompt.ts.txt +37 -15
- package/src/executor/messages/AssistantFileResource.ts +61 -17
- package/src/executor/services/ExecutorInflightService.ts +0 -10
- package/src/executor/store/history/jsonl/JsonlSessionHistoryStore.ts +130 -4
- package/src/executor/types/SessionHistoryMeta.ts +6 -0
- package/src/index.ts +100 -29
- package/src/plugin/core/ActionScheduleExecutor.ts +1 -1
- package/src/plugin/core/BasePlugin.ts +8 -22
- package/src/plugin/core/HookRegistry.ts +1 -1
- package/src/plugin/core/PluginActionFactory.ts +1 -8
- package/src/plugin/core/PluginActionRunner.ts +8 -11
- package/src/plugin/core/PluginCatalog.ts +2 -5
- package/src/plugin/core/PluginCommand.ts +2 -1
- package/src/plugin/core/PluginHttpRoutes.ts +1 -1
- package/src/plugin/core/PluginLocalExecution.ts +4 -7
- package/src/plugin/core/PluginRegistry.ts +10 -10
- package/src/plugin/core/PluginStateController.ts +12 -60
- package/src/plugin/types/PluginApi.ts +6 -81
- package/src/rpc/Client.ts +22 -16
- package/src/session/Session.ts +5 -21
- package/src/session/SessionSystemBuilder.ts +1 -1
- package/src/session/browse/Browse.ts +103 -84
- package/src/session/preview/SessionMessagePreview.ts +75 -0
- package/src/session/services/SessionStateService.ts +3 -4
- package/src/session/services/SessionViewService.ts +6 -6
- package/src/session/storage/Metadata.ts +26 -0
- package/src/session/storage/Persistence.ts +1 -1
- package/src/session/storage/RuntimeSessionPort.ts +0 -20
- package/src/types/plugin/PluginAction.ts +0 -10
- package/src/types/plugin/PluginCommand.ts +2 -3
- package/src/types/plugin/PluginControl.ts +80 -0
- package/src/types/plugin/PluginDefinition.ts +1 -1
- package/src/types/plugin/PluginRuntime.ts +1 -3
- package/src/types/plugin/PluginState.ts +3 -20
- package/src/types/rpc/RpcProtocol.ts +4 -4
- package/src/types/runtime/agent/AgentContext.ts +1 -9
- package/src/types/session/SessionLocalState.ts +1 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/bin/config/DowncitySchema.d.ts +0 -20
- package/bin/config/DowncitySchema.d.ts.map +0 -1
- package/bin/config/DowncitySchema.js +0 -160
- package/bin/config/DowncitySchema.js.map +0 -1
- package/bin/executor/composer/system/default/assets/plugin.prompt.d.ts +0 -7
- package/bin/executor/composer/system/default/assets/plugin.prompt.d.ts.map +0 -1
- package/bin/executor/composer/system/default/assets/plugin.prompt.js +0 -8
- package/bin/executor/composer/system/default/assets/plugin.prompt.js.map +0 -1
- package/bin/plugin/core/Activation.d.ts +0 -18
- package/bin/plugin/core/Activation.d.ts.map +0 -1
- package/bin/plugin/core/Activation.js +0 -21
- package/bin/plugin/core/Activation.js.map +0 -1
- package/bin/plugin/core/Manager.d.ts +0 -7
- package/bin/plugin/core/Manager.d.ts.map +0 -1
- package/bin/plugin/core/Manager.js +0 -6
- package/bin/plugin/core/Manager.js.map +0 -1
- package/bin/plugin/types/Plugin.d.ts +0 -15
- package/bin/plugin/types/Plugin.d.ts.map +0 -1
- package/bin/plugin/types/Plugin.js +0 -9
- package/bin/plugin/types/Plugin.js.map +0 -1
- package/bin/plugin/types/PluginLifecycle.d.ts +0 -34
- package/bin/plugin/types/PluginLifecycle.d.ts.map +0 -1
- package/bin/plugin/types/PluginLifecycle.js +0 -9
- package/bin/plugin/types/PluginLifecycle.js.map +0 -1
- package/bin/plugin/types/PluginState.d.ts +0 -5
- package/bin/plugin/types/PluginState.d.ts.map +0 -1
- package/bin/plugin/types/PluginState.js +0 -5
- package/bin/plugin/types/PluginState.js.map +0 -1
- package/bin/plugin/types/Plugins.d.ts +0 -61
- package/bin/plugin/types/Plugins.d.ts.map +0 -1
- package/bin/plugin/types/Plugins.js +0 -5
- package/bin/plugin/types/Plugins.js.map +0 -1
- package/bin/session/index.d.ts +0 -15
- package/bin/session/index.d.ts.map +0 -1
- package/bin/session/index.js +0 -15
- package/bin/session/index.js.map +0 -1
- package/bin/types/agent/AgentTypes.d.ts +0 -18
- package/bin/types/agent/AgentTypes.d.ts.map +0 -1
- package/bin/types/agent/AgentTypes.js +0 -10
- package/bin/types/agent/AgentTypes.js.map +0 -1
- package/bin/utils/logger/Format.d.ts +0 -11
- package/bin/utils/logger/Format.d.ts.map +0 -1
- package/bin/utils/logger/Format.js +0 -10
- package/bin/utils/logger/Format.js.map +0 -1
- package/src/config/DowncitySchema.ts +0 -175
- package/src/executor/composer/system/default/assets/plugin.prompt.ts +0 -9
- package/src/executor/composer/system/default/assets/plugin.prompt.ts.txt +0 -20
- package/src/plugin/core/Activation.ts +0 -26
- package/src/plugin/core/Manager.ts +0 -17
- package/src/plugin/types/Plugin.ts +0 -63
- package/src/plugin/types/PluginLifecycle.ts +0 -35
- package/src/plugin/types/PluginState.ts +0 -12
- package/src/plugin/types/Plugins.ts +0 -67
- package/src/session/index.ts +0 -57
- package/src/types/agent/AgentTypes.ts +0 -58
- package/src/utils/logger/Format.ts +0 -15
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
// Source: src/executor/composer/system/default/assets/core.prompt.ts.txt
|
|
7
|
-
const TEXT_MODULE_CONTENT = "
|
|
7
|
+
const TEXT_MODULE_CONTENT = "# Downcity Agent\n\nYou are a Downcity Agent running inside the Downcity Agent runtime.\n\nYour job is to help the user inspect, edit, run, debug, and operate the current project through the runtime tools and enabled plugins.\n\nYou have permission to use and modify only the project bound to the current session unless the user explicitly asks for a broader action and the runtime grants the required capability.\n\n# Harness Design\n\nDowncity Agent is a harness around an existing human-owned workspace, not a replacement platform or a detached autonomous worker.\n\n- The harness lets the Agent enter the user's existing project instead of forcing the user to migrate work into a new AI platform.\n- The project structure is the control surface. Files, directories, scripts, docs, configuration, and existing workflows define the practical business boundary.\n- The Agent inherits the workspace's knowledge topology. Read and follow the existing structure before creating new abstractions, stores, or process layers.\n- The harness keeps the human in control. The user owns intent, boundaries, approvals, and final judgment; the Agent supplies high-density execution inside those boundaries.\n- Agent state should remain observable and handoff-friendly. Prefer outputs, changes, and explanations that a human can inspect, resume, or correct through normal project artifacts.\n- Autonomy should be progressive. Execute low-risk, reversible work directly; pause for clarification or approval when the boundary, consequence, or owner is unclear.\n- Tools and plugins are harness surfaces. Use them to act precisely in the current execution context without treating any tool as a reason to ignore the user's native workflow.\n- Prefer project-native, maintainable changes over clever platform-shaped abstractions. The best result should feel like it naturally belongs in the existing workspace.\n\n# Shell Commands\n\nCommand execution tools:\n- Prefer `shell_exec` for short, one-off commands.\n- Use `shell_session` for long-running tasks, tasks that need status checks, or processes that need stdin interaction.\n- Start a long-running command with `shell_session({ action: \"start\", cmd })` and keep the returned `shell_id`.\n- `shell_id` identifies a shell session; it is not a chat `session_id`.\n- During long-running tasks, read new output with `shell_session({ action: \"read\", shell_id })`. Do not implement your own high-frequency empty polling loop.\n- When a process needs stdin, use `shell_session({ action: \"send\", shell_id, input })`.\n- When sending stdin to an unrestricted shell session, every `send` call must include a clear `reason` and wait for user confirmation. Approval for `start` only authorizes starting the process; it does not authorize later stdin writes.\n- When a command session is complete and no longer needed, release it with `shell_session({ action: \"stop\", shell_id })`.\n- Do not forward very long raw shell output directly to the user. Summarize it first.\n- Shell commands run in the Safe Sandbox by default: the project directory is readable/writable, network is available, HOME points to `.downcity/sandbox/`, and the real user HOME plus system directories are not writable.\n- When installing Python dependencies, prefer a project-local `.venv`; do not use `pip install --user`.\n- If a task requires global installation, host-directory writes, or host-level capabilities, you may request `sandbox: \"unrestricted\"`. You must provide a clear `reason` and wait for user confirmation before executing.\n- Do not attempt `sudo`, Xcode Command Line Tools installation, SSH/keychain/shell profile modification, or obviously destructive commands. These requests are rejected even with unrestricted sandbox access.\n- If the user rejects an unrestricted sandbox request, do not keep asking for the same command. Explain the impact and provide a project-local alternative when possible.\n- Downloads for models, tool caches, and temporary state should naturally live under `.downcity/sandbox/` or the project directory. Do not assume access to the real user's cache.\n\n# Plugin System\n\nYou are working in a plugin-based execution environment.\n\nPlugin call rules:\n- When you need plugin capabilities, prefer invoking the plugin action through the available tools.\n- If `plugin_read` is available and you are unsure about a plugin action, parameter schema, or example, first call `plugin_read({ plugin, action? })` to read metadata.\n- If `plugin_call` is available, call the action with `plugin_call({ plugin, action, payload })`.\n- `plugin_call.plugin` is the plugin name, for example `skill`, `task`, `memory`, or `contact`.\n- `plugin_call.action` is the action name, for example `list`, `lookup`, `create`, or `run`.\n- `plugin_call.payload` is a structured JSON payload. Pass `{}` when there are no parameters. If the action metadata declares an input schema, the payload must conform to that schema.\n- `ActionSchedule` is an internal Agent capability for delayed plugin actions. It is not a standalone plugin.\n\nSpecific plugin capabilities are defined by the action metadata returned by `plugin_read` and by each plugin's system prompt.\n\n# Default Decisions And Clarification\n\n- Execute first and communicate second by default: for low-risk, reversible requests where the user's intent is clear enough, use the current date, timezone, chat context, and common defaults to proceed. Do not repeatedly ask about obvious event titles, default platforms, or clear time expressions.\n- Ask a clarification only when missing information would materially change the outcome, such as the date, target object, amount, account, recipient, or when an action is irreversible, high-risk, or privacy-sensitive.\n- When handling relative time expressions, prefer the runtime-provided `current_date`, `current_time`, and `timezone`. If inbound `<info>` explicitly provides `user_timezone`, use that first; otherwise use the runtime clock timezone. For expressions such as \"today\", \"tomorrow\", \"2 PM\", or \"two hours earlier\", resolve them to absolute times before acting, and state the absolute date and time in your reply.\n- When a task depends on external permissions, system capabilities, or third-party connections such as calendars, reminders, chat channels, or system authorization, probe availability first before promising to create, send, or write anything.\n- If probing shows a blocker caused by system permissions, host environment, or connection state, state the real blocker and next step directly. Do not first promise that you can do it and then spend multiple turns asking follow-up questions.\n- If you have enough information to complete several low-risk default actions in one pass, complete them and briefly state which assumptions you used.\n\n# Very Important\n\nSafety and boundaries:\n- Do not run destructive commands such as `rm -rf` or `git reset --hard` unless the user explicitly asks for them.\n- For API keys, tokens, secrets, environment variables, bot credentials, and similar sensitive configuration, prefer guiding the user to maintain them in Console surfaces such as `Global / Env` or `Global / Channel Accounts`. Do not ask the user to paste secret values directly into the current chat.\n- Secret-listing capabilities can return only configured key names and descriptions. They never return secret values. Do not ask the user to \"send the secret to you\" or keep trying to retrieve plaintext secrets.\n";
|
|
8
8
|
|
|
9
9
|
export default TEXT_MODULE_CONTENT;
|
|
@@ -1,18 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
1
|
+
# Downcity Agent
|
|
2
|
+
|
|
3
|
+
You are a Downcity Agent running inside the Downcity Agent runtime.
|
|
4
|
+
|
|
5
|
+
Your job is to help the user inspect, edit, run, debug, and operate the current project through the runtime tools and enabled plugins.
|
|
6
|
+
|
|
7
|
+
You have permission to use and modify only the project bound to the current session unless the user explicitly asks for a broader action and the runtime grants the required capability.
|
|
8
|
+
|
|
9
|
+
# Harness Design
|
|
10
|
+
|
|
11
|
+
Downcity Agent is a harness around an existing human-owned workspace, not a replacement platform or a detached autonomous worker.
|
|
12
|
+
|
|
13
|
+
- The harness lets the Agent enter the user's existing project instead of forcing the user to migrate work into a new AI platform.
|
|
14
|
+
- The project structure is the control surface. Files, directories, scripts, docs, configuration, and existing workflows define the practical business boundary.
|
|
15
|
+
- The Agent inherits the workspace's knowledge topology. Read and follow the existing structure before creating new abstractions, stores, or process layers.
|
|
16
|
+
- The harness keeps the human in control. The user owns intent, boundaries, approvals, and final judgment; the Agent supplies high-density execution inside those boundaries.
|
|
17
|
+
- Agent state should remain observable and handoff-friendly. Prefer outputs, changes, and explanations that a human can inspect, resume, or correct through normal project artifacts.
|
|
18
|
+
- Autonomy should be progressive. Execute low-risk, reversible work directly; pause for clarification or approval when the boundary, consequence, or owner is unclear.
|
|
19
|
+
- Tools and plugins are harness surfaces. Use them to act precisely in the current execution context without treating any tool as a reason to ignore the user's native workflow.
|
|
20
|
+
- Prefer project-native, maintainable changes over clever platform-shaped abstractions. The best result should feel like it naturally belongs in the existing workspace.
|
|
21
|
+
|
|
22
|
+
# Shell Commands
|
|
16
23
|
|
|
17
24
|
Command execution tools:
|
|
18
25
|
- Prefer `shell_exec` for short, one-off commands.
|
|
@@ -31,6 +38,21 @@ Command execution tools:
|
|
|
31
38
|
- If the user rejects an unrestricted sandbox request, do not keep asking for the same command. Explain the impact and provide a project-local alternative when possible.
|
|
32
39
|
- Downloads for models, tool caches, and temporary state should naturally live under `.downcity/sandbox/` or the project directory. Do not assume access to the real user's cache.
|
|
33
40
|
|
|
41
|
+
# Plugin System
|
|
42
|
+
|
|
43
|
+
You are working in a plugin-based execution environment.
|
|
44
|
+
|
|
45
|
+
Plugin call rules:
|
|
46
|
+
- When you need plugin capabilities, prefer invoking the plugin action through the available tools.
|
|
47
|
+
- If `plugin_read` is available and you are unsure about a plugin action, parameter schema, or example, first call `plugin_read({ plugin, action? })` to read metadata.
|
|
48
|
+
- If `plugin_call` is available, call the action with `plugin_call({ plugin, action, payload })`.
|
|
49
|
+
- `plugin_call.plugin` is the plugin name, for example `skill`, `task`, `memory`, or `contact`.
|
|
50
|
+
- `plugin_call.action` is the action name, for example `list`, `lookup`, `create`, or `run`.
|
|
51
|
+
- `plugin_call.payload` is a structured JSON payload. Pass `{}` when there are no parameters. If the action metadata declares an input schema, the payload must conform to that schema.
|
|
52
|
+
- `ActionSchedule` is an internal Agent capability for delayed plugin actions. It is not a standalone plugin.
|
|
53
|
+
|
|
54
|
+
Specific plugin capabilities are defined by the action metadata returned by `plugin_read` and by each plugin's system prompt.
|
|
55
|
+
|
|
34
56
|
# Default Decisions And Clarification
|
|
35
57
|
|
|
36
58
|
- Execute first and communicate second by default: for low-risk, reversible requests where the user's intent is clear enough, use the current date, timezone, chat context, and common defaults to proceed. Do not repeatedly ask about obvious event titles, default platforms, or clear time expressions.
|
|
@@ -15,6 +15,11 @@ import fs from "fs-extra";
|
|
|
15
15
|
import type { FileUIPart } from "ai";
|
|
16
16
|
import { getDowncityResourcesDirPath } from "@/config/Paths.js";
|
|
17
17
|
|
|
18
|
+
/** 单个 assistant 资源下载允许占用的最长时间。 */
|
|
19
|
+
const ASSISTANT_RESOURCE_TIMEOUT_MS = 30_000;
|
|
20
|
+
/** 单个 assistant 资源允许写入内存与磁盘的最大字节数。 */
|
|
21
|
+
const MAX_ASSISTANT_RESOURCE_BYTES = 25 * 1024 * 1024;
|
|
22
|
+
|
|
18
23
|
type ParsedDataUrl = {
|
|
19
24
|
/**
|
|
20
25
|
* data URL 声明的媒体类型。
|
|
@@ -186,22 +191,49 @@ async function read_remote_resource(raw_url: string): Promise<{
|
|
|
186
191
|
filename?: string;
|
|
187
192
|
bytes: Buffer;
|
|
188
193
|
}> {
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
194
|
+
const abort_controller = new AbortController();
|
|
195
|
+
const timeout = setTimeout(() => {
|
|
196
|
+
abort_controller.abort(new Error("Assistant resource download timed out"));
|
|
197
|
+
}, ASSISTANT_RESOURCE_TIMEOUT_MS);
|
|
198
|
+
try {
|
|
199
|
+
const result = await fetch_with_retry(raw_url, abort_controller.signal);
|
|
200
|
+
if (!result.ok) {
|
|
201
|
+
throw new Error(
|
|
202
|
+
`Failed to download assistant file resource: ${result.status} :: url=${raw_url}`,
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
const declared_size = Number(result.headers.get("content-length"));
|
|
206
|
+
if (Number.isFinite(declared_size) && declared_size > MAX_ASSISTANT_RESOURCE_BYTES) {
|
|
207
|
+
throw new Error(`Assistant file resource exceeds 25 MiB :: url=${raw_url}`);
|
|
208
|
+
}
|
|
209
|
+
if (!result.body) {
|
|
210
|
+
throw new Error(`Assistant file resource has no response body :: url=${raw_url}`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const reader = result.body.getReader();
|
|
214
|
+
const chunks: Buffer[] = [];
|
|
215
|
+
let total_bytes = 0;
|
|
216
|
+
while (true) {
|
|
217
|
+
const { done, value } = await reader.read();
|
|
218
|
+
if (done) break;
|
|
219
|
+
total_bytes += value.byteLength;
|
|
220
|
+
if (total_bytes > MAX_ASSISTANT_RESOURCE_BYTES) {
|
|
221
|
+
await reader.cancel();
|
|
222
|
+
throw new Error(`Assistant file resource exceeds 25 MiB :: url=${raw_url}`);
|
|
223
|
+
}
|
|
224
|
+
chunks.push(Buffer.from(value));
|
|
225
|
+
}
|
|
226
|
+
if (total_bytes === 0) {
|
|
227
|
+
throw new Error(`Downloaded assistant file resource is empty :: url=${raw_url}`);
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
mediaType: result.headers.get("content-type")?.split(";")[0]?.trim(),
|
|
231
|
+
filename: filename_from_url(raw_url),
|
|
232
|
+
bytes: Buffer.concat(chunks, total_bytes),
|
|
233
|
+
};
|
|
234
|
+
} finally {
|
|
235
|
+
clearTimeout(timeout);
|
|
199
236
|
}
|
|
200
|
-
return {
|
|
201
|
-
mediaType: result.headers.get("content-type")?.split(";")[0]?.trim(),
|
|
202
|
-
filename: filename_from_url(raw_url),
|
|
203
|
-
bytes,
|
|
204
|
-
};
|
|
205
237
|
}
|
|
206
238
|
|
|
207
239
|
/**
|
|
@@ -213,13 +245,14 @@ async function read_remote_resource(raw_url: string): Promise<{
|
|
|
213
245
|
*/
|
|
214
246
|
async function fetch_with_retry(
|
|
215
247
|
raw_url: string,
|
|
248
|
+
signal: AbortSignal,
|
|
216
249
|
): Promise<Response> {
|
|
217
250
|
const delays_ms = [250, 1_000];
|
|
218
251
|
const attempts = delays_ms.length + 1;
|
|
219
252
|
let last_error: unknown;
|
|
220
253
|
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
221
254
|
try {
|
|
222
|
-
return await fetch(raw_url);
|
|
255
|
+
return await fetch(raw_url, { signal });
|
|
223
256
|
} catch (error) {
|
|
224
257
|
last_error = error;
|
|
225
258
|
if (!is_transient_fetch_error(error) || attempt === attempts - 1) {
|
|
@@ -286,7 +319,14 @@ async function read_local_resource(
|
|
|
286
319
|
: path.isAbsolute(raw)
|
|
287
320
|
? raw
|
|
288
321
|
: path.resolve(projectRoot, raw);
|
|
322
|
+
const file_stat = await fs.stat(file_path);
|
|
323
|
+
if (file_stat.size > MAX_ASSISTANT_RESOURCE_BYTES) {
|
|
324
|
+
throw new Error(`Assistant file resource exceeds 25 MiB: ${file_path}`);
|
|
325
|
+
}
|
|
289
326
|
const bytes = await fs.readFile(file_path);
|
|
327
|
+
if (bytes.length > MAX_ASSISTANT_RESOURCE_BYTES) {
|
|
328
|
+
throw new Error(`Assistant file resource exceeds 25 MiB: ${file_path}`);
|
|
329
|
+
}
|
|
290
330
|
if (bytes.length === 0) {
|
|
291
331
|
throw new Error(`Assistant file resource is empty: ${file_path}`);
|
|
292
332
|
}
|
|
@@ -306,6 +346,9 @@ async function materialize_file_part(params: {
|
|
|
306
346
|
}
|
|
307
347
|
const parsed_data_url = parse_data_url(raw_url);
|
|
308
348
|
if (parsed_data_url) {
|
|
349
|
+
if (parsed_data_url.bytes.length > MAX_ASSISTANT_RESOURCE_BYTES) {
|
|
350
|
+
throw new Error("Assistant data URL resource exceeds 25 MiB");
|
|
351
|
+
}
|
|
309
352
|
const media_type =
|
|
310
353
|
String(params.part.mediaType || parsed_data_url.media_type || "").trim() ||
|
|
311
354
|
"application/octet-stream";
|
|
@@ -352,8 +395,9 @@ async function materialize_file_part(params: {
|
|
|
352
395
|
} catch (error) {
|
|
353
396
|
// 关键点(中文):远程下载失败时保留原始 URL,不让整张图导致 action 失败。
|
|
354
397
|
// 这样图片至少以链接形式存在,用户可手动点击或后续再试。
|
|
398
|
+
const error_message = error instanceof Error ? error.message : String(error);
|
|
355
399
|
console.warn(
|
|
356
|
-
`[AssistantFileResource] remote download failed, keeping original url :: ${
|
|
400
|
+
`[AssistantFileResource] remote download failed, keeping original url :: ${error_message}`,
|
|
357
401
|
);
|
|
358
402
|
return params.part;
|
|
359
403
|
}
|
|
@@ -23,10 +23,6 @@ interface ExecutorInflightServiceOptions {
|
|
|
23
23
|
*/
|
|
24
24
|
history_store: SessionHistoryStore;
|
|
25
25
|
|
|
26
|
-
/**
|
|
27
|
-
* inflight 更新完成后的异步通知。
|
|
28
|
-
*/
|
|
29
|
-
run_after_session_updated_async?: () => Promise<void>;
|
|
30
26
|
}
|
|
31
27
|
|
|
32
28
|
/**
|
|
@@ -35,13 +31,10 @@ interface ExecutorInflightServiceOptions {
|
|
|
35
31
|
export class ExecutorInflightService {
|
|
36
32
|
private readonly session_id: string;
|
|
37
33
|
private readonly history_store: SessionHistoryStore;
|
|
38
|
-
private readonly run_after_session_updated_async?: ExecutorInflightServiceOptions["run_after_session_updated_async"];
|
|
39
34
|
|
|
40
35
|
constructor(options: ExecutorInflightServiceOptions) {
|
|
41
36
|
this.session_id = String(options.session_id || "").trim();
|
|
42
37
|
this.history_store = options.history_store;
|
|
43
|
-
this.run_after_session_updated_async =
|
|
44
|
-
options.run_after_session_updated_async;
|
|
45
38
|
|
|
46
39
|
if (!this.session_id) {
|
|
47
40
|
throw new Error("ExecutorInflightService requires a non-empty session_id");
|
|
@@ -98,8 +91,5 @@ export class ExecutorInflightService {
|
|
|
98
91
|
};
|
|
99
92
|
|
|
100
93
|
await this.history_store.write_inflight(next_message);
|
|
101
|
-
if (this.run_after_session_updated_async) {
|
|
102
|
-
await this.run_after_session_updated_async();
|
|
103
|
-
}
|
|
104
94
|
}
|
|
105
95
|
}
|
|
@@ -34,6 +34,7 @@ import type {
|
|
|
34
34
|
SessionHistoryCompactInput,
|
|
35
35
|
SessionHistoryStore,
|
|
36
36
|
} from "@/executor/store/history/SessionHistoryStore.js";
|
|
37
|
+
import { resolve_session_message_preview } from "@/session/preview/SessionMessagePreview.js";
|
|
37
38
|
|
|
38
39
|
/**
|
|
39
40
|
* JSONL history store 构造参数。
|
|
@@ -359,6 +360,19 @@ export class JsonlSessionHistoryStore implements SessionHistoryStore {
|
|
|
359
360
|
...(this.normalizeText(raw.modelLabel)
|
|
360
361
|
? { modelLabel: this.normalizeText(raw.modelLabel) }
|
|
361
362
|
: {}),
|
|
363
|
+
...(typeof raw.messageCount === "number" &&
|
|
364
|
+
Number.isInteger(raw.messageCount) &&
|
|
365
|
+
raw.messageCount >= 0
|
|
366
|
+
? { messageCount: raw.messageCount }
|
|
367
|
+
: {}),
|
|
368
|
+
...(this.normalizeText(raw.previewText)
|
|
369
|
+
? { previewText: this.normalizeText(raw.previewText) }
|
|
370
|
+
: {}),
|
|
371
|
+
...(typeof raw.historyBytes === "number" &&
|
|
372
|
+
Number.isInteger(raw.historyBytes) &&
|
|
373
|
+
raw.historyBytes >= 0
|
|
374
|
+
? { historyBytes: raw.historyBytes }
|
|
375
|
+
: {}),
|
|
362
376
|
};
|
|
363
377
|
} catch {
|
|
364
378
|
return {
|
|
@@ -391,6 +405,19 @@ export class JsonlSessionHistoryStore implements SessionHistoryStore {
|
|
|
391
405
|
...(this.normalizeText(next.modelLabel)
|
|
392
406
|
? { modelLabel: this.normalizeText(next.modelLabel) }
|
|
393
407
|
: {}),
|
|
408
|
+
...(typeof next.messageCount === "number" &&
|
|
409
|
+
Number.isInteger(next.messageCount) &&
|
|
410
|
+
next.messageCount >= 0
|
|
411
|
+
? { messageCount: next.messageCount }
|
|
412
|
+
: {}),
|
|
413
|
+
...(this.normalizeText(next.previewText)
|
|
414
|
+
? { previewText: this.normalizeText(next.previewText) }
|
|
415
|
+
: {}),
|
|
416
|
+
...(typeof next.historyBytes === "number" &&
|
|
417
|
+
Number.isInteger(next.historyBytes) &&
|
|
418
|
+
next.historyBytes >= 0
|
|
419
|
+
? { historyBytes: next.historyBytes }
|
|
420
|
+
: {}),
|
|
394
421
|
};
|
|
395
422
|
await fs.writeJson(this.getMetaFilePath(), normalized, { spaces: 2 });
|
|
396
423
|
}
|
|
@@ -432,6 +459,82 @@ export class JsonlSessionHistoryStore implements SessionHistoryStore {
|
|
|
432
459
|
);
|
|
433
460
|
}
|
|
434
461
|
|
|
462
|
+
private async get_history_bytes_unsafe(): Promise<number> {
|
|
463
|
+
try {
|
|
464
|
+
const file_stat = await statNative(this.getMessagesFilePath());
|
|
465
|
+
return file_stat.size;
|
|
466
|
+
} catch {
|
|
467
|
+
return 0;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
private async read_persisted_records_unsafe(): Promise<SessionRecordV1[]> {
|
|
472
|
+
const raw = await fs.readFile(this.getMessagesFilePath(), "utf8").catch(() => "");
|
|
473
|
+
const records: SessionRecordV1[] = [];
|
|
474
|
+
for (const line of raw.split("\n").filter(Boolean)) {
|
|
475
|
+
try {
|
|
476
|
+
const record = this.normalizePersistedMessage(JSON.parse(line));
|
|
477
|
+
if (record) records.push(record);
|
|
478
|
+
} catch {
|
|
479
|
+
// 关键点(中文):摘要重建跳过损坏行,与正式历史读取语义保持一致。
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return records;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
private async write_history_summary_unsafe(
|
|
486
|
+
records: SessionRecordV1[],
|
|
487
|
+
): Promise<void> {
|
|
488
|
+
const current = await this.readMetaUnsafe();
|
|
489
|
+
const last_record = records[records.length - 1];
|
|
490
|
+
const preview_text = last_record
|
|
491
|
+
? resolve_session_message_preview(last_record).slice(0, 180).trim()
|
|
492
|
+
: "";
|
|
493
|
+
const { previewText: _previous_preview, ...metadata_without_preview } = current;
|
|
494
|
+
void _previous_preview;
|
|
495
|
+
await this.writeMetaUnsafe({
|
|
496
|
+
...metadata_without_preview,
|
|
497
|
+
updatedAt: Date.now(),
|
|
498
|
+
messageCount: records.length,
|
|
499
|
+
historyBytes: await this.get_history_bytes_unsafe(),
|
|
500
|
+
...(preview_text ? { previewText: preview_text } : {}),
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
private async update_history_summary_after_append_unsafe(input: {
|
|
505
|
+
/** 本次追加前 messages.jsonl 的字节长度。 */
|
|
506
|
+
previous_history_bytes: number;
|
|
507
|
+
/** 本次实际追加的 records,按写入顺序排列。 */
|
|
508
|
+
appended_records: SessionRecordV1[];
|
|
509
|
+
}): Promise<void> {
|
|
510
|
+
if (input.appended_records.length === 0) return;
|
|
511
|
+
const current = await this.readMetaUnsafe();
|
|
512
|
+
const previous_message_count = current.messageCount;
|
|
513
|
+
if (
|
|
514
|
+
current.historyBytes !== input.previous_history_bytes ||
|
|
515
|
+
typeof previous_message_count !== "number"
|
|
516
|
+
) {
|
|
517
|
+
await this.write_history_summary_unsafe(
|
|
518
|
+
await this.read_persisted_records_unsafe(),
|
|
519
|
+
);
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
const last_record = input.appended_records[input.appended_records.length - 1];
|
|
524
|
+
const preview_text = resolve_session_message_preview(last_record)
|
|
525
|
+
.slice(0, 180)
|
|
526
|
+
.trim();
|
|
527
|
+
const { previewText: _previous_preview, ...metadata_without_preview } = current;
|
|
528
|
+
void _previous_preview;
|
|
529
|
+
await this.writeMetaUnsafe({
|
|
530
|
+
...metadata_without_preview,
|
|
531
|
+
updatedAt: Date.now(),
|
|
532
|
+
messageCount: previous_message_count + input.appended_records.length,
|
|
533
|
+
historyBytes: await this.get_history_bytes_unsafe(),
|
|
534
|
+
...(preview_text ? { previewText: preview_text } : {}),
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
|
|
435
538
|
private async rewriteMessagesUnsafe(messages: SessionRecordV1[]): Promise<void> {
|
|
436
539
|
const file = this.getMessagesFilePath();
|
|
437
540
|
const temp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
@@ -445,7 +548,7 @@ export class JsonlSessionHistoryStore implements SessionHistoryStore {
|
|
|
445
548
|
|
|
446
549
|
private async upsert_action_record_unsafe(
|
|
447
550
|
message: SessionActionRecordV1,
|
|
448
|
-
): Promise<
|
|
551
|
+
): Promise<SessionRecordV1[]> {
|
|
449
552
|
const file = this.getMessagesFilePath();
|
|
450
553
|
const raw = await fs.readFile(file, "utf8").catch(() => "");
|
|
451
554
|
const messages: SessionRecordV1[] = [];
|
|
@@ -468,6 +571,7 @@ export class JsonlSessionHistoryStore implements SessionHistoryStore {
|
|
|
468
571
|
|
|
469
572
|
if (!replaced) messages.push(message);
|
|
470
573
|
await this.rewriteMessagesUnsafe(messages);
|
|
574
|
+
return messages;
|
|
471
575
|
}
|
|
472
576
|
|
|
473
577
|
private async withWriteLock<T>(fn: () => Promise<T>): Promise<T> {
|
|
@@ -555,8 +659,8 @@ export class JsonlSessionHistoryStore implements SessionHistoryStore {
|
|
|
555
659
|
}),
|
|
556
660
|
getArchiveDirPath: () => this.getArchiveDirPath(),
|
|
557
661
|
getMessagesFilePath: () => this.getMessagesFilePath(),
|
|
558
|
-
|
|
559
|
-
|
|
662
|
+
writeHistorySummaryUnsafe: (records) =>
|
|
663
|
+
this.write_history_summary_unsafe(records),
|
|
560
664
|
},
|
|
561
665
|
{
|
|
562
666
|
model: input.model,
|
|
@@ -575,21 +679,31 @@ export class JsonlSessionHistoryStore implements SessionHistoryStore {
|
|
|
575
679
|
const normalized = this.normalizePersistedMessage(message);
|
|
576
680
|
if (!normalized) return;
|
|
577
681
|
if (is_session_action_record(normalized)) {
|
|
578
|
-
await this.upsert_action_record_unsafe(normalized);
|
|
682
|
+
const records = await this.upsert_action_record_unsafe(normalized);
|
|
683
|
+
await this.write_history_summary_unsafe(records);
|
|
579
684
|
return;
|
|
580
685
|
}
|
|
581
686
|
|
|
687
|
+
const previous_history_bytes = await this.get_history_bytes_unsafe();
|
|
688
|
+
const appended_records: SessionRecordV1[] = [];
|
|
689
|
+
|
|
582
690
|
// 关键点(中文):若上一次 assistant 在运行中中断,新的 user 到来前先把残留快照收口到正式历史,
|
|
583
691
|
// 避免 `list_records()` 时旧 inflight 跑到新 user 后面,打乱时序。
|
|
584
692
|
if (is_session_message_record(normalized) && normalized.role === "user") {
|
|
585
693
|
const current_inflight = await this.read_inflight_unsafe();
|
|
586
694
|
if (current_inflight) {
|
|
587
695
|
await this.appendMessageUnsafe(current_inflight);
|
|
696
|
+
appended_records.push(current_inflight);
|
|
588
697
|
await this.removeInflightUnsafe();
|
|
589
698
|
}
|
|
590
699
|
}
|
|
591
700
|
|
|
592
701
|
await this.appendMessageUnsafe(normalized);
|
|
702
|
+
appended_records.push(normalized);
|
|
703
|
+
await this.update_history_summary_after_append_unsafe({
|
|
704
|
+
previous_history_bytes,
|
|
705
|
+
appended_records,
|
|
706
|
+
});
|
|
593
707
|
});
|
|
594
708
|
}
|
|
595
709
|
|
|
@@ -611,7 +725,12 @@ export class JsonlSessionHistoryStore implements SessionHistoryStore {
|
|
|
611
725
|
.map((message) => `${JSON.stringify(message)}\n`)
|
|
612
726
|
.join("");
|
|
613
727
|
await this.withWriteLock(async () => {
|
|
728
|
+
const previous_history_bytes = await this.get_history_bytes_unsafe();
|
|
614
729
|
await fs.appendFile(this.getMessagesFilePath(), payload, "utf8");
|
|
730
|
+
await this.update_history_summary_after_append_unsafe({
|
|
731
|
+
previous_history_bytes,
|
|
732
|
+
appended_records: normalized_list,
|
|
733
|
+
});
|
|
615
734
|
});
|
|
616
735
|
}
|
|
617
736
|
|
|
@@ -628,6 +747,7 @@ export class JsonlSessionHistoryStore implements SessionHistoryStore {
|
|
|
628
747
|
|
|
629
748
|
async finalize_inflight(message?: SessionRecordV1 | null): Promise<void> {
|
|
630
749
|
await this.withWriteLock(async () => {
|
|
750
|
+
const previous_history_bytes = await this.get_history_bytes_unsafe();
|
|
631
751
|
const current_inflight = await this.read_inflight_unsafe();
|
|
632
752
|
const normalized_message = this.normalizePersistedMessage(message);
|
|
633
753
|
const final_message =
|
|
@@ -640,6 +760,12 @@ export class JsonlSessionHistoryStore implements SessionHistoryStore {
|
|
|
640
760
|
if (current_inflight) {
|
|
641
761
|
await this.removeInflightUnsafe();
|
|
642
762
|
}
|
|
763
|
+
if (final_message) {
|
|
764
|
+
await this.update_history_summary_after_append_unsafe({
|
|
765
|
+
previous_history_bytes,
|
|
766
|
+
appended_records: [final_message],
|
|
767
|
+
});
|
|
768
|
+
}
|
|
643
769
|
});
|
|
644
770
|
}
|
|
645
771
|
|
|
@@ -23,4 +23,10 @@ export type SessionHistoryMetaV1 = {
|
|
|
23
23
|
title?: string;
|
|
24
24
|
/** 当前 session 绑定模型的可读标签。 */
|
|
25
25
|
modelLabel?: string;
|
|
26
|
+
/** 当前 session 已持久化记录数量,用于列表查询避免扫描完整历史。 */
|
|
27
|
+
messageCount?: number;
|
|
28
|
+
/** 当前 session 最后一条记录的用户可见摘要,用于列表预览。 */
|
|
29
|
+
previewText?: string;
|
|
30
|
+
/** 生成当前摘要时 messages.jsonl 的字节长度,用于校验摘要是否仍然有效。 */
|
|
31
|
+
historyBytes?: number;
|
|
26
32
|
};
|