tamfis-code 0.6.3__tar.gz → 0.6.5__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.3/tamfis_code.egg-info → tamfis_code-0.6.5}/PKG-INFO +1 -1
  2. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/pyproject.toml +1 -1
  3. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/__init__.py +1 -1
  4. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/live_input.py +7 -2
  5. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/runner_local.py +50 -2
  6. {tamfis_code-0.6.3 → tamfis_code-0.6.5/tamfis_code.egg-info}/PKG-INFO +1 -1
  7. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_live_input.py +14 -0
  8. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_runner_local.py +23 -0
  9. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_workspace_scope.py +11 -0
  10. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/MANIFEST.in +0 -0
  11. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/README.md +0 -0
  12. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/USAGE_INSTALL_RELEASE.md +0 -0
  13. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/setup.cfg +0 -0
  14. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/__main__.py +0 -0
  15. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/agent_definitions.py +0 -0
  16. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/agents.py +0 -0
  17. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/api_client.py +0 -0
  18. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/cli.py +0 -0
  19. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/clipboard.py +0 -0
  20. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/completion.py +0 -0
  21. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/config.py +0 -0
  22. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/custom_commands.py +0 -0
  23. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/doctor.py +0 -0
  24. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/enforcer.py +0 -0
  25. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/evidence.py +0 -0
  26. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/hooks.py +0 -0
  27. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/indexer.py +0 -0
  28. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/instructions.py +0 -0
  29. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/interactive.py +0 -0
  30. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/local_chat.py +0 -0
  31. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/local_tools.py +0 -0
  32. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/mcp.py +0 -0
  33. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/mcp_stdio_server.py +0 -0
  34. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/metrics.py +0 -0
  35. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/model_registry.py +0 -0
  36. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/openhands/__init__.py +0 -0
  37. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/openhands/agent.py +0 -0
  38. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/openhands/agent_server.py +0 -0
  39. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/openhands/automation.py +0 -0
  40. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/openhands/compat.py +0 -0
  41. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/openhands/conversation.py +0 -0
  42. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/openhands/delegation.py +0 -0
  43. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/openhands/events.py +0 -0
  44. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/openhands/leases.py +0 -0
  45. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/openhands/security.py +0 -0
  46. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/openhands/skills.py +0 -0
  47. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/openhands/tools.py +0 -0
  48. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/openhands/workspace.py +0 -0
  49. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/orchestrator/__init__.py +0 -0
  50. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/orchestrator/context.py +0 -0
  51. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/orchestrator/engine.py +0 -0
  52. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/orchestrator/planner.py +0 -0
  53. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/orchestrator/protocols.py +0 -0
  54. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/orchestrator/validator.py +0 -0
  55. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/project.py +0 -0
  56. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/provider_protocols.py +0 -0
  57. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/providers.py +0 -0
  58. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/pty.py +0 -0
  59. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/references.py +0 -0
  60. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/render.py +0 -0
  61. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/routing.py +0 -0
  62. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/runner.py +0 -0
  63. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/safety.py +0 -0
  64. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/screenshot.py +0 -0
  65. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/sessions.py +0 -0
  66. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/state.py +0 -0
  67. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/swarm.py +0 -0
  68. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/tasks.py +0 -0
  69. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/tool_policy.py +0 -0
  70. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code/workspace.py +0 -0
  71. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code.egg-info/SOURCES.txt +0 -0
  72. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code.egg-info/dependency_links.txt +0 -0
  73. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code.egg-info/entry_points.txt +0 -0
  74. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code.egg-info/requires.txt +0 -0
  75. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tamfis_code.egg-info/top_level.txt +0 -0
  76. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_agent_definitions.py +0 -0
  77. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_agents.py +0 -0
  78. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_agents_delegation.py +0 -0
  79. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_build_installation.py +0 -0
  80. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_cli_commands.py +0 -0
  81. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_clipboard.py +0 -0
  82. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_completion.py +0 -0
  83. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_context_rollover.py +0 -0
  84. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_custom_commands.py +0 -0
  85. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_cwd_validation.py +0 -0
  86. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_doctor_session_diagnostics.py +0 -0
  87. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_enforcer.py +0 -0
  88. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_fake_tool_detection.py +0 -0
  89. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_find_references.py +0 -0
  90. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_hooks.py +0 -0
  91. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_indexer.py +0 -0
  92. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_instructions.py +0 -0
  93. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_integration_new.py +0 -0
  94. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_interactive_standalone.py +0 -0
  95. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_local_pty.py +0 -0
  96. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_mcp.py +0 -0
  97. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_mcp_search_bounds.py +0 -0
  98. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_mcp_standalone_degradation.py +0 -0
  99. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_mcp_stdio_server.py +0 -0
  100. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_metrics.py +0 -0
  101. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_openhands_runtime.py +0 -0
  102. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_openrouter_cost_routing.py +0 -0
  103. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_orchestrator.py +0 -0
  104. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_plan_mode_gate.py +0 -0
  105. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_plan_steps_and_attempts.py +0 -0
  106. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_provider_protocols.py +0 -0
  107. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_reasoning_effort_capability.py +0 -0
  108. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_reasoning_plan.py +0 -0
  109. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_references.py +0 -0
  110. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_render_task_diagnostics.py +0 -0
  111. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_routing.py +0 -0
  112. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_runner_helpers.py +0 -0
  113. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_safety.py +0 -0
  114. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_safety_manifest.py +0 -0
  115. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_sessions.py +0 -0
  116. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_swarm.py +0 -0
  117. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_tamfis_code_api_client.py +0 -0
  118. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_tamfis_code_approval.py +0 -0
  119. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_tamfis_code_approval_flow.py +0 -0
  120. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_tamfis_code_config.py +0 -0
  121. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_tamfis_code_intent.py +0 -0
  122. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_tamfis_code_render.py +0 -0
  123. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_tamfis_code_repl_exit.py +0 -0
  124. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_tamfis_code_tasks.py +0 -0
  125. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_tamfis_code_workspace.py +0 -0
  126. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_version_consistency.py +0 -0
  127. {tamfis_code-0.6.3 → tamfis_code-0.6.5}/tests/test_workspace_discovery_gaps.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tamfis-code
3
- Version: 0.6.3
3
+ Version: 0.6.5
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.3"
3
+ version = "0.6.5"
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.3"
20
+ __version__ = "0.6.5"
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
@@ -125,12 +125,17 @@ class LiveInputListener:
125
125
 
126
126
  def _dispatch(self) -> None:
127
127
  buf = bytes(self._buf)
128
- if buf == _CTRL_T:
128
+ # os.read() is allowed to return more than one byte. A keypress can
129
+ # therefore arrive together with terminal noise or a paste prefix;
130
+ # equality-only matching silently discarded Ctrl+T in that case and
131
+ # made the in-task editor appear broken. Consume the control byte
132
+ # wherever it occurs, while keeping the editor single-flight.
133
+ if _CTRL_T in buf:
129
134
  self._buf.clear()
130
135
  if self._interject_task is None or self._interject_task.done():
131
136
  self._interject_task = asyncio.ensure_future(self._interject())
132
137
  return
133
- if buf == _SHIFT_TAB:
138
+ if _SHIFT_TAB in buf:
134
139
  self._buf.clear()
135
140
  self._cycle_mode()
136
141
  return
@@ -275,7 +275,11 @@ def _objective_excluded_names(workspace_root: str, objective: str) -> set[str]:
275
275
  def _detect_workspace_scope(workspace_root: str, objective: str) -> list[Path]:
276
276
  """Resolve the smallest useful set of project roots for this turn.
277
277
 
278
- Exact directory names mentioned by the user win. A request containing
278
+ Exact directory names mentioned by the user win. Explicit absolute paths
279
+ in the objective are also authoritative task-local scope expansions. This
280
+ matters when the CLI is launched from a parent/admin checkout but the
281
+ user names a sibling project such as `/home/tamfisseo/package.json`.
282
+ A request containing
279
283
  "stack" under /home/tamfisgpt selects the canonical TamfisGPT stack roots.
280
284
  Otherwise, when the workspace itself is a project, it remains the scope;
281
285
  when it is merely a parent directory, only immediate child projects are
@@ -289,6 +293,32 @@ def _detect_workspace_scope(workspace_root: str, objective: str) -> list[Path]:
289
293
  root = Path(workspace_root).expanduser().resolve()
290
294
  lowered = (objective or "").lower()
291
295
 
296
+ # CWD is the default boundary, but an explicitly named existing absolute
297
+ # project path is an intentional request to inspect that project. Resolve
298
+ # package/manifests to their containing project directory and keep the
299
+ # expansion bounded to the project marker -- never treat an arbitrary
300
+ # absolute path or filesystem parent as a workspace.
301
+ explicit_roots: list[Path] = []
302
+ for raw_path in re.findall(r"(?<![\w.-])/(?:[A-Za-z0-9._~+\-]+/)*[A-Za-z0-9._~+\-]+", objective or ""):
303
+ candidate = Path(raw_path.rstrip(".,;:)]}"))
304
+ try:
305
+ candidate = candidate.resolve()
306
+ if candidate.is_file():
307
+ candidate = candidate.parent
308
+ if candidate.is_dir() and _is_project_root(candidate):
309
+ explicit_roots.append(candidate)
310
+ except OSError:
311
+ continue
312
+ if explicit_roots:
313
+ deduped: list[Path] = []
314
+ seen_explicit: set[str] = set()
315
+ for candidate in explicit_roots:
316
+ key = str(candidate)
317
+ if key not in seen_explicit:
318
+ seen_explicit.add(key)
319
+ deduped.append(candidate)
320
+ return deduped
321
+
292
322
  try:
293
323
  children = [child for child in root.iterdir() if child.is_dir()]
294
324
  except OSError:
@@ -1153,6 +1183,14 @@ def _legacy_resume_messages(state, incoming_objective: str) -> tuple[list[dict[s
1153
1183
  # the tail of the accumulated text, and triggers within a couple KB of a
1154
1184
  # genuine loop, long before the content can grow large enough to matter.
1155
1185
  _DEGENERATE_REPETITION_RE = re.compile(r"(.{6,200}?)\1{4,}", re.DOTALL)
1186
+ # A separate guard for a common context-confusion failure: the model starts
1187
+ # replaying a synthetic transcript ("then the user said ... then the
1188
+ # assistant responded ...") instead of answering the current task. The
1189
+ # individual paragraphs are often too long or slightly varied for the
1190
+ # contiguous backreference above, so count the unmistakable transcript
1191
+ # markers in the rolling tail.
1192
+ _CONVERSATION_ECHO_USER_MARKER = re.compile(r"\bthen\s+the\s+user\s+said\b", re.IGNORECASE)
1193
+ _CONVERSATION_ECHO_ASSISTANT_MARKER = re.compile(r"\bthen\s+the\s+assistant\s+(?:said|responded)\b", re.IGNORECASE)
1156
1194
  _DEGENERATE_REPETITION_TAIL_WINDOW = 8_000
1157
1195
  _STREAM_QUALITY_LAG_CHARS = 1_024
1158
1196
  _CORRUPTED_STREAM_MIN_CHARS = 600
@@ -2247,6 +2285,12 @@ async def _stream_one_completion(
2247
2285
  if _DEGENERATE_REPETITION_RE.search(tail_buffer):
2248
2286
  quality_failure_reason = "degenerate_repetition"
2249
2287
  break
2288
+ if (
2289
+ len(_CONVERSATION_ECHO_USER_MARKER.findall(tail_buffer)) >= 3
2290
+ and len(_CONVERSATION_ECHO_ASSISTANT_MARKER.findall(tail_buffer)) >= 3
2291
+ ):
2292
+ quality_failure_reason = "conversation_echo"
2293
+ break
2250
2294
  if _corrupted_lexical_stream_index(tail_buffer) is not None:
2251
2295
  quality_failure_reason = "corrupted_output"
2252
2296
  break
@@ -2278,6 +2322,8 @@ async def _stream_one_completion(
2278
2322
  diagnosis = (
2279
2323
  "Detected corrupted provider token output; discarding it and requesting a clean route."
2280
2324
  if quality_failure_reason == "corrupted_output"
2325
+ else "Detected a repeated conversation transcript; discarding it and requesting a clean route."
2326
+ if quality_failure_reason == "conversation_echo"
2281
2327
  else "Detected the model repeating itself in a loop; discarding that completion."
2282
2328
  )
2283
2329
  renderer.handle_event({
@@ -3604,7 +3650,7 @@ async def run_local_agent_turn(
3604
3650
  renderer.handle_event({"event_type": "ai_task_failed", "payload": {"error": message}})
3605
3651
  return TaskOutcome(status="failed", error=message)
3606
3652
 
3607
- if finish_reason in {"degenerate_repetition", "corrupted_output"}:
3653
+ if finish_reason in {"degenerate_repetition", "conversation_echo", "corrupted_output"}:
3608
3654
  failed_quality_provider = resolved_provider
3609
3655
  quality_failed_providers.add(failed_quality_provider)
3610
3656
  checkpoint_partial_parts.clear()
@@ -3647,6 +3693,8 @@ async def run_local_agent_turn(
3647
3693
  reason_text = (
3648
3694
  "corrupted token output"
3649
3695
  if finish_reason == "corrupted_output"
3696
+ else "a repeated conversation transcript"
3697
+ if finish_reason == "conversation_echo"
3650
3698
  else "a degenerate repetition loop"
3651
3699
  )
3652
3700
  error = (
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tamfis-code
3
- Version: 0.6.3
3
+ Version: 0.6.5
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
@@ -174,6 +174,20 @@ class CtrlTInjectsFollowUpTests(_StatePatchMixin, unittest.IsolatedAsyncioTestCa
174
174
  import asyncio
175
175
  asyncio.run(_run())
176
176
 
177
+ def test_ctrl_t_is_detected_when_read_with_adjacent_terminal_bytes(self):
178
+ async def _run():
179
+ renderer = StreamRenderer(_console())
180
+ cfg = _config("ask")
181
+ listener = LiveInputListener(session_id=1, renderer=renderer, cli_config=cfg)
182
+ with patch.object(listener, "_interject", new_callable=AsyncMock) as mocked:
183
+ listener._buf = bytearray(b"x" + _CTRL_T + b"y")
184
+ listener._dispatch()
185
+ await asyncio.sleep(0)
186
+ mocked.assert_awaited_once()
187
+
188
+ import asyncio
189
+ asyncio.run(_run())
190
+
177
191
  import asyncio
178
192
  asyncio.run(_run())
179
193
 
@@ -767,6 +767,29 @@ class RunLocalAgentTurnTests(_StatePatchMixin, unittest.TestCase):
767
767
  )
768
768
  self.assertNotIn(looping_phrase, visible)
769
769
 
770
+ def test_repeated_conversation_transcript_is_stopped_early(self):
771
+ with tempfile.TemporaryDirectory() as ws:
772
+ rounds = [[
773
+ _chunk(_delta(content=(
774
+ 'Then the user said "audit the site". Then the assistant responded '
775
+ "with a long analysis. " if index % 2 else
776
+ 'Then the user said "continue until fixed". Then the assistant responded '
777
+ "with another long analysis. "
778
+ ))) for index in range(20)
779
+ ]]
780
+ client = _FakeClient(rounds)
781
+ manager = _FakeManager(client)
782
+ renderer = _RecordingRenderer()
783
+ outcome = asyncio.run(run_local_agent_turn(
784
+ manager, ProviderType.NVIDIA, None,
785
+ [{"role": "user", "content": "summarize the site"}],
786
+ self._console(), renderer,
787
+ workspace_root=ws, session_id=1, approval_policy="auto", interactive=False,
788
+ ))
789
+ self.assertEqual(outcome.status, "failed")
790
+ self.assertIn("repeated conversation transcript", outcome.error)
791
+ self.assertEqual(len(client.calls), 1)
792
+
770
793
  def test_change_request_completed_with_no_mutation_gets_a_caveat(self):
771
794
  """Confirmed live: a weak model can narrate "I'll fix this" -- complete
772
795
  with a fabricated "corrected" code block -- without ever calling
@@ -79,6 +79,17 @@ class DetectWorkspaceScopeTests(unittest.TestCase):
79
79
  scope = _detect_workspace_scope(str(root), "fix the bug")
80
80
  self.assertEqual(scope, [root.resolve()])
81
81
 
82
+ def test_explicit_absolute_project_path_expands_scope_from_admin_cwd(self):
83
+ with tempfile.TemporaryDirectory() as parent, tempfile.TemporaryDirectory() as admin:
84
+ project = Path(parent) / "tamfisseo"
85
+ project.mkdir()
86
+ (project / "package.json").write_text("{}")
87
+ scope = _detect_workspace_scope(
88
+ str(Path(admin)),
89
+ f"check the project at {project / 'package.json'} and fix its bugs",
90
+ )
91
+ self.assertEqual(scope, [project.resolve()])
92
+
82
93
  def test_explicitly_named_root_is_honored_even_if_archived(self):
83
94
  """A user who explicitly names a backup directory is making a
84
95
  deliberate request -- classification only governs *implicit*
File without changes
File without changes
File without changes