deepstrike 0.2.32__tar.gz → 0.2.34__tar.gz
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.
- {deepstrike-0.2.32 → deepstrike-0.2.34}/Cargo.lock +5 -5
- {deepstrike-0.2.32 → deepstrike-0.2.34}/Cargo.toml +2 -2
- {deepstrike-0.2.32 → deepstrike-0.2.34}/PKG-INFO +1 -1
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/context/config.rs +10 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/context/manager.rs +66 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/context/renderer.rs +222 -25
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/context/task_state.rs +25 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/scheduler/state_machine/mod.rs +36 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/pyproject.toml +1 -1
- {deepstrike-0.2.32 → deepstrike-0.2.34}/README.md +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/Cargo.toml +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/context/compression.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/context/dashboard.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/context/mod.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/context/partitions.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/context/pressure.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/context/renewal.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/context/sections.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/context/skill_catalog.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/context/snapshot.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/context/summarizer.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/context/text.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/context/token_engine.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/governance/audit.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/governance/constraint.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/governance/mod.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/governance/permission.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/governance/pipeline.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/governance/quota.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/governance/rate_limit.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/governance/sandbox.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/governance/tool_decision.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/governance/veto.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/harness/eval.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/harness/mod.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/lib.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/memory/curator.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/memory/durable.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/memory/extractor.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/memory/idle_pipeline.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/memory/mod.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/memory/runtime.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/memory/semantic.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/memory/session.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/memory/synthesis.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/memory/trace_analyzer.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/memory/working.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/mm/handle.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/mm/memory.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/mm/mod.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/orchestration/mod.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/orchestration/task_graph.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/orchestration/tournament.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/orchestration/workflow/mod.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/orchestration/workflow/run.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/proc/mod.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/runtime/event_log.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/runtime/kernel.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/runtime/mod.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/runtime/repair.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/runtime/replay.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/runtime/session.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/runtime/snapshot.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/scheduler/milestone.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/scheduler/mod.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/scheduler/policy.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/scheduler/rollback.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/scheduler/state_machine/capability.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/scheduler/state_machine/eviction.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/scheduler/state_machine/gate.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/scheduler/state_machine/milestone_exec.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/scheduler/state_machine/process.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/scheduler/state_machine/signal.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/scheduler/state_machine/tests.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/scheduler/state_machine/workflow.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/scheduler/tcb.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/signals/attention.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/signals/mod.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/signals/queue.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/signals/router.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/syscall/mod.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/types/agent.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/types/capability.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/types/contract.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/types/error.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/types/message.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/types/milestone.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/types/mod.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/types/model.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/types/policy.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/types/result.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/types/signal.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/types/skill.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/types/task.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-py/Cargo.toml +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-py/src/lib.rs +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/__init__.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/collaboration/__init__.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/collaboration/contract.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/collaboration/handoff.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/collaboration/harness.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/collaboration/modes.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/collaboration/pool.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/governance.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/harness/__init__.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/harness/harness.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/harness/judge.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/kernel/__init__.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/knowledge/__init__.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/knowledge/source.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/memory/__init__.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/memory/agent.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/memory/in_memory_store.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/memory/protocols.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/memory/working.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/providers/__init__.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/providers/anthropic.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/providers/anthropic_compatible.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/providers/base.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/providers/deepseek.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/providers/factories.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/providers/gemini.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/providers/glm.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/providers/kimi.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/providers/minimax.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/providers/ollama.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/providers/openai.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/providers/openai_responses.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/providers/qwen.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/providers/replay.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/providers/replay_validator.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/providers/stream.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/providers/vendor_profiles.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/runtime/__init__.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/runtime/archive.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/runtime/credential_vault.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/runtime/eval.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/runtime/event_stream.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/runtime/execution_plane.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/runtime/facade.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/runtime/filtered_plane.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/runtime/kernel_event_log.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/runtime/kernel_step.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/runtime/large_result_spool.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/runtime/mcp_proxy_plane.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/runtime/os_profile.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/runtime/os_snapshot.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/runtime/output_schema.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/runtime/process_sandbox_plane.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/runtime/provider_replay.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/runtime/reactive_session.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/runtime/reducers.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/runtime/remote_vpc_plane.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/runtime/replay_fixture.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/runtime/replay_provider.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/runtime/replay_sanitize.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/runtime/run_group.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/runtime/runner.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/runtime/session_log.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/runtime/session_repair.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/runtime/sub_agent_orchestrator.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/runtime/turn_policy.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/runtime/workflow_control_flow.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/runtime/workflow_store.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/runtime/worktree_plane.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/safety/__init__.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/safety/permissions.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/signals/__init__.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/signals/gateway.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/signals/scheduled.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/signals/types.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/skills/__init__.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/skills/registry.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/skills/watcher.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/tools/__init__.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/tools/builtin/__init__.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/tools/builtin/read_file.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/tools/errors.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/tools/execution.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/tools/registry.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/types/__init__.py +0 -0
- {deepstrike-0.2.32 → deepstrike-0.2.34}/deepstrike/types/agent.py +0 -0
|
@@ -194,7 +194,7 @@ dependencies = [
|
|
|
194
194
|
|
|
195
195
|
[[package]]
|
|
196
196
|
name = "deepstrike-core"
|
|
197
|
-
version = "0.2.
|
|
197
|
+
version = "0.2.34"
|
|
198
198
|
dependencies = [
|
|
199
199
|
"compact_str",
|
|
200
200
|
"pretty_assertions",
|
|
@@ -206,7 +206,7 @@ dependencies = [
|
|
|
206
206
|
|
|
207
207
|
[[package]]
|
|
208
208
|
name = "deepstrike-node"
|
|
209
|
-
version = "0.2.
|
|
209
|
+
version = "0.2.34"
|
|
210
210
|
dependencies = [
|
|
211
211
|
"compact_str",
|
|
212
212
|
"deepstrike-core",
|
|
@@ -218,7 +218,7 @@ dependencies = [
|
|
|
218
218
|
|
|
219
219
|
[[package]]
|
|
220
220
|
name = "deepstrike-py"
|
|
221
|
-
version = "0.2.
|
|
221
|
+
version = "0.2.34"
|
|
222
222
|
dependencies = [
|
|
223
223
|
"compact_str",
|
|
224
224
|
"deepstrike-core",
|
|
@@ -229,7 +229,7 @@ dependencies = [
|
|
|
229
229
|
|
|
230
230
|
[[package]]
|
|
231
231
|
name = "deepstrike-sdk"
|
|
232
|
-
version = "0.2.
|
|
232
|
+
version = "0.2.34"
|
|
233
233
|
dependencies = [
|
|
234
234
|
"async-stream",
|
|
235
235
|
"async-trait",
|
|
@@ -263,7 +263,7 @@ dependencies = [
|
|
|
263
263
|
|
|
264
264
|
[[package]]
|
|
265
265
|
name = "deepstrike-wasm"
|
|
266
|
-
version = "0.2.
|
|
266
|
+
version = "0.2.34"
|
|
267
267
|
dependencies = [
|
|
268
268
|
"compact_str",
|
|
269
269
|
"deepstrike-core",
|
|
@@ -3,13 +3,13 @@ resolver = "2"
|
|
|
3
3
|
members = ["crates/deepstrike-core", "crates/deepstrike-py"]
|
|
4
4
|
|
|
5
5
|
[workspace.package]
|
|
6
|
-
version = "0.2.
|
|
6
|
+
version = "0.2.34"
|
|
7
7
|
edition = "2024"
|
|
8
8
|
license = "MIT"
|
|
9
9
|
repository = "https://github.com/kongusen/deepstrike"
|
|
10
10
|
|
|
11
11
|
[workspace.dependencies]
|
|
12
|
-
deepstrike-core = { path = "crates/deepstrike-core", version = "0.2.
|
|
12
|
+
deepstrike-core = { path = "crates/deepstrike-core", version = "0.2.34" }
|
|
13
13
|
notify = "6"
|
|
14
14
|
serde = { version = "1", features = ["derive"] }
|
|
15
15
|
serde_json = "1"
|
|
@@ -49,6 +49,15 @@ pub struct ContextConfig {
|
|
|
49
49
|
/// Defaults to false; uses concise natural-language notes instead.
|
|
50
50
|
pub verbose_control_notes: bool,
|
|
51
51
|
|
|
52
|
+
/// Collapse the *narration* text of OLD assistant turns (those past the
|
|
53
|
+
/// `preserve_recent_msgs` window that also carry tool calls) to a short stub at render time —
|
|
54
|
+
/// non-destructively (the full text stays in `partitions.history`). The model's user-facing
|
|
55
|
+
/// preamble ("好的,我来…先X") has no value once it has aged out of the recent window, but
|
|
56
|
+
/// re-feeding it verbatim every turn primes the model to keep emitting the same preamble (an
|
|
57
|
+
/// in-context repetition trap). Tool calls and pairing are untouched; current progress lives in
|
|
58
|
+
/// the TASK STATE turn. Defaults to true.
|
|
59
|
+
pub collapse_assistant_narration: bool,
|
|
60
|
+
|
|
52
61
|
// ── Layer 3: Time-based decay ───────────────────────────────────────
|
|
53
62
|
|
|
54
63
|
/// Minutes of inactivity before triggering Micro-Compact (Layer 3).
|
|
@@ -104,6 +113,7 @@ impl Default for ContextConfig {
|
|
|
104
113
|
preserve_recent_turns: 2,
|
|
105
114
|
render_dashboard: false,
|
|
106
115
|
verbose_control_notes: false,
|
|
116
|
+
collapse_assistant_narration: true,
|
|
107
117
|
micro_compact_idle_minutes: 60,
|
|
108
118
|
preserved_tool_results: 5,
|
|
109
119
|
autocompact_buffer: 13_000,
|
|
@@ -18,6 +18,21 @@ use compact_str::CompactString;
|
|
|
18
18
|
pub const MEMORY_TOOL_NAME: &str = "memory";
|
|
19
19
|
pub const KNOWLEDGE_TOOL_NAME: &str = "knowledge";
|
|
20
20
|
|
|
21
|
+
/// Control-plane meta-tools: kernel-handled tools that drive state/capabilities rather than do task
|
|
22
|
+
/// work. Excluded from the `recent_actions` progress log (2b) so the footer reflects real progress.
|
|
23
|
+
const META_TOOL_NAMES: &[&str] = &[
|
|
24
|
+
"update_plan",
|
|
25
|
+
"skill",
|
|
26
|
+
MEMORY_TOOL_NAME,
|
|
27
|
+
KNOWLEDGE_TOOL_NAME,
|
|
28
|
+
"submit_workflow_nodes",
|
|
29
|
+
"start_workflow",
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
fn is_meta_tool(name: &str) -> bool {
|
|
33
|
+
META_TOOL_NAMES.contains(&name)
|
|
34
|
+
}
|
|
35
|
+
|
|
21
36
|
/// Internal context engine backing [`crate::runtime::KernelRuntime`].
|
|
22
37
|
///
|
|
23
38
|
/// Exposed for in-crate use and tests; external callers should drive the kernel
|
|
@@ -411,6 +426,7 @@ impl ContextManager {
|
|
|
411
426
|
self.config.preserve_recent_msgs,
|
|
412
427
|
&self.handles,
|
|
413
428
|
self.frozen_history_len,
|
|
429
|
+
self.config.collapse_assistant_narration,
|
|
414
430
|
)
|
|
415
431
|
}
|
|
416
432
|
|
|
@@ -485,6 +501,24 @@ impl ContextManager {
|
|
|
485
501
|
self.partitions.task_state.apply(update);
|
|
486
502
|
}
|
|
487
503
|
|
|
504
|
+
/// 2b: record this turn's tool activity into the task-state recency log (kernel-derived progress
|
|
505
|
+
/// that feeds the State-turn footer). Each entry is `(name, compact_args)`; the rendered signature
|
|
506
|
+
/// is `name(args)` (or bare `name` for no-arg calls) so the no-progress STOP keys on the WHOLE
|
|
507
|
+
/// call — same tool with different args (a legit loop over items) reads as distinct progress, not
|
|
508
|
+
/// a repeat. Control-plane meta-tools (plan/skill/memory/knowledge/workflow authoring) are noise,
|
|
509
|
+
/// not task progress — filtered by name. A turn with only meta-tool calls records nothing.
|
|
510
|
+
pub fn note_tool_actions(&mut self, calls: &[(String, String)]) {
|
|
511
|
+
let summary = calls
|
|
512
|
+
.iter()
|
|
513
|
+
.filter(|(name, _)| !is_meta_tool(name))
|
|
514
|
+
.map(|(name, args)| {
|
|
515
|
+
if args.is_empty() { name.clone() } else { format!("{name}({args})") }
|
|
516
|
+
})
|
|
517
|
+
.collect::<Vec<_>>()
|
|
518
|
+
.join(", ");
|
|
519
|
+
self.partitions.task_state.note_actions(summary);
|
|
520
|
+
}
|
|
521
|
+
|
|
488
522
|
// ── Section pinning ───────────────────────────────────────────────────────
|
|
489
523
|
|
|
490
524
|
pub fn pin_section(&mut self, id: &str) -> bool { self.sections.pin(id) }
|
|
@@ -635,6 +669,38 @@ mod tests {
|
|
|
635
669
|
use crate::types::message::Message;
|
|
636
670
|
use crate::types::skill::SkillMetadata;
|
|
637
671
|
|
|
672
|
+
#[test]
|
|
673
|
+
fn note_tool_actions_keys_on_name_and_args_so_legit_loops_dont_false_stop() {
|
|
674
|
+
// Same tool, DIFFERENT args across turns = real progress (e.g. process item 1, 2, 3) —
|
|
675
|
+
// must NOT trip the no-progress STOP backstop.
|
|
676
|
+
let mut mgr = ContextManager::new(100_000);
|
|
677
|
+
mgr.init_task("process items".to_string(), vec![]);
|
|
678
|
+
mgr.note_tool_actions(&[("step".to_string(), "{\"n\":1}".to_string())]);
|
|
679
|
+
mgr.note_tool_actions(&[("step".to_string(), "{\"n\":2}".to_string())]);
|
|
680
|
+
mgr.note_tool_actions(&[("step".to_string(), "{\"n\":3}".to_string())]);
|
|
681
|
+
assert_eq!(
|
|
682
|
+
mgr.partitions.task_state.recent_actions,
|
|
683
|
+
["step({\"n\":1})", "step({\"n\":2})", "step({\"n\":3})"]
|
|
684
|
+
);
|
|
685
|
+
let txt = mgr.render().state_turn.unwrap().content.as_text().unwrap().to_string();
|
|
686
|
+
assert!(!txt.contains("STOP:"), "same-tool/diff-args loop must not trip STOP: {txt}");
|
|
687
|
+
|
|
688
|
+
// Genuine stall — same tool, SAME args repeated — DOES trip the STOP.
|
|
689
|
+
let mut mgr2 = ContextManager::new(100_000);
|
|
690
|
+
mgr2.init_task("g".to_string(), vec![]);
|
|
691
|
+
for _ in 0..3 {
|
|
692
|
+
mgr2.note_tool_actions(&[("document_read".to_string(), "{\"id\":\"x\"}".to_string())]);
|
|
693
|
+
}
|
|
694
|
+
let txt2 = mgr2.render().state_turn.unwrap().content.as_text().unwrap().to_string();
|
|
695
|
+
assert!(txt2.contains("STOP:"), "identical repeated call must trip STOP: {txt2}");
|
|
696
|
+
|
|
697
|
+
// Meta-tools are control plane, not task progress — filtered out entirely.
|
|
698
|
+
let mut mgr3 = ContextManager::new(100_000);
|
|
699
|
+
mgr3.init_task("g".to_string(), vec![]);
|
|
700
|
+
mgr3.note_tool_actions(&[("update_plan".to_string(), "{\"current_step\":1}".to_string())]);
|
|
701
|
+
assert!(mgr3.partitions.task_state.recent_actions.is_empty());
|
|
702
|
+
}
|
|
703
|
+
|
|
638
704
|
#[test]
|
|
639
705
|
fn manager_renew_uses_task_state_goal() {
|
|
640
706
|
let mut mgr = ContextManager::new(1_000);
|
|
@@ -124,27 +124,69 @@ fn build_system_knowledge(partitions: &ContextPartitions) -> String {
|
|
|
124
124
|
.join("\n\n")
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
/// P1-F: a one-line recency footer
|
|
128
|
-
///
|
|
129
|
-
///
|
|
130
|
-
///
|
|
131
|
-
///
|
|
127
|
+
/// P1-F (+ 2b/2c): a one-line recency footer at the *last* content before the "Proceed." anchor —
|
|
128
|
+
/// the highest-attention position in the prompt (the model attends most to the final tokens).
|
|
129
|
+
///
|
|
130
|
+
/// It LEADS WITH FORWARD MOTION (what just happened · what to do next · the standing directive), not
|
|
131
|
+
/// a verbatim restatement of the goal. Re-injecting the bare goal at this peak-attention slot every
|
|
132
|
+
/// turn primes the model to *re-narrate intent* ("好的,我来将<goal>…") instead of acting — an
|
|
133
|
+
/// undamped repetition trap when there is no plan/progress to advance. The full goal still LEADS the
|
|
134
|
+
/// TASK STATE block above (primacy + reference), so goal-adherence is preserved; the footer restates
|
|
135
|
+
/// the goal only when nothing has happened yet (e.g. turn 1, no actions). `None` when there is no goal.
|
|
136
|
+
///
|
|
137
|
+
/// The "just did" clause is kernel-derived from `recent_actions` (real tool activity), and a trailing
|
|
138
|
+
/// run of an identical action raises an explicit STOP — a cheap no-progress backstop that breaks the
|
|
139
|
+
/// read→re-read→re-narrate loop in-band, at the position the model weights most.
|
|
132
140
|
fn salience_footer(ts: &TaskState) -> Option<String> {
|
|
133
141
|
if ts.goal.is_empty() {
|
|
134
142
|
return None;
|
|
135
143
|
}
|
|
136
|
-
let mut
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
144
|
+
let mut clauses: Vec<String> = Vec::new();
|
|
145
|
+
|
|
146
|
+
// What just happened — display tool NAMES only. The full `name(args)` signatures are kept in
|
|
147
|
+
// `recent_actions` for the repeat check below, but rendering them every turn bloats the volatile
|
|
148
|
+
// footer; the names alone show motion at the peak-attention slot.
|
|
149
|
+
let recent = ts.recent_actions.as_slice();
|
|
150
|
+
let action_name = |entry: &str| entry.split('(').next().unwrap_or(entry).to_string();
|
|
151
|
+
if let Some(last) = recent.last() {
|
|
152
|
+
let start = recent.len().saturating_sub(3);
|
|
153
|
+
let names = recent[start..].iter().map(|e| action_name(e)).collect::<Vec<_>>().join(" → ");
|
|
154
|
+
clauses.push(format!("did: {names}"));
|
|
155
|
+
|
|
156
|
+
// No-progress backstop: the SAME call — name AND args — repeated on the last ≥2 turns is a
|
|
157
|
+
// stall (a legit loop varies its args, so it reads as distinct progress, not a repeat).
|
|
158
|
+
let trailing_repeat = recent.iter().rev().take_while(|a| *a == last).count();
|
|
159
|
+
if trailing_repeat >= 2 {
|
|
160
|
+
clauses.push(format!(
|
|
161
|
+
"STOP: `{}` repeated {trailing_repeat}× unchanged — do something different or report",
|
|
162
|
+
action_name(last)
|
|
163
|
+
));
|
|
142
164
|
}
|
|
143
165
|
}
|
|
166
|
+
|
|
167
|
+
// What to do next — the active plan step if the model maintains one, else a short forward nudge.
|
|
168
|
+
let active_step = ts
|
|
169
|
+
.current_step
|
|
170
|
+
.and_then(|i| ts.plan.get(i).map(|s| (i, s)))
|
|
171
|
+
.filter(|(_, s)| !s.done);
|
|
172
|
+
if let Some((i, step)) = active_step {
|
|
173
|
+
clauses.push(format!("next: step {} — {}", i + 1, step.label));
|
|
174
|
+
} else if !recent.is_empty() {
|
|
175
|
+
clauses.push("next: advance the goal".to_string());
|
|
176
|
+
}
|
|
177
|
+
|
|
144
178
|
if let Some(d) = ts.directives.last() {
|
|
145
|
-
|
|
179
|
+
clauses.push(format!("must: {d}"));
|
|
146
180
|
}
|
|
147
|
-
|
|
181
|
+
|
|
182
|
+
// Lead with the goal only when no forward clause fills the footer (turn 1, nothing done yet);
|
|
183
|
+
// otherwise the forward clauses carry the salience and the goal stays in the block above.
|
|
184
|
+
let body = if clauses.is_empty() {
|
|
185
|
+
format!("→ focus: {}", ts.goal)
|
|
186
|
+
} else {
|
|
187
|
+
format!("→ {}", clauses.join(" · "))
|
|
188
|
+
};
|
|
189
|
+
Some(body)
|
|
148
190
|
}
|
|
149
191
|
|
|
150
192
|
/// Build the State turn (the volatile tail): task_state + signals + a recency focus footer +
|
|
@@ -194,6 +236,37 @@ fn collapse_preview(output: &str) -> String {
|
|
|
194
236
|
)
|
|
195
237
|
}
|
|
196
238
|
|
|
239
|
+
/// Stub substituted for a collapsed assistant preamble. Carries no goal text (that would re-seed the
|
|
240
|
+
/// very repetition this removes) and points the model at the authoritative State turn instead.
|
|
241
|
+
const NARRATION_STUB: &str = "[earlier narration collapsed; tool call(s) preserved below — current progress is in the TASK STATE block]";
|
|
242
|
+
|
|
243
|
+
/// Minimum narration length (chars, CJK-aware) worth collapsing. Short preambles aren't worth a
|
|
244
|
+
/// stub substitution (and the one-time cache churn it costs as the turn ages out of the window).
|
|
245
|
+
const NARRATION_COLLAPSE_MIN_CHARS: usize = 40;
|
|
246
|
+
|
|
247
|
+
/// Method 1: read-time collapse of an OLD assistant turn's narration. Targets exactly the
|
|
248
|
+
/// "preamble before action" turns — `Role::Assistant`, a `Content::Text` body, AND a non-empty
|
|
249
|
+
/// `tool_calls` (the model narrated intent, then acted). Returns a projected copy whose text is
|
|
250
|
+
/// replaced by [`NARRATION_STUB`] while `tool_calls` (and thus tool_use/tool_result pairing) are
|
|
251
|
+
/// left intact; the original full text stays in `partitions.history`, so the projection reverses if
|
|
252
|
+
/// the flag is turned off. `None` when the message isn't a collapsible narration turn or the flag is
|
|
253
|
+
/// off. Caller restricts this to messages already past the protected recent window.
|
|
254
|
+
fn project_assistant_narration(msg: &Message, enabled: bool) -> Option<Message> {
|
|
255
|
+
if !enabled || msg.role != Role::Assistant || msg.tool_calls.is_empty() {
|
|
256
|
+
return None;
|
|
257
|
+
}
|
|
258
|
+
let Content::Text(text) = &msg.content else {
|
|
259
|
+
return None;
|
|
260
|
+
};
|
|
261
|
+
if text == NARRATION_STUB || text.chars().count() < NARRATION_COLLAPSE_MIN_CHARS {
|
|
262
|
+
return None;
|
|
263
|
+
}
|
|
264
|
+
let mut projected = msg.clone();
|
|
265
|
+
projected.content = Content::Text(NARRATION_STUB.to_string());
|
|
266
|
+
projected.token_count = None; // recomputed against the smaller stub
|
|
267
|
+
Some(projected)
|
|
268
|
+
}
|
|
269
|
+
|
|
197
270
|
/// If any of `msg`'s tool-result parts is `Collapsed` per the handle table, return a projected
|
|
198
271
|
/// copy with those parts previewed; `None` if nothing is collapsed (render the message as-is).
|
|
199
272
|
fn project_message(msg: &Message, handles: &HandleTable) -> Option<Message> {
|
|
@@ -240,7 +313,9 @@ pub fn render(
|
|
|
240
313
|
engine: &ContextTokenEngine,
|
|
241
314
|
preserve_recent_msgs: usize,
|
|
242
315
|
) -> RenderedContext {
|
|
243
|
-
|
|
316
|
+
// The convenience wrapper renders history verbatim (no narration collapse) — callers that want
|
|
317
|
+
// Method-1 collapse drive `render_projected` with the flag (the kernel passes it from config).
|
|
318
|
+
render_projected(partitions, budget, engine, preserve_recent_msgs, &HandleTable::new(), 0, false)
|
|
244
319
|
}
|
|
245
320
|
|
|
246
321
|
/// Render with Layer-4 read-time projection driven by `handles`: tool results whose handle is
|
|
@@ -258,6 +333,7 @@ pub fn render_projected(
|
|
|
258
333
|
preserve_recent_msgs: usize,
|
|
259
334
|
handles: &HandleTable,
|
|
260
335
|
frozen_history_len: usize,
|
|
336
|
+
collapse_narration: bool,
|
|
261
337
|
) -> RenderedContext {
|
|
262
338
|
let system_stable = build_system_stable(partitions);
|
|
263
339
|
let system_knowledge = build_system_knowledge(partitions);
|
|
@@ -275,8 +351,14 @@ pub fn render_projected(
|
|
|
275
351
|
// message: a collapsed tool result renders as a preview and is costed at its reduced size.
|
|
276
352
|
let mut kept_rev: Vec<Message> = Vec::new();
|
|
277
353
|
for msg in partitions.history.messages.iter().rev() {
|
|
278
|
-
|
|
279
|
-
|
|
354
|
+
let is_protected = kept_rev.len() < preserve_recent_msgs;
|
|
355
|
+
// `projected` is `Some` only when read-time projection shrank the message. Two disjoint
|
|
356
|
+
// sources: a `Collapsed` tool-result preview (handle-driven, any age) OR — once the turn has
|
|
357
|
+
// aged past the protected recent window — an assistant-narration stub (Method 1). A message
|
|
358
|
+
// is either a tool-result Parts message or an assistant Text+tool_calls message, never both.
|
|
359
|
+
let projected = project_message(msg, handles).or_else(|| {
|
|
360
|
+
if is_protected { None } else { project_assistant_narration(msg, collapse_narration) }
|
|
361
|
+
});
|
|
280
362
|
let effective = projected.as_ref().unwrap_or(msg);
|
|
281
363
|
let tokens = match &projected {
|
|
282
364
|
Some(p) => engine.count_message(p),
|
|
@@ -284,7 +366,6 @@ pub fn render_projected(
|
|
|
284
366
|
};
|
|
285
367
|
if tokens == 0 { continue; }
|
|
286
368
|
|
|
287
|
-
let is_protected = kept_rev.len() < preserve_recent_msgs;
|
|
288
369
|
if is_protected {
|
|
289
370
|
kept_rev.push(effective.clone());
|
|
290
371
|
remaining = remaining.saturating_sub(tokens);
|
|
@@ -455,7 +536,7 @@ mod tests {
|
|
|
455
536
|
h.residency = Residency::Collapsed;
|
|
456
537
|
handles.insert(h);
|
|
457
538
|
|
|
458
|
-
let rc = render_projected(&c, 10_000, &engine(), 4, &handles, 0);
|
|
539
|
+
let rc = render_projected(&c, 10_000, &engine(), 4, &handles, 0, false);
|
|
459
540
|
let rendered: String = rc
|
|
460
541
|
.turns
|
|
461
542
|
.iter()
|
|
@@ -498,7 +579,7 @@ mod tests {
|
|
|
498
579
|
let mut handles = HandleTable::new();
|
|
499
580
|
handles.insert(Handle::resident_for(1, HandleKind::ToolResult, 60, "c2"));
|
|
500
581
|
|
|
501
|
-
let rc = render_projected(&c, 10_000, &engine(), 4, &handles, 0);
|
|
582
|
+
let rc = render_projected(&c, 10_000, &engine(), 4, &handles, 0, false);
|
|
502
583
|
let rendered: String = rc
|
|
503
584
|
.turns
|
|
504
585
|
.iter()
|
|
@@ -518,7 +599,7 @@ mod tests {
|
|
|
518
599
|
// ── P1-F: state-turn recency footer ───────────────────────────────────
|
|
519
600
|
|
|
520
601
|
#[test]
|
|
521
|
-
fn
|
|
602
|
+
fn state_turn_footer_leads_with_next_step_not_bare_goal() {
|
|
522
603
|
let mut c = ctx();
|
|
523
604
|
c.task_state = TaskState {
|
|
524
605
|
goal: "ship the cache work".to_string(),
|
|
@@ -530,14 +611,56 @@ mod tests {
|
|
|
530
611
|
let rc = render(&c, 100_000, &engine(), 4);
|
|
531
612
|
let text = rc.state_turn.unwrap().content.as_text().unwrap().to_string();
|
|
532
613
|
|
|
533
|
-
// The full TASK STATE block still
|
|
614
|
+
// The full TASK STATE block still LEADS (primacy) — goal-adherence preserved ...
|
|
534
615
|
assert!(text.starts_with("[TASK STATE] goal: ship the cache work"));
|
|
535
|
-
// ...
|
|
616
|
+
// ... but the peak-attention footer leads with the forward action, not a goal restatement.
|
|
536
617
|
let before_proceed = text.rsplit_once("\n\nProceed.").expect("ends with Proceed").0;
|
|
537
618
|
let last_block = before_proceed.rsplit("\n\n").next().unwrap();
|
|
538
|
-
assert!(last_block.starts_with("→
|
|
539
|
-
assert!(last_block.contains("step 1: do E"));
|
|
619
|
+
assert!(last_block.starts_with("→ next: step 1 — do E"), "got: {last_block}");
|
|
540
620
|
assert!(last_block.contains("must: don't break ABI"));
|
|
621
|
+
// The bare goal must NOT be re-injected at the peak-attention tail (the repetition fuel).
|
|
622
|
+
assert!(!last_block.contains("focus: ship the cache work"), "got: {last_block}");
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
#[test]
|
|
626
|
+
fn footer_falls_back_to_focus_goal_when_nothing_done_yet() {
|
|
627
|
+
// Turn 1: no actions, no plan — the footer surfaces the goal so the model knows the objective.
|
|
628
|
+
let mut c = ctx();
|
|
629
|
+
c.task_state = TaskState { goal: "build the thing".to_string(), ..Default::default() };
|
|
630
|
+
let rc = render(&c, 100_000, &engine(), 4);
|
|
631
|
+
let text = rc.state_turn.unwrap().content.as_text().unwrap().to_string();
|
|
632
|
+
let footer = text.rsplit_once("\n\nProceed.").unwrap().0.rsplit("\n\n").next().unwrap();
|
|
633
|
+
assert_eq!(footer, "→ focus: build the thing");
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
#[test]
|
|
637
|
+
fn footer_shows_recent_actions_and_forward_nudge_without_a_plan() {
|
|
638
|
+
// No curated plan, but real tool activity (2b) → the footer shows motion + a forward nudge,
|
|
639
|
+
// and the goal is NOT restated at the tail.
|
|
640
|
+
let mut c = ctx();
|
|
641
|
+
c.task_state = TaskState { goal: "rebuild §4.4 as SVG".to_string(), ..Default::default() };
|
|
642
|
+
c.task_state.note_actions("module_list");
|
|
643
|
+
c.task_state.note_actions("module_read");
|
|
644
|
+
let rc = render(&c, 100_000, &engine(), 4);
|
|
645
|
+
let footer = rc.state_turn.unwrap().content.as_text().unwrap()
|
|
646
|
+
.rsplit_once("\n\nProceed.").unwrap().0.rsplit("\n\n").next().unwrap().to_string();
|
|
647
|
+
assert!(footer.contains("did: module_list → module_read"), "got: {footer}");
|
|
648
|
+
assert!(footer.contains("next: advance the goal"), "got: {footer}");
|
|
649
|
+
assert!(!footer.contains("focus: rebuild §4.4 as SVG"), "goal must not lead the footer");
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
#[test]
|
|
653
|
+
fn footer_raises_stop_on_repeated_action() {
|
|
654
|
+
// The same action on the last ≥2 turns ⇒ explicit STOP backstop (breaks the read-loop in-band).
|
|
655
|
+
let mut c = ctx();
|
|
656
|
+
c.task_state = TaskState { goal: "g".to_string(), ..Default::default() };
|
|
657
|
+
c.task_state.note_actions("document_read");
|
|
658
|
+
c.task_state.note_actions("document_read");
|
|
659
|
+
c.task_state.note_actions("document_read");
|
|
660
|
+
let rc = render(&c, 100_000, &engine(), 4);
|
|
661
|
+
let footer = rc.state_turn.unwrap().content.as_text().unwrap()
|
|
662
|
+
.rsplit_once("\n\nProceed.").unwrap().0.rsplit("\n\n").next().unwrap().to_string();
|
|
663
|
+
assert!(footer.contains("STOP: `document_read` repeated 3×"), "got: {footer}");
|
|
541
664
|
}
|
|
542
665
|
|
|
543
666
|
#[test]
|
|
@@ -627,13 +750,87 @@ mod tests {
|
|
|
627
750
|
let mut h = Handle::resident_for(1, HandleKind::ToolResult, 250, "c1");
|
|
628
751
|
h.residency = Residency::Collapsed;
|
|
629
752
|
handles.insert(h);
|
|
630
|
-
let collapsed = render_projected(&c, 100_000, &engine(), 4, &handles, 0).prefix_fingerprint();
|
|
753
|
+
let collapsed = render_projected(&c, 100_000, &engine(), 4, &handles, 0, false).prefix_fingerprint();
|
|
631
754
|
|
|
632
755
|
// turn 0 ("start") is byte-stable; the collapsed tool result at turn 1 drifts.
|
|
633
756
|
assert_eq!(collapsed.common_turn_prefix(&resident), 1, "drift begins at the collapsed turn");
|
|
634
757
|
assert!(!collapsed.extends(&resident));
|
|
635
758
|
}
|
|
636
759
|
|
|
760
|
+
// ── Method 1: assistant-narration collapse ─────────────────────────────
|
|
761
|
+
|
|
762
|
+
fn assistant_with_call(text: &str) -> Message {
|
|
763
|
+
let mut m = Message::assistant(text);
|
|
764
|
+
m.tool_calls = vec![crate::types::message::ToolCall {
|
|
765
|
+
id: "c1".into(),
|
|
766
|
+
name: "module_read".into(),
|
|
767
|
+
arguments: serde_json::json!({}),
|
|
768
|
+
}];
|
|
769
|
+
m
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
#[test]
|
|
773
|
+
fn old_assistant_narration_collapses_keeping_tool_calls() {
|
|
774
|
+
let mut c = ctx();
|
|
775
|
+
// Oldest = a long preamble + a tool call; then enough recent turns to push it past the window.
|
|
776
|
+
c.history.push(assistant_with_call(&"好的,我来将 §4.4 的 Mermaid 部署架构图重新构建为 SVG 版本。先找到当前 Mermaid 模块的位置。".repeat(1)), 60);
|
|
777
|
+
for i in 0..5 { c.history.push(Message::user(format!("recent {i}")), 5); }
|
|
778
|
+
|
|
779
|
+
// collapse ON (preserve window = 4, so the oldest narration turn is past it)
|
|
780
|
+
let rc = render_projected(&c, 100_000, &engine(), 4, &HandleTable::new(), 0, true);
|
|
781
|
+
let narration = rc
|
|
782
|
+
.turns
|
|
783
|
+
.iter()
|
|
784
|
+
.find(|m| m.content.as_text() == Some(NARRATION_STUB))
|
|
785
|
+
.expect("old narration replaced by stub");
|
|
786
|
+
assert_eq!(narration.tool_calls.len(), 1, "tool call (pairing) preserved");
|
|
787
|
+
assert_eq!(narration.tool_calls[0].name, "module_read");
|
|
788
|
+
// No verbatim preamble survives in the rendered prefix.
|
|
789
|
+
assert!(!rc.turns.iter().any(|m| m.content.as_text().map(|t| t.contains("先找到当前 Mermaid")).unwrap_or(false)));
|
|
790
|
+
// Original history is untouched (non-destructive projection).
|
|
791
|
+
assert!(c.history.messages[0].content.as_text().unwrap().contains("先找到当前 Mermaid"));
|
|
792
|
+
|
|
793
|
+
// collapse OFF → verbatim narration survives.
|
|
794
|
+
let rc_off = render_projected(&c, 100_000, &engine(), 4, &HandleTable::new(), 0, false);
|
|
795
|
+
assert!(rc_off.turns.iter().any(|m| m.content.as_text().map(|t| t.contains("先找到当前 Mermaid")).unwrap_or(false)));
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
#[test]
|
|
799
|
+
fn recent_assistant_narration_within_window_is_not_collapsed() {
|
|
800
|
+
let mut c = ctx();
|
|
801
|
+
// Only 2 turns, preserve window = 4 → the narration turn is protected → never collapsed.
|
|
802
|
+
c.history.push(assistant_with_call(&"好的,我来将 §4.4 重新构建为 SVG。先定位模块位置确认范围读取内容。".to_string()), 60);
|
|
803
|
+
c.history.push(Message::user("ok"), 5);
|
|
804
|
+
let rc = render_projected(&c, 100_000, &engine(), 4, &HandleTable::new(), 0, true);
|
|
805
|
+
assert!(rc.turns.iter().any(|m| m.content.as_text().map(|t| t.contains("先定位模块位置")).unwrap_or(false)), "recent narration kept verbatim");
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
#[test]
|
|
809
|
+
fn assistant_without_tool_calls_is_never_collapsed() {
|
|
810
|
+
let mut c = ctx();
|
|
811
|
+
// A pure final answer (no tool calls) is substantive — must survive even when old.
|
|
812
|
+
c.history.push(Message::assistant("这是给用户的最终结论,包含实质内容,不应被折叠掉以免丢信息。"), 40);
|
|
813
|
+
for i in 0..5 { c.history.push(Message::user(format!("r{i}")), 5); }
|
|
814
|
+
let rc = render_projected(&c, 100_000, &engine(), 4, &HandleTable::new(), 0, true);
|
|
815
|
+
assert!(rc.turns.iter().any(|m| m.content.as_text().map(|t| t.contains("最终结论")).unwrap_or(false)), "answer-only turns are not narration");
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
#[test]
|
|
819
|
+
fn collapsing_narration_drifts_only_that_turn_in_the_cache_prefix() {
|
|
820
|
+
// The cost made visible: collapsing rewrites that one turn in place → the prefix hash drifts
|
|
821
|
+
// at its position (one-time, as it ages past the window), but earlier turns stay reusable.
|
|
822
|
+
let mut c = ctx();
|
|
823
|
+
c.history.push(Message::user("start"), 5);
|
|
824
|
+
c.history.push(assistant_with_call(&"好的,我来将 §4.4 重新构建为 SVG 版本。先找到 Mermaid 模块的确切位置再读取其内容。".to_string()), 60);
|
|
825
|
+
for i in 0..4 { c.history.push(Message::user(format!("recent {i}")), 5); }
|
|
826
|
+
|
|
827
|
+
let verbatim = render_projected(&c, 100_000, &engine(), 4, &HandleTable::new(), 0, false).prefix_fingerprint();
|
|
828
|
+
let collapsed = render_projected(&c, 100_000, &engine(), 4, &HandleTable::new(), 0, true).prefix_fingerprint();
|
|
829
|
+
// turn 0 ("start") is byte-stable; drift begins at the collapsed narration turn (index 1).
|
|
830
|
+
assert_eq!(collapsed.common_turn_prefix(&verbatim), 1, "only the collapsed turn drifts");
|
|
831
|
+
assert!(!collapsed.extends(&verbatim));
|
|
832
|
+
}
|
|
833
|
+
|
|
637
834
|
#[test]
|
|
638
835
|
fn protected_recent_messages_kept_whole_over_budget() {
|
|
639
836
|
let mut c = ctx();
|
|
@@ -41,6 +41,13 @@ pub struct TaskState {
|
|
|
41
41
|
/// Call IDs or artifact hashes that must be preserved from compression.
|
|
42
42
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
|
43
43
|
pub preserved_refs: Vec<String>,
|
|
44
|
+
/// Rolling log of recent *task* activity — one entry per turn, each a compact summary of that
|
|
45
|
+
/// turn's tool calls (e.g. "module_read, module_list"). Kernel-maintained from REAL tool
|
|
46
|
+
/// activity (not model-curated), so the State turn always shows forward motion even when the
|
|
47
|
+
/// model never maintains `plan`. Lives in the volatile State turn (out of the cacheable prefix),
|
|
48
|
+
/// so updating it never churns the prompt cache. Bounded + recency-ordered; newest last.
|
|
49
|
+
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
|
50
|
+
pub recent_actions: Vec<String>,
|
|
44
51
|
/// Append-only log of all compression events. Never overwritten.
|
|
45
52
|
/// Rendered into systemVolatile so the model always sees compression history.
|
|
46
53
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
|
@@ -65,6 +72,9 @@ impl PlanStep {
|
|
|
65
72
|
/// Maximum durable directives retained; past this the oldest is dropped (recency window).
|
|
66
73
|
pub const MAX_DIRECTIVES: usize = 8;
|
|
67
74
|
|
|
75
|
+
/// Maximum recent action-turns retained for the recency footer (bounded ring).
|
|
76
|
+
pub const MAX_RECENT_ACTIONS: usize = 6;
|
|
77
|
+
|
|
68
78
|
/// Partial update applied by the SDK or via `update_plan` meta-tool.
|
|
69
79
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
|
70
80
|
pub struct TaskUpdate {
|
|
@@ -161,6 +171,21 @@ impl TaskState {
|
|
|
161
171
|
}
|
|
162
172
|
}
|
|
163
173
|
|
|
174
|
+
/// Record one turn's tool activity into the recency log (kernel-driven). `summary` is a compact
|
|
175
|
+
/// string of the turn's task tool names; blank input is ignored. Bounded at
|
|
176
|
+
/// [`MAX_RECENT_ACTIONS`] (oldest dropped past the cap).
|
|
177
|
+
pub fn note_actions(&mut self, summary: impl Into<String>) {
|
|
178
|
+
let summary = summary.into();
|
|
179
|
+
if summary.trim().is_empty() {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
self.recent_actions.push(summary);
|
|
183
|
+
if self.recent_actions.len() > MAX_RECENT_ACTIONS {
|
|
184
|
+
let overflow = self.recent_actions.len() - MAX_RECENT_ACTIONS;
|
|
185
|
+
self.recent_actions.drain(0..overflow);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
164
189
|
/// Append a compression event to the log. Never overwrites existing entries.
|
|
165
190
|
pub fn log_compression(&mut self, action: &str, summary: String) {
|
|
166
191
|
self.compression_log.push(CompressionEntry {
|
{deepstrike-0.2.32 → deepstrike-0.2.34}/crates/deepstrike-core/src/scheduler/state_machine/mod.rs
RENAMED
|
@@ -21,6 +21,26 @@ use crate::types::result::{LoopResult, TerminationReason};
|
|
|
21
21
|
use crate::types::signal::RuntimeSignal;
|
|
22
22
|
use crate::types::task::RuntimeTask;
|
|
23
23
|
|
|
24
|
+
/// Compact digest of a tool call's arguments for the recency log (2b). Kept short and CJK-safe — it
|
|
25
|
+
/// only needs to make `same-tool / different-args` calls distinguishable (so a legit loop isn't
|
|
26
|
+
/// flagged as a no-progress repeat) and to read sensibly in the "just did: …" footer. Empty for
|
|
27
|
+
/// no-arg / `{}` calls. Lives in the volatile State turn, so length here never churns the cache.
|
|
28
|
+
fn compact_tool_args(args: &serde_json::Value) -> String {
|
|
29
|
+
if args.is_null() {
|
|
30
|
+
return String::new();
|
|
31
|
+
}
|
|
32
|
+
let s = args.to_string();
|
|
33
|
+
if s == "{}" {
|
|
34
|
+
return String::new();
|
|
35
|
+
}
|
|
36
|
+
const MAX: usize = 48;
|
|
37
|
+
if s.chars().count() <= MAX {
|
|
38
|
+
s
|
|
39
|
+
} else {
|
|
40
|
+
format!("{}…", s.chars().take(MAX).collect::<String>())
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
24
44
|
/// The *turn step* of the L* execution loop (M1d).
|
|
25
45
|
///
|
|
26
46
|
/// Schedulability (`Ready/Running/Blocked/Suspended/Done`) is no longer carried here — it lives
|
|
@@ -484,6 +504,22 @@ impl LoopStateMachine {
|
|
|
484
504
|
self.ctx.record_activity(now_ms);
|
|
485
505
|
}
|
|
486
506
|
|
|
507
|
+
// 2b: record this turn's tool activity into the task-state recency log (meta-tools
|
|
508
|
+
// filtered inside). The State-turn footer renders it as "just did: …" + a forward
|
|
509
|
+
// nudge / STOP, so progress is kernel-derived and never depends on the model
|
|
510
|
+
// remembering to call `update_plan`. Tool *names* live only on the request (results
|
|
511
|
+
// carry call_id only), so this is the turn to capture them.
|
|
512
|
+
//
|
|
513
|
+
// Capture name AND a compact arg digest: the no-progress STOP keys on whether the
|
|
514
|
+
// SAME call repeats, and a legit loop (same tool, DIFFERENT args — e.g. processing 20
|
|
515
|
+
// items) is real progress, not a stall. Keying on the name alone false-positives those
|
|
516
|
+
// loops; including args distinguishes "step(n=1), step(n=2)…" from a true repeat.
|
|
517
|
+
let action_sigs: Vec<(String, String)> = calls
|
|
518
|
+
.iter()
|
|
519
|
+
.map(|c| (c.name.to_string(), compact_tool_args(&c.arguments)))
|
|
520
|
+
.collect();
|
|
521
|
+
self.ctx.note_tool_actions(&action_sigs);
|
|
522
|
+
|
|
487
523
|
match self.gate_tool_calls(&calls) {
|
|
488
524
|
GateToolOutcome::Blocked(action) => return action,
|
|
489
525
|
GateToolOutcome::Suspended => return LoopAction::AwaitingResume,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|