tamfis-code 0.6.1__tar.gz → 0.6.2__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.
Files changed (127) hide show
  1. {tamfis_code-0.6.1/tamfis_code.egg-info → tamfis_code-0.6.2}/PKG-INFO +1 -1
  2. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/pyproject.toml +1 -1
  3. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/__init__.py +1 -1
  4. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/model_registry.py +18 -0
  5. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/providers.py +12 -17
  6. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/render.py +35 -17
  7. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/runner.py +8 -1
  8. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/runner_local.py +17 -1
  9. {tamfis_code-0.6.1 → tamfis_code-0.6.2/tamfis_code.egg-info}/PKG-INFO +1 -1
  10. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_fake_tool_detection.py +11 -0
  11. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_routing.py +11 -9
  12. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_tamfis_code_render.py +26 -0
  13. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/MANIFEST.in +0 -0
  14. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/README.md +0 -0
  15. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/USAGE_INSTALL_RELEASE.md +0 -0
  16. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/setup.cfg +0 -0
  17. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/__main__.py +0 -0
  18. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/agent_definitions.py +0 -0
  19. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/agents.py +0 -0
  20. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/api_client.py +0 -0
  21. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/cli.py +0 -0
  22. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/clipboard.py +0 -0
  23. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/completion.py +0 -0
  24. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/config.py +0 -0
  25. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/custom_commands.py +0 -0
  26. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/doctor.py +0 -0
  27. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/enforcer.py +0 -0
  28. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/evidence.py +0 -0
  29. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/hooks.py +0 -0
  30. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/indexer.py +0 -0
  31. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/instructions.py +0 -0
  32. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/interactive.py +0 -0
  33. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/live_input.py +0 -0
  34. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/local_chat.py +0 -0
  35. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/local_tools.py +0 -0
  36. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/mcp.py +0 -0
  37. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/mcp_stdio_server.py +0 -0
  38. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/metrics.py +0 -0
  39. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/openhands/__init__.py +0 -0
  40. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/openhands/agent.py +0 -0
  41. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/openhands/agent_server.py +0 -0
  42. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/openhands/automation.py +0 -0
  43. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/openhands/compat.py +0 -0
  44. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/openhands/conversation.py +0 -0
  45. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/openhands/delegation.py +0 -0
  46. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/openhands/events.py +0 -0
  47. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/openhands/leases.py +0 -0
  48. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/openhands/security.py +0 -0
  49. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/openhands/skills.py +0 -0
  50. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/openhands/tools.py +0 -0
  51. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/openhands/workspace.py +0 -0
  52. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/orchestrator/__init__.py +0 -0
  53. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/orchestrator/context.py +0 -0
  54. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/orchestrator/engine.py +0 -0
  55. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/orchestrator/planner.py +0 -0
  56. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/orchestrator/protocols.py +0 -0
  57. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/orchestrator/validator.py +0 -0
  58. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/project.py +0 -0
  59. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/provider_protocols.py +0 -0
  60. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/pty.py +0 -0
  61. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/references.py +0 -0
  62. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/routing.py +0 -0
  63. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/safety.py +0 -0
  64. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/screenshot.py +0 -0
  65. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/sessions.py +0 -0
  66. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/state.py +0 -0
  67. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/swarm.py +0 -0
  68. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/tasks.py +0 -0
  69. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/tool_policy.py +0 -0
  70. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code/workspace.py +0 -0
  71. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code.egg-info/SOURCES.txt +0 -0
  72. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code.egg-info/dependency_links.txt +0 -0
  73. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code.egg-info/entry_points.txt +0 -0
  74. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code.egg-info/requires.txt +0 -0
  75. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tamfis_code.egg-info/top_level.txt +0 -0
  76. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_agent_definitions.py +0 -0
  77. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_agents.py +0 -0
  78. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_agents_delegation.py +0 -0
  79. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_build_installation.py +0 -0
  80. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_cli_commands.py +0 -0
  81. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_clipboard.py +0 -0
  82. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_completion.py +0 -0
  83. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_context_rollover.py +0 -0
  84. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_custom_commands.py +0 -0
  85. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_cwd_validation.py +0 -0
  86. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_doctor_session_diagnostics.py +0 -0
  87. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_enforcer.py +0 -0
  88. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_find_references.py +0 -0
  89. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_hooks.py +0 -0
  90. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_indexer.py +0 -0
  91. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_instructions.py +0 -0
  92. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_integration_new.py +0 -0
  93. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_interactive_standalone.py +0 -0
  94. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_live_input.py +0 -0
  95. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_local_pty.py +0 -0
  96. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_mcp.py +0 -0
  97. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_mcp_search_bounds.py +0 -0
  98. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_mcp_standalone_degradation.py +0 -0
  99. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_mcp_stdio_server.py +0 -0
  100. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_metrics.py +0 -0
  101. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_openhands_runtime.py +0 -0
  102. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_openrouter_cost_routing.py +0 -0
  103. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_orchestrator.py +0 -0
  104. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_plan_mode_gate.py +0 -0
  105. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_plan_steps_and_attempts.py +0 -0
  106. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_provider_protocols.py +0 -0
  107. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_reasoning_effort_capability.py +0 -0
  108. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_reasoning_plan.py +0 -0
  109. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_references.py +0 -0
  110. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_render_task_diagnostics.py +0 -0
  111. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_runner_helpers.py +0 -0
  112. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_runner_local.py +0 -0
  113. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_safety.py +0 -0
  114. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_safety_manifest.py +0 -0
  115. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_sessions.py +0 -0
  116. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_swarm.py +0 -0
  117. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_tamfis_code_api_client.py +0 -0
  118. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_tamfis_code_approval.py +0 -0
  119. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_tamfis_code_approval_flow.py +0 -0
  120. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_tamfis_code_config.py +0 -0
  121. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_tamfis_code_intent.py +0 -0
  122. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_tamfis_code_repl_exit.py +0 -0
  123. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_tamfis_code_tasks.py +0 -0
  124. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_tamfis_code_workspace.py +0 -0
  125. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_version_consistency.py +0 -0
  126. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_workspace_discovery_gaps.py +0 -0
  127. {tamfis_code-0.6.1 → tamfis_code-0.6.2}/tests/test_workspace_scope.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tamfis-code
3
- Version: 0.6.1
3
+ Version: 0.6.2
4
4
  Summary: TamfisGPT Code -- portable subscription-backed terminal coding agent with local tools, PTY, plans, queues, and approvals
5
5
  Project-URL: Homepage, https://github.com/tamfitronics/tamfis-code
6
6
  Project-URL: Repository, https://github.com/tamfitronics/tamfis-code
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tamfis-code"
3
- version = "0.6.1"
3
+ version = "0.6.2"
4
4
  description = "TamfisGPT Code -- portable subscription-backed terminal coding agent with local tools, PTY, plans, queues, and approvals"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -17,7 +17,7 @@ See docs/REMOTE_AGENT_MASTER_SPEC.md, Phase 21, for the original --remote
17
17
  architecture's spec.
18
18
  """
19
19
 
20
- __version__ = "0.6.1"
20
+ __version__ = "0.6.2"
21
21
 
22
22
  # Bumped whenever a CLI release requires a minimum backend Remote API
23
23
  # contract version. Only meaningful for --remote; the standalone path has no
@@ -59,6 +59,24 @@ MODELS: dict[str, ModelRecord] = {
59
59
  ModelCapabilities(vision=True, long_context=True), 1000000,
60
60
  ("long_context_review", "repository_search", "vision_assisted_coding"), "high", "medium",
61
61
  ),
62
+ "qwen/qwen3-coder": ModelRecord(
63
+ "qwen/qwen3-coder", "openrouter",
64
+ ModelCapabilities(parallel_tool_calls=True, long_context=True), 256000,
65
+ ("repository_audit", "multi_file_edit", "debugging", "planning", "tool_heavy_execution"),
66
+ "frontier", "medium",
67
+ ),
68
+ "deepseek/deepseek-chat-v3-0324": ModelRecord(
69
+ "deepseek/deepseek-chat-v3-0324", "openrouter",
70
+ ModelCapabilities(parallel_tool_calls=True, long_context=True), 128000,
71
+ ("repository_search", "multi_file_edit", "debugging", "planning"),
72
+ "high", "low",
73
+ ),
74
+ "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning": ModelRecord(
75
+ "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning", "nvidia",
76
+ ModelCapabilities(parallel_tool_calls=True, long_context=True), 128000,
77
+ ("repository_audit", "multi_file_edit", "debugging", "planning", "tool_heavy_execution"),
78
+ "frontier", "medium",
79
+ ),
62
80
  "Qwen/Qwen2.5-7B-Instruct": ModelRecord(
63
81
  "Qwen/Qwen2.5-7B-Instruct", "hf",
64
82
  ModelCapabilities(long_context=False), 32768,
@@ -226,22 +226,14 @@ class ProviderManager:
226
226
  name="NVIDIA NIM",
227
227
  base_url="https://integrate.api.nvidia.com/v1",
228
228
  api_key_env="NVIDIA_API_KEY",
229
- # meta/llama-3.1-70b-instruct is the stable coding/tool default.
230
- # Nemotron remains selectable, but live streams have produced
231
- # corrupted lexical loops that pass an ordinary finish-reason
232
- # check. moonshotai/kimi-k2.6 was tried as the default before
233
- # this -- confirmed live (real HTTP calls, real account) that
234
- # it's a hard, permanent 404 ("Function ... Not found for
235
- # account"), not a transient outage: it's listed in NVIDIA's
236
- # general /v1/models catalog but this account (and, going by
237
- # the entitlement-shaped error, likely most ordinary NVIDIA API
238
- # keys -- third-party marketplace models commonly need separate
239
- # enablement NVIDIA's own first-party models don't) has no
240
- # actual deployment access to it. A default that 404s outright
241
- # for typical accounts is strictly worse than nemotron's
242
- # occasional loop -- confirmed the replacement actually works
243
- # live before switching to it.
244
- default_model="meta/llama-3.1-70b-instruct",
229
+ # The plain Llama instruct route can answer fluently but has
230
+ # repeatedly narrated/fabricated local tool results in this CLI.
231
+ # This NVIDIA reasoning model was verified to return genuine
232
+ # tool_calls and to accept reasoning parameters without the
233
+ # indefinite stream stall seen on the plain instruct models.
234
+ # Keep the older models selectable for accounts where this route
235
+ # is not enabled, but do not make them the automatic coding path.
236
+ default_model="nvidia/nemotron-3-nano-omni-30b-a3b-reasoning",
245
237
  models=[
246
238
  "nvidia/nemotron-3-super-120b-a12b",
247
239
  "nvidia/nemotron-3-ultra-550b-a55b",
@@ -303,7 +295,10 @@ class ProviderManager:
303
295
  name="OpenRouter",
304
296
  base_url="https://openrouter.ai/api/v1",
305
297
  api_key_env="OPENROUTER_API_KEY",
306
- default_model="google/gemini-2.5-flash",
298
+ # Paid coding/repair work gets a coder-tuned route. Routine work
299
+ # still uses the free model below, so this does not turn every
300
+ # inspection into a paid request.
301
+ default_model="qwen/qwen3-coder",
307
302
  # Used for plain chat/Q&A/inspection -- anything that isn't
308
303
  # deep research or genuinely demanding coding/analysis work --
309
304
  # so routine turns don't spend paid OpenRouter credits at all.
@@ -432,6 +432,32 @@ class StreamRenderer:
432
432
  if self._live is not None:
433
433
  self._live.update(self._build_status())
434
434
 
435
+ def _print_plan_snapshot(
436
+ self, items: list[dict[str, Any]], *, title: str,
437
+ assumptions: Optional[list[Any]] = None, risks: Optional[list[Any]] = None,
438
+ ) -> None:
439
+ """Print an authoritative plan snapshot to scrollback.
440
+
441
+ Rich Live is intentionally transient for the spinner, so it cannot
442
+ be the source of truth for a plan users need to follow. Every plan
443
+ creation and every status transition gets a durable snapshot here.
444
+ """
445
+ table = Table.grid(padding=(0, 1))
446
+ table.add_column(width=4, justify="right", style="cyan")
447
+ table.add_column(ratio=1)
448
+ for index, item in enumerate(items, start=1):
449
+ status = str(item.get("status") or "pending")
450
+ marker = "✓" if status == "completed" else (
451
+ "✗" if status == "failed" else ("▶" if status == "in_progress" else "○")
452
+ )
453
+ table.add_row(f"{index}. {marker}", f"{item.get('step') or ''} · {status}")
454
+ body: list[Any] = [table]
455
+ if assumptions:
456
+ body.extend([Text("Assumptions", style="bold"), Text(" • " + "\n • ".join(map(str, assumptions)))])
457
+ if risks:
458
+ body.extend([Text("Risks", style="bold yellow"), Text(" • " + "\n • ".join(map(str, risks)))])
459
+ self.console.print(Panel(Group(*body), title=title, border_style="cyan", expand=False))
460
+
435
461
  def _stop_live(self) -> None:
436
462
  """End the progress display before ordinary streamed output begins."""
437
463
  if self._live is not None:
@@ -574,14 +600,15 @@ class StreamRenderer:
574
600
  return
575
601
 
576
602
  if event_type == "plan_step_progress":
577
- # In-place step-status refresh (see engine.py's
578
- # _sync_plan_progress) -- unlike plan_created, this never
579
- # reprints the plan banner or touches the spinner phase; it
580
- # only updates the live step markers, silently, every round.
603
+ # Update both the live view and durable scrollback. The live view
604
+ # may already have been stopped when assistant output began, so
605
+ # refreshing it alone makes progress appear to disappear.
606
+ self._close_assistant()
581
607
  items = payload.get("items") if isinstance(payload.get("items"), list) else []
582
608
  if items:
583
609
  self._plan_steps = [item for item in items if isinstance(item, dict) and item.get("status") != "context"]
584
610
  self._refresh_live()
611
+ self._print_plan_snapshot(self._plan_steps, title=payload.get("title") or "Plan progress")
585
612
  return
586
613
 
587
614
  if event_type == "plan_created":
@@ -595,21 +622,12 @@ class StreamRenderer:
595
622
  # Rich's TTY Live region is transient and is stopped when
596
623
  # assistant output begins. Always print a durable snapshot;
597
624
  # otherwise the plan disappears at execution start.
598
- table = Table.grid(padding=(0, 1))
599
- table.add_column(width=4, justify="right", style="cyan")
600
- table.add_column(ratio=1)
601
- for index, item in enumerate(self._plan_steps, start=1):
602
- status = item.get("status")
603
- marker = "✓" if status == "completed" else ("▶" if status == "in_progress" else "○")
604
- table.add_row(f"{index}. {marker}", str(item.get("step") or ""))
605
625
  assumptions = payload.get("assumptions") or []
606
626
  risks = payload.get("risks") or []
607
- body: list[Any] = [table]
608
- if assumptions:
609
- body.extend([Text("Assumptions", style="bold"), Text(" • " + "\n • ".join(map(str, assumptions)))])
610
- if risks:
611
- body.extend([Text("Risks", style="bold yellow"), Text(" • " + "\n • ".join(map(str, risks)))])
612
- self.console.print(Panel(Group(*body), title=payload.get("title") or "Execution plan", border_style="cyan", expand=False))
627
+ self._print_plan_snapshot(
628
+ self._plan_steps, title=payload.get("title") or "Execution plan",
629
+ assumptions=assumptions, risks=risks,
630
+ )
613
631
  return
614
632
  if stage == "tool_execution":
615
633
  match = _TOOL_ANNOUNCE_RE.search(content)
@@ -546,7 +546,7 @@ async def _stream_task(
546
546
  continue
547
547
 
548
548
  phase_by_event = {
549
- "plan_created": "plan", "tool_call_requested": "execute",
549
+ "plan_created": "plan", "plan_step_progress": "execute", "tool_call_requested": "execute",
550
550
  "command_started": "execute", "file_mutation": "execute",
551
551
  "approval_required": "waiting_for_approval", "task_diagnostics": "validate",
552
552
  "ai_task_completed": "report", "ai_task_failed": "report",
@@ -566,6 +566,13 @@ async def _stream_task(
566
566
  if state.active_plan_id:
567
567
  local_state.update_plan_steps(session_id, state.active_plan_id, items)
568
568
 
569
+ if event_type == "plan_step_progress":
570
+ items = payload.get("items") if isinstance(payload.get("items"), list) else []
571
+ last_plan_items = items
572
+ state = local_state.get_session_state(session_id)
573
+ if state.active_plan_id and items:
574
+ local_state.update_plan_steps(session_id, state.active_plan_id, items)
575
+
569
576
  if event_type == "file_mutation":
570
577
  state = local_state.get_session_state(session_id)
571
578
  mutation_id = str(payload.get("mutation_id") or "")
@@ -96,6 +96,18 @@ NARRATED_TOOL_CORRECTION = (
96
96
  "Only provide a final answer after the requested inspection or action actually ran."
97
97
  )
98
98
 
99
+ # Keep the user-facing completion readable even when a provider streams a
100
+ # long audit. Plans and live progress already have their own durable renderer
101
+ # panels; the model must not recreate those as a second, drifting bullet list.
102
+ FINAL_RESPONSE_FORMAT_INSTRUCTION = (
103
+ "When you finish, write a concise evidence-backed response using exactly these "
104
+ "sections when applicable: Summary, Changes, Verification, and Remaining issues. "
105
+ "Use one short bullet per concrete fact under a section; do not emit nested or "
106
+ "unrelated bullet lists, do not repeat the execution plan, and do not claim a "
107
+ "tool ran unless a real tool result appears in the conversation. If no files "
108
+ "changed, say so plainly under Summary."
109
+ )
110
+
99
111
  # Same one-chance-then-fallback shape as narrated tool intent, for the
100
112
  # distinct failure of giving up outright instead of narrating.
101
113
  MAX_CAPITULATION_RETRIES_PER_PROVIDER = 1
@@ -1947,7 +1959,7 @@ def _looks_like_capitulation(text: str) -> bool:
1947
1959
  # same way: refuse the round, ask for a real tool call, and fall back
1948
1960
  # across providers the same one-chance-then-switch way as narrated intent.
1949
1961
  _FABRICATED_TOOL_RESULT_RE = re.compile(
1950
- r"the\s+\w+\s+tool\s+(?:has\s+)?(?:found|returned|shows?|revealed|indicates?)|"
1962
+ r"the\s+\w+\s+tool\s+(?:has\s+)?(?:found|returned|executed|ran|shows?|revealed|indicates?)|"
1951
1963
  r"(?:the\s+)?results?\s+(?:suggest|indicate)s?\b|"
1952
1964
  r"i\s+encountered\s+an?\s+(?:access|permission)\s+issue|"
1953
1965
  r"(?:here\s+are\s+the\s+)?allowed\s+directories\s+(?:are|include|where\s+i\s+can)|"
@@ -2929,6 +2941,10 @@ async def run_local_agent_turn(
2929
2941
  # it survives later compaction and remains authoritative in every round.
2930
2942
  insert_at = 1 if working_messages and working_messages[0].get("role") == "system" else 0
2931
2943
  working_messages.insert(insert_at, scope_message)
2944
+ working_messages.insert(insert_at + 1, {
2945
+ "role": "system",
2946
+ "content": FINAL_RESPONSE_FORMAT_INSTRUCTION,
2947
+ })
2932
2948
  if resumed_from_checkpoint or resumed_from_legacy or _requests_autonomous_execution(incoming_objective):
2933
2949
  working_messages.insert(insert_at + 1, {"role": "system", "content": RESUME_EXECUTION_INSTRUCTION})
2934
2950
  checkpoint_mode = str(prior_checkpoint.get("mode") or ("read_only" if read_only else "execute"))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tamfis-code
3
- Version: 0.6.1
3
+ Version: 0.6.2
4
4
  Summary: TamfisGPT Code -- portable subscription-backed terminal coding agent with local tools, PTY, plans, queues, and approvals
5
5
  Project-URL: Homepage, https://github.com/tamfitronics/tamfis-code
6
6
  Project-URL: Repository, https://github.com/tamfitronics/tamfis-code
@@ -25,6 +25,7 @@ from tamfis_code.runner_local import (
25
25
  _looks_like_narrated_tool_intent,
26
26
  _requests_autonomous_execution,
27
27
  _requests_no_confirmation,
28
+ _looks_like_fabricated_tool_result,
28
29
  )
29
30
 
30
31
 
@@ -55,6 +56,16 @@ class FakeToolCallDetectionTests(unittest.TestCase):
55
56
  def test_unregistered_function_looking_text_is_not_flagged(self):
56
57
  self.assertFalse(_looks_like_fake_tool_call("def calculate_total(items): return sum(items)"))
57
58
 
59
+ def test_transcript_style_list_directory_claim_is_fabricated_result(self):
60
+ self.assertTrue(_looks_like_fabricated_tool_result(
61
+ "The list_directory tool has found the following files and directories."
62
+ ))
63
+
64
+ def test_transcript_style_execute_command_claim_is_fabricated_result(self):
65
+ self.assertTrue(_looks_like_fabricated_tool_result(
66
+ "The execute_command tool has executed the npm test command."
67
+ ))
68
+
58
69
 
59
70
  class NarratedToolIntentDetectionTests(unittest.TestCase):
60
71
  def test_let_me_check_is_detected(self):
@@ -96,17 +96,13 @@ def test_openrouter_default_is_not_openai_family():
96
96
  assert all(not model.startswith("openai/") for model in cfg.models)
97
97
 
98
98
 
99
- def test_nvidia_default_model_is_not_loop_prone_nemotron_or_unentitled_kimi():
100
- # Live-reported regression: moonshotai/kimi-k2.6 was tried as the
101
- # default to avoid nemotron's occasional lexical-loop corruption, but
102
- # confirmed live (real HTTP calls against a real account) to be a hard,
103
- # permanent 404 -- listed in NVIDIA's general /v1/models catalog but
104
- # with no actual deployment access on an ordinary API key. A default
105
- # that 404s outright is strictly worse than nemotron's occasional loop.
99
+ def test_nvidia_default_model_is_tool_capable_and_not_unentitled_kimi():
100
+ # The plain Llama route is fluent but has been observed fabricating local
101
+ # tool results. Use the verified NVIDIA reasoning/tool route instead;
102
+ # never use the account-unentitled Kimi route as the default.
106
103
  default_model = ProviderManager.PROVIDERS[ProviderType.NVIDIA].default_model
107
- assert default_model != "nvidia/nemotron-3-super-120b-a12b"
108
104
  assert default_model != "moonshotai/kimi-k2.6"
109
- assert default_model == "meta/llama-3.1-70b-instruct"
105
+ assert default_model == "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning"
110
106
 
111
107
 
112
108
  def test_kimi_k2_6_is_still_selectable_on_openrouter_and_hf():
@@ -126,6 +122,12 @@ def test_kimi_k2_6_is_still_selectable_on_openrouter_and_hf():
126
122
  assert ProviderManager.PROVIDERS[ProviderType.HF].default_model != "moonshotai/Kimi-K2.6"
127
123
 
128
124
 
125
+ def test_openrouter_paid_coding_default_is_qwen_coder():
126
+ config = ProviderManager.PROVIDERS[ProviderType.OPENROUTER]
127
+ assert config.default_model == "qwen/qwen3-coder"
128
+ assert config.default_model in config.models
129
+
130
+
129
131
  def test_remote_fallback_candidates_stay_in_policy_order():
130
132
  manager = _manager_with(ProviderType.NVIDIA, ProviderType.OPENROUTER, ProviderType.HF)
131
133
  # Use a tool-requiring task that does not require long context, so HF is
@@ -396,6 +396,32 @@ class StreamRendererTests(unittest.TestCase):
396
396
  renderer.finish()
397
397
  self.assertIn("inspect mission pipeline", console.file.getvalue())
398
398
 
399
+ def test_plan_progress_is_durable_after_live_panel_stops(self):
400
+ console = Console(file=StringIO(), no_color=True, width=200, force_terminal=True)
401
+ renderer = StreamRenderer(console)
402
+ try:
403
+ renderer.handle_event({
404
+ "event_type": "plan_created",
405
+ "payload": {"title": "Plan", "items": [
406
+ {"step": "Inspect", "status": "pending"},
407
+ {"step": "Fix", "status": "pending"},
408
+ ]},
409
+ })
410
+ renderer.handle_event({"event_type": "assistant_delta", "payload": {"content": "Working."}})
411
+ renderer.handle_event({
412
+ "event_type": "plan_step_progress",
413
+ "payload": {"items": [
414
+ {"step": "Inspect", "status": "completed"},
415
+ {"step": "Fix", "status": "in_progress"},
416
+ ]},
417
+ })
418
+ finally:
419
+ renderer.finish()
420
+ output = console.file.getvalue()
421
+ self.assertIn("Plan progress", output)
422
+ self.assertIn("Inspect · completed", output)
423
+ self.assertIn("Fix · in_progress", output)
424
+
399
425
  def test_approval_gate_suspends_and_resumes_the_live_status_line(self):
400
426
  # Regression guard: Rich's Live redraws on its own timer, independent
401
427
  # of a blocking console.input() approval prompt -- without suspending
File without changes
File without changes
File without changes