ltcai 9.1.0 → 9.2.0

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 (40) hide show
  1. package/README.md +49 -41
  2. package/docs/CHANGELOG.md +32 -0
  3. package/docs/COMMUNITY_AND_PLUGINS.md +4 -3
  4. package/docs/DEVELOPMENT.md +8 -8
  5. package/docs/LEGACY_COMPATIBILITY.md +1 -1
  6. package/docs/ONBOARDING.md +2 -2
  7. package/docs/OPERATIONS.md +1 -1
  8. package/docs/TRUST_MODEL.md +1 -1
  9. package/docs/WHY_LATTICE.md +1 -1
  10. package/docs/kg-schema.md +1 -1
  11. package/docs/mcp-tools.md +1 -1
  12. package/lattice_brain/__init__.py +1 -1
  13. package/lattice_brain/runtime/multi_agent.py +1 -1
  14. package/latticeai/__init__.py +1 -1
  15. package/latticeai/api/chat_helpers.py +4 -0
  16. package/latticeai/api/chat_intents.py +30 -16
  17. package/latticeai/core/agent.py +31 -4
  18. package/latticeai/core/agent_prompts.py +5 -0
  19. package/latticeai/core/file_generation.py +451 -0
  20. package/latticeai/core/legacy_compatibility.py +1 -1
  21. package/latticeai/core/marketplace.py +1 -1
  22. package/latticeai/core/workspace_os.py +1 -1
  23. package/latticeai/services/architecture_readiness.py +1 -1
  24. package/latticeai/services/product_readiness.py +1 -1
  25. package/package.json +1 -1
  26. package/scripts/check_current_release_docs.mjs +1 -1
  27. package/src-tauri/Cargo.lock +1 -1
  28. package/src-tauri/Cargo.toml +1 -1
  29. package/src-tauri/tauri.conf.json +1 -1
  30. package/static/app/asset-manifest.json +10 -10
  31. package/static/app/assets/{Act-Bzz0bUyW.js → Act-C3dBrWE-.js} +1 -1
  32. package/static/app/assets/{Brain-Dj2J20YA.js → Brain-DBYgdcjt.js} +1 -1
  33. package/static/app/assets/{Capture-CqlEl1Ga.js → Capture-Cf3hqRtN.js} +1 -1
  34. package/static/app/assets/{Library-B03FP1Yx.js → Library-CFfkNn3s.js} +1 -1
  35. package/static/app/assets/{System-80lHW0Ux.js → System-BOurbT-v.js} +1 -1
  36. package/static/app/assets/{index-BiMofBTM.js → index-A3M9sElj.js} +3 -3
  37. package/static/app/assets/{primitives-Q1A96_7v.js → primitives-DcUUmhdC.js} +1 -1
  38. package/static/app/assets/{textarea-D13RtnTo.js → textarea-BklR6zN4.js} +1 -1
  39. package/static/app/index.html +1 -1
  40. package/static/sw.js +1 -1
package/README.md CHANGED
@@ -1,13 +1,18 @@
1
1
  # Lattice AI
2
2
 
3
- **Lattice AI 9.1.0 is the local-first Digital Brain platform. Code Review Completion & Fail-Closed Runtime closes every actionable finding from the July 11 review: security boundaries now deny by default, runtime and chat state have typed ownership, frontend failures stay visible and tested, and repository/release hygiene is enforced across the 9.1.0 line.**
3
+ **Lattice AI 9.2.0 is the local-first Digital Brain platform. Model-Agnostic File Generation makes "create a file" work reliably with any loaded LLM: file requests run through a strict extension-aware prompt, robust extraction, per-type validation, one corrective retry, and a deterministic repair fallback, so even small local models (gemma/qwen class) always produce a structurally valid HTML, JSON, or code file.**
4
4
 
5
5
  **Lattice AI는 모델이 바뀌어도 내 지식과 맥락을 보존하는 로컬 우선 AI 브레인입니다.**
6
6
 
7
- > The 9.1.0 release adds request-scoped model routing,
7
+ > The 9.2.0 release hardens file creation end to end: chat file requests are
8
+ > routed to a deterministic direct-write path (including type-only requests
9
+ > like "html 파일 만들어줘"), model replies are treated as untrusted content
10
+ > and cleaned of fences, reasoning blocks, and chat framing, and the agent
11
+ > JSON loop tolerates small-model formatting slips instead of aborting.
12
+ > The 9.1.0 release added request-scoped model routing,
8
13
  > workspace-isolated graph identities and frontend caches, fail-closed admin
9
14
  > gates, SSRF-safe web capture, private local state permissions, and reproducible
10
- > release/test isolation. The same release introduces a human-first UI:
15
+ > release/test isolation. The same release introduced a human-first UI:
11
16
  > a visible knowledge journey from conversation or source capture into the
12
17
  > living Brain, its real relationship graph, and memory-grounded automation;
13
18
  > the empty Brain home now keeps that complete loop inside one viewport, with
@@ -87,10 +92,11 @@ You need Lattice AI when:
87
92
  The screenshots below are the latest checked-in visual evidence captures. They
88
93
  keep the first-run Brain flow, memory graph, source capture, model library,
89
94
  system view, admin console, and review center visible as release gates while
90
- 9.1.0 completes the July 11 code review with fail-closed access control,
91
- typed runtime/model state, a decomposed chat API, honest frontend error states
92
- and unit tests, and repository/release hygiene. The captures below are the
93
- checked-in 9.1.0 visual release evidence for that product flow.
95
+ 9.2.0 makes file generation model-agnostic: chat file requests always end in
96
+ a structurally valid file regardless of which LLM is loaded, backed by
97
+ extraction, validation, corrective retry, and deterministic repair. The
98
+ captures below are the checked-in 9.2.0 visual release evidence for that
99
+ product flow.
94
100
 
95
101
  ### 1. Wake Brain
96
102
 
@@ -102,21 +108,21 @@ confirm owner, check the computer, choose the Brain voice.
102
108
  Choose the owner of the Brain. The profile is not a SaaS account by default; it
103
109
  is the local identity for the knowledge you keep.
104
110
 
105
- ![Login](output/release/v9.1.0/screenshots/01-login.png)
111
+ ![Login](output/release/v9.2.0/screenshots/01-login.png)
106
112
 
107
113
  ### 3. Recommended Models
108
114
 
109
115
  Start with a short list: safest recommendation, faster model, stronger model.
110
116
  Advanced details stay available without overwhelming first-time users.
111
117
 
112
- ![Recommended Models](output/release/v9.1.0/screenshots/02-recommended-models.png)
118
+ ![Recommended Models](output/release/v9.2.0/screenshots/02-recommended-models.png)
113
119
 
114
120
  ### 4. Install And Load
115
121
 
116
122
  Download and load only after consent. Lattice explains model size, local
117
123
  execution, and network use before work starts.
118
124
 
119
- ![Install and Load](output/release/v9.1.0/screenshots/03-install-load-progress.png)
125
+ ![Install and Load](output/release/v9.2.0/screenshots/03-install-load-progress.png)
120
126
 
121
127
  ### 5. Brain Chat
122
128
 
@@ -128,14 +134,14 @@ and its visible life signal follow real listening, recall, synthesis, and action
128
134
  state. Detailed memory rings, provenance, conversation history, and
129
135
  model/runtime proof open as overlays only when requested.
130
136
 
131
- ![One-viewport Living Brain Home](output/release/v9.1.0/screenshots/04-brain-chat-home.png)
137
+ ![One-viewport Living Brain Home](output/release/v9.2.0/screenshots/04-brain-chat-home.png)
132
138
 
133
139
  ### 6. Review Center
134
140
 
135
141
  Automation results are staged for review before they become durable decisions.
136
142
  Snooze, unsnooze, run now, approve, and dismiss actions stay explicit.
137
143
 
138
- ![Review Center](output/release/v9.1.0/screenshots/12-review-center.png)
144
+ ![Review Center](output/release/v9.2.0/screenshots/12-review-center.png)
139
145
 
140
146
  ## Brain Depths
141
147
 
@@ -151,10 +157,10 @@ The user travels inward from everyday memory to deeper structure:
151
157
 
152
158
  Walkthrough:
153
159
 
154
- ![v9.1.0 Living Brain walkthrough](output/release/v9.1.0/gifs/v9.1.0-living-brain-walkthrough.gif)
160
+ ![v9.2.0 Living Brain walkthrough](output/release/v9.2.0/gifs/v9.2.0-living-brain-walkthrough.gif)
155
161
 
156
162
  Screenshot index and capture notes:
157
- [output/release/v9.1.0/SCREENSHOT_INDEX.md](output/release/v9.1.0/SCREENSHOT_INDEX.md)
163
+ [output/release/v9.2.0/SCREENSHOT_INDEX.md](output/release/v9.2.0/SCREENSHOT_INDEX.md)
158
164
 
159
165
  ## Install
160
166
 
@@ -239,42 +245,43 @@ See [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for developer workflow details.
239
245
 
240
246
  ## Current Release
241
247
 
242
- The current release is **9.1.0 — Code Review Completion & Fail-Closed Runtime**:
243
-
244
- - Telegram rejects every chat and callback outside
245
- `LATTICEAI_TELEGRAM_ALLOWED_CHAT_IDS`, and its local API bridge requires
246
- `LATTICEAI_SERVER_SESSION_TOKEN` instead of scanning stored sessions.
247
- - Public invitation authorization is signed and expiring, default invitation
248
- credentials are removed, Knowledge Graph scope lookup fails closed, and
249
- desktop/knowledge/network tools use explicit policy and consent boundaries.
250
- - App assembly exports typed runtime stages, model routing uses injected typed
251
- state, and chat history, documents, streaming, and contracts are split into
252
- focused modules.
253
- - The React workspace distinguishes unavailable services from empty Brain data,
254
- gates success callbacks on real success, and has Vitest coverage for API
255
- result shapes, proof state, conversation state, primitives, and i18n.
256
- - Large frontend i18n/CSS/Brain hooks and repeated runtime utilities are split,
257
- obsolete aliases and local VSIX artifacts are removed, and review documents
258
- live under `docs/reviews/` as immutable history.
259
-
260
- Expected artifacts for 9.1.0 release must use exact filenames:
261
-
262
- - `dist/ltcai-9.1.0-py3-none-any.whl`
263
- - `dist/ltcai-9.1.0.tar.gz`
264
- - `ltcai-9.1.0.tgz`
265
- - `dist/ltcai-9.1.0.vsix`
266
- - `src-tauri/target/release/bundle/dmg/Lattice AI_9.1.0_aarch64.dmg`
248
+ The current release is **9.2.0 — Model-Agnostic File Generation**:
249
+
250
+ - A new `latticeai.core.file_generation` pipeline treats every model reply as
251
+ untrusted content: extension-aware strict prompting, extraction of the real
252
+ payload from fences/`<think>` blocks/chat framing, per-type structural
253
+ validation (complete HTML documents, parseable JSON, CSS rule blocks), one
254
+ corrective retry that tells the model what was wrong, and a deterministic
255
+ repair fallback that guarantees a valid file.
256
+ - Type-only requests without an explicit filename ("html 파일 만들어줘",
257
+ "웹페이지 만들어줘") now resolve to an inferred target and use the
258
+ deterministic direct-write path instead of the model-driven agent loop.
259
+ - The agent executor tolerates small-model formatting slips: `<think>` blocks
260
+ and trailing commas are stripped from action JSON, and parse failures feed a
261
+ corrective format reminder back to the model instead of aborting the run.
262
+ - The executor prompt pins exact file-content rules for `write_file` so
263
+ agent-created files are complete and fence-free.
264
+ - File writes report whether content was generated cleanly, retried, or
265
+ auto-repaired in the response payload (`generation` metadata).
266
+
267
+ Expected artifacts for 9.2.0 release must use exact filenames:
268
+
269
+ - `dist/ltcai-9.2.0-py3-none-any.whl`
270
+ - `dist/ltcai-9.2.0.tar.gz`
271
+ - `ltcai-9.2.0.tgz`
272
+ - `dist/ltcai-9.2.0.vsix`
273
+ - `src-tauri/target/release/bundle/dmg/Lattice AI_9.2.0_aarch64.dmg`
267
274
 
268
275
  Do not use wildcard artifact uploads. Package registry publishing remains owner-run.
269
276
 
270
277
  See [docs/ROADMAP_RECOMMENDATIONS.md](docs/ROADMAP_RECOMMENDATIONS.md) for the
271
- strategic roadmap slices applied through 9.1.0 and the follow-up tracks.
278
+ strategic roadmap slices applied through 9.2.0 and the follow-up tracks.
272
279
 
273
280
  ## Known Limitations
274
281
 
275
282
  - External package registries are owner-published and can lag behind GitHub.
276
283
  - PostgreSQL/pgvector is optional scale/migration tooling. SQLite remains the
277
- live local Brain store in 9.1.0.
284
+ live local Brain store in 9.2.0.
278
285
  - Docker, model downloads, cloud model calls, Telegram, Brain Network, and update
279
286
  checks require explicit user action.
280
287
  - Conversation does not fabricate answers when no model is loaded.
@@ -286,6 +293,7 @@ strategic roadmap slices applied through 9.1.0 and the follow-up tracks.
286
293
 
287
294
  | Version | Theme |
288
295
  | --- | --- |
296
+ | 9.2.0 | Model-Agnostic File Generation: chat file requests always produce structurally valid files with any LLM via extraction, per-type validation, corrective retry, deterministic repair, inferred file targets, and a fault-tolerant agent JSON loop |
289
297
  | 9.1.0 | Code Review Completion & Fail-Closed Runtime: all July 11 review findings closed across fail-closed security, typed runtime/model/chat boundaries, honest frontend failures and tests, and repository hygiene |
290
298
  | 9.0.0 | Code Review Closure & Runtime Cleanup: July 8 code-review follow-ups fixed, chat/runtime reliability improved, duplicated utility surfaces consolidated, runtime audit append paths moved to JSONL, and release metadata/artifacts synchronized |
291
299
  | 8.9.0 | Scoped Memory & Tool Policy Hardening: authenticated history/KG reads are workspace-scoped, direct Tool API paths enforce registry policy, local approvals hash tokens at rest, AgentRuntime approval semantics are explicit, and frontend/runtime seams are split |
package/docs/CHANGELOG.md CHANGED
@@ -4,6 +4,38 @@ The top entry is either the current unreleased main-branch work or the current
4
4
  release line. Older entries are historical and may describe behavior as it
5
5
  existed at that release.
6
6
 
7
+ ## [9.2.0] - 2026-07-20
8
+
9
+ ### Added
10
+ - Added `latticeai/core/file_generation.py`, a model-agnostic file content
11
+ pipeline: extension-aware strict prompting with pinned first lines, payload
12
+ extraction from fences/`<think>` blocks/chat framing, per-type structural
13
+ validation, one corrective retry, and a deterministic repair fallback that
14
+ guarantees a structurally valid file from any loaded LLM.
15
+ - Added filename inference for chat file requests that name a type but no path
16
+ ("html 파일 만들어줘" → `generated_page.html`), keeping such requests on the
17
+ deterministic direct-write path instead of the model-driven agent loop.
18
+ - Added `generation` metadata (attempts, validation reasons, auto-repair flag)
19
+ to `/chat` direct file-action responses, and a user-facing notice when the
20
+ saved file was produced by auto-repair.
21
+ - Added `tests/unit/test_file_generation.py` covering extraction, validation,
22
+ repair, inference, prompting, and retry/repair orchestration (22 tests).
23
+
24
+ ### Changed
25
+ - Chat file-action routing counts explicit artifact types (html, 웹페이지,
26
+ webpage) as file words, so "html 페이지 만들어줘" creates a real file.
27
+ - Direct file generation clamps temperature to ≤0.3 and raises the token
28
+ budget to ≥4096 so generated documents complete.
29
+ - The agent executor prompt pins `write_file` content rules: complete raw
30
+ content, no Markdown fences, extension-valid documents.
31
+
32
+ ### Fixed
33
+ - Small local models (gemma/qwen class) no longer save chat wrappers, fenced
34
+ code blocks, reasoning traces, or truncated documents as file bytes.
35
+ - The agent JSON loop no longer aborts on the first malformed action reply:
36
+ `extract_action` strips `<think>` blocks and tolerates trailing commas, and
37
+ up to two corrective format reminders are fed back before halting.
38
+
7
39
  ## [9.1.0] - 2026-07-11
8
40
 
9
41
  ### Added
@@ -1,12 +1,13 @@
1
1
  # Community And Plugins
2
2
 
3
- Current release: **9.1.0 — Code Review Completion & Fail-Closed Runtime**.
3
+ Current release: **9.2.0 — Model-Agnostic File Generation**.
4
4
 
5
5
  LatticeAI defines the path from a strong local-first framework (8.4.0
6
6
  action-aware baseline, 8.5.0 registry+DI hardening, 8.6.0 capture/navigation
7
7
  reliability, 8.7.0 runtime-state hygiene, 8.8.0 Brain Core extraction
8
- readiness, 8.9.0 scoped Tool API hardening, 9.0.0 cleanup closure, and 9.1.0
9
- fail-closed review completion) to a product ecosystem. The
8
+ readiness, 8.9.0 scoped Tool API hardening, 9.0.0 cleanup closure, 9.1.0
9
+ fail-closed review completion, and 9.2.0 model-agnostic file generation) to a
10
+ product ecosystem. The
10
11
  immediate goal is small and practical: make it clear how
11
12
  contributors can extend the Brain without weakening local-first trust,
12
13
  workspace scoping, or release quality.
@@ -1,10 +1,10 @@
1
1
  # Lattice AI Development
2
2
 
3
- Current release: **9.1.0 — Code Review Completion & Fail-Closed Runtime**.
3
+ Current release: **9.2.0 — Model-Agnostic File Generation**.
4
4
 
5
5
  This document is for contributors working on the local-first Digital Brain
6
6
  codebase. Product positioning and quick start stay in `README.md`; release
7
- history is intentionally limited to 8.0.0-9.1.0 in `docs/CHANGELOG.md` and
7
+ history is intentionally limited to 8.0.0-9.2.0 in `docs/CHANGELOG.md` and
8
8
  `RELEASE.md`.
9
9
 
10
10
  ## Product Contract
@@ -153,10 +153,10 @@ For user-facing, API, runtime, release, or packaging changes, check:
153
153
  Release/publish examples must use exact target-version filenames. Do not
154
154
  document wildcard artifact upload commands.
155
155
 
156
- For 9.1.0 release work, exact artifacts are:
156
+ For 9.2.0 release work, exact artifacts are:
157
157
 
158
- - `dist/ltcai-9.1.0-py3-none-any.whl`
159
- - `dist/ltcai-9.1.0.tar.gz`
160
- - `ltcai-9.1.0.tgz`
161
- - `dist/ltcai-9.1.0.vsix`
162
- - `src-tauri/target/release/bundle/dmg/Lattice AI_9.1.0_aarch64.dmg`
158
+ - `dist/ltcai-9.2.0-py3-none-any.whl`
159
+ - `dist/ltcai-9.2.0.tar.gz`
160
+ - `ltcai-9.2.0.tgz`
161
+ - `dist/ltcai-9.2.0.vsix`
162
+ - `src-tauri/target/release/bundle/dmg/Lattice AI_9.2.0_aarch64.dmg`
@@ -1,6 +1,6 @@
1
1
  # Legacy Compatibility Map
2
2
 
3
- Current target: **9.1.0 — Code Review Completion & Fail-Closed Runtime**.
3
+ Current target: **9.2.0 — Model-Agnostic File Generation**.
4
4
 
5
5
  Lattice AI is moving toward a smaller, modular architecture centered on
6
6
  `lattice_brain`, `latticeai.services`, `latticeai.api`, and `latticeai.runtime`.
@@ -1,6 +1,6 @@
1
1
  # Lattice AI Onboarding
2
2
 
3
- Current release: **9.1.0 — Code Review Completion & Fail-Closed Runtime**.
3
+ Current release: **9.2.0 — Model-Agnostic File Generation**.
4
4
 
5
5
  The first-run goal is a five-minute path from "I opened the app" to "my Brain
6
6
  has a source, a question, and proof." This page is the product contract behind
@@ -40,7 +40,7 @@ read the docs first.
40
40
 
41
41
  ## Release Gate
42
42
 
43
- 9.1.0 treats onboarding as a release gate, not marketing copy. The current
43
+ 9.2.0 treats onboarding as a release gate, not marketing copy. The current
44
44
  machine-checkable product readiness report requires this five-minute contract,
45
45
  the Brain Home surface, setup helpers, graph ingestion tests, and exact release
46
46
  artifact documentation before the release can be called complete.
@@ -1,4 +1,4 @@
1
- # Lattice AI — Operations Guide (v9.1.0)
1
+ # Lattice AI — Operations Guide (v9.2.0)
2
2
 
3
3
  ## 1. 데이터 파일 위치
4
4
 
@@ -1,6 +1,6 @@
1
1
  # Lattice AI Trust Model
2
2
 
3
- Current release: **9.1.0 — Code Review Completion & Fail-Closed Runtime**.
3
+ Current release: **9.2.0 — Model-Agnostic File Generation**.
4
4
 
5
5
  Lattice AI is local-first, explicit about external communication, and honest
6
6
  when a capability is unavailable.
@@ -1,6 +1,6 @@
1
1
  # Why Lattice AI Exists
2
2
 
3
- Current release: **9.1.0 — Code Review Completion & Fail-Closed Runtime**.
3
+ Current release: **9.2.0 — Model-Agnostic File Generation**.
4
4
 
5
5
  **Lattice AI is a local-first Digital Brain that keeps your knowledge durable
6
6
  across any AI model.**
package/docs/kg-schema.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Knowledge Graph Schema
2
2
 
3
- Current release: **9.1.0 — Code Review Completion & Fail-Closed Runtime**.
3
+ Current release: **9.2.0 — Model-Agnostic File Generation**.
4
4
 
5
5
  명세 출처: `lattice_ai_full_spec.pptx` 슬라이드 20·21·22
6
6
  구현: `kg_schema.py`
package/docs/mcp-tools.md CHANGED
@@ -1,4 +1,4 @@
1
- # MCP 도구 카탈로그 (v9.1.0)
1
+ # MCP 도구 카탈로그 (v9.2.0)
2
2
 
3
3
  Lattice AI는 MCP(Model Context Protocol) 서버로 동작하여 Claude Desktop, Cursor 등에서 직접 도구를 사용할 수 있습니다.
4
4
 
@@ -26,7 +26,7 @@ from .storage import (
26
26
  storage_from_env,
27
27
  )
28
28
 
29
- __version__ = "9.1.0"
29
+ __version__ = "9.2.0"
30
30
 
31
31
  __all__ = [
32
32
  "AgentRuntime",
@@ -21,7 +21,7 @@ from .contracts import multi_agent_contract
21
21
  from ..utils import now_iso as _now
22
22
 
23
23
 
24
- MULTI_AGENT_VERSION = "9.1.0"
24
+ MULTI_AGENT_VERSION = "9.2.0"
25
25
 
26
26
  AGENT_ROLES = ("researcher", "planner", "executor", "reviewer", "release")
27
27
  CORE_PIPELINE = ("planner", "executor", "reviewer")
@@ -1,3 +1,3 @@
1
1
  """Lattice AI - modular server package."""
2
2
 
3
- __version__ = "9.1.0"
3
+ __version__ = "9.2.0"
@@ -128,6 +128,10 @@ def is_file_action_request(text: str) -> bool:
128
128
  has_target = bool(_FILE_TARGET_RE.search(raw))
129
129
  has_file_word = any(word in lower for word in (
130
130
  "file", "파일", "문서", "artifact", "아티팩트", "save as", "저장",
131
+ # Explicit artifact types: users asking for "an html page" expect a
132
+ # real file, not a code block in chat — route them to the file path
133
+ # where the model-agnostic generation pipeline guarantees validity.
134
+ "html", "웹페이지", "웹 페이지", "홈페이지", "webpage", "web page",
131
135
  ))
132
136
  has_action = any(word in lower for word in (
133
137
  "create", "make", "write", "save", "generate", "edit", "update",
@@ -14,10 +14,10 @@ from latticeai.api.chat_helpers import (
14
14
  file_action_target,
15
15
  format_network_status,
16
16
  inline_file_action_content,
17
- strip_generated_file_content,
18
17
  )
19
18
  from latticeai.api.chat_stream import agent_payload_stream, single_answer_response
20
19
  from latticeai.core.agent import AgentState
20
+ from latticeai.core.file_generation import generate_file_content, infer_file_target
21
21
 
22
22
 
23
23
  class ChatIntentController:
@@ -185,28 +185,36 @@ class ChatIntentController:
185
185
  return single_answer_response(req, answer, model="client_url")
186
186
 
187
187
  async def direct_file_action(self, req: Any, *, model_id: Optional[str]):
188
- target_path = file_action_target(req.message)
188
+ # An explicit path ("report.txt") wins; otherwise infer one from an
189
+ # explicit type keyword ("html 파일 만들어줘") so weak models never
190
+ # have to drive the JSON tool loop just to create a file.
191
+ target_path = file_action_target(req.message) or infer_file_target(req.message)
189
192
  if not target_path:
190
193
  return None
191
194
  content = inline_file_action_content(req.message)
195
+ generation_meta: Optional[Dict[str, Any]] = None
192
196
  if content is None and not model_id:
193
197
  return self.no_model_response()
194
198
  if content is None and model_id:
195
- generation_context = (
196
- "Create the exact content for the requested file. "
197
- "Return only the file bytes as plain text. "
198
- "Do not wrap the answer in Markdown fences, commentary, or explanations.\n\n"
199
- f"Target path: {target_path}\n"
200
- f"User request: {req.message}"
201
- )
202
- raw_content = await self.router.generate_as(
203
- model_id,
204
- message="Return only the requested file content.",
205
- context=generation_context,
206
- max_tokens=req.max_tokens,
207
- temperature=req.temperature,
199
+ # Model-agnostic pipeline: strict extension-aware prompt →
200
+ # extraction validation one corrective retry → deterministic
201
+ # repair. Guarantees a structurally valid file from any LLM.
202
+ async def _generate(context: str) -> str:
203
+ return str(
204
+ await self.router.generate_as(
205
+ model_id,
206
+ message="Return only the requested file content.",
207
+ context=context,
208
+ max_tokens=max(int(req.max_tokens or 0), 4096),
209
+ temperature=min(req.temperature, 0.3),
210
+ )
211
+ )
212
+
213
+ content, generation_meta = await generate_file_content(
214
+ _generate,
215
+ target_path=target_path,
216
+ user_request=req.message,
208
217
  )
209
- content = strip_generated_file_content(str(raw_content))
210
218
  if content is None:
211
219
  raise HTTPException(
212
220
  status_code=400,
@@ -220,6 +228,10 @@ class ChatIntentController:
220
228
  except self.tool_error as exc:
221
229
  raise HTTPException(status_code=400, detail=str(exc)) from exc
222
230
  answer = f"{result.get('path') or target_path} 파일을 만들었습니다."
231
+ if generation_meta and generation_meta.get("repaired"):
232
+ answer += (
233
+ " (모델 출력이 불완전해 자동 보정을 거쳐 유효한 파일로 저장했습니다.)"
234
+ )
223
235
  created_files = [
224
236
  {
225
237
  "path": result.get("path") or target_path,
@@ -248,6 +260,8 @@ class ChatIntentController:
248
260
  "routed_to_agent": True,
249
261
  "action_route": "direct_write_file",
250
262
  }
263
+ if generation_meta is not None:
264
+ payload["generation"] = generation_meta
251
265
  if req.stream:
252
266
  return StreamingResponse(
253
267
  agent_payload_stream(
@@ -69,9 +69,16 @@ class AgentRunContext:
69
69
  self.approved_by_human: bool = False
70
70
 
71
71
 
72
+ _THINK_BLOCK_RE = re.compile(
73
+ r"<(think|thinking|reasoning)>.*?</\1>", flags=re.DOTALL | re.IGNORECASE
74
+ )
75
+
76
+
72
77
  def extract_action(raw: str) -> Dict:
73
78
  """Parse one JSON action object out of an LLM response (tolerant of fences/prose)."""
74
- text = raw.strip()
79
+ # Small local models often prepend <think>...</think> reasoning that can
80
+ # itself contain braces — drop it before locating the action object.
81
+ text = _THINK_BLOCK_RE.sub("", raw).strip()
75
82
  fenced = re.search(r"```(?:json)?\s*(\{.*?\})\s*```", text, flags=re.DOTALL)
76
83
  if fenced:
77
84
  text = fenced.group(1).strip()
@@ -83,8 +90,14 @@ def extract_action(raw: str) -> Dict:
83
90
 
84
91
  try:
85
92
  action = json.loads(text)
86
- except json.JSONDecodeError as exc:
87
- raise ValueError(f"Agent did not return valid JSON: {exc}") from exc
93
+ except json.JSONDecodeError:
94
+ # Second chance for the most common small-model JSON slips: trailing
95
+ # commas before a closing brace/bracket.
96
+ repaired = re.sub(r",\s*([}\]])", r"\1", text)
97
+ try:
98
+ action = json.loads(repaired)
99
+ except json.JSONDecodeError as exc:
100
+ raise ValueError(f"Agent did not return valid JSON: {exc}") from exc
88
101
 
89
102
  if not isinstance(action, dict) or "action" not in action:
90
103
  raise ValueError("Agent JSON must include an action field.")
@@ -248,6 +261,7 @@ class SingleAgentRuntime:
248
261
  d = self.deps
249
262
  exec_count = sum(1 for s in ctx.transcript if s.get("state") == AgentState.EXECUTING.value)
250
263
  budget = max(1, max_steps - exec_count)
264
+ parse_failures = 0
251
265
 
252
266
  for _ in range(budget):
253
267
  corrections_hint = (
@@ -280,11 +294,24 @@ class SingleAgentRuntime:
280
294
  try:
281
295
  action = extract_action(str(raw))
282
296
  except ValueError as exc:
297
+ parse_failures += 1
283
298
  ctx.transcript.append({
284
299
  "state": AgentState.EXECUTING.value, "action": "parse_error",
285
300
  "raw": str(raw)[:400], "error": str(exc),
286
301
  })
287
- break
302
+ if parse_failures >= 3:
303
+ break
304
+ # Weak models often need one concrete reminder of the wire
305
+ # format; feed it through the corrections channel and retry
306
+ # instead of aborting the whole run on the first slip.
307
+ hint = (
308
+ 'Your last reply was not a single JSON action object. Reply with '
309
+ 'EXACTLY one JSON object like {"thoughts": "...", "action": '
310
+ '"tool_name", "args": {...}} and nothing else.'
311
+ )
312
+ if hint not in ctx.corrections:
313
+ ctx.corrections.append(hint)
314
+ continue
288
315
 
289
316
  name = action.get("action")
290
317
  thoughts = str(action.get("thoughts") or "")[:600]
@@ -43,6 +43,11 @@ You think and act like a senior software engineer:
43
43
  Respond with exactly ONE JSON object per step:
44
44
  {"thoughts": "what you learned / why this next action", "action": "tool_name", "args": {...}}
45
45
 
46
+ When writing a file (write_file), args.content must be the COMPLETE raw file
47
+ content: no Markdown fences, no commentary, valid for the file's extension
48
+ (an .html file starts with <!DOCTYPE html> and ends with </html>; a .json
49
+ file must parse as strict JSON).
50
+
46
51
  When the task is fully done AND a tool result in this run confirms it:
47
52
  {"thoughts": "verified", "action": "final", "message": "한국어로 무엇을 했고 어디서 검증했는지 요약"}
48
53