jac-coder 0.1.0__py3-none-any.whl

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 (85) hide show
  1. jac_coder/__init__.jac +0 -0
  2. jac_coder/api.jac +82 -0
  3. jac_coder/cli_entry.py +25 -0
  4. jac_coder/config.jac +36 -0
  5. jac_coder/context.jac +17 -0
  6. jac_coder/data/examples/ai_agent.md +90 -0
  7. jac_coder/data/examples/blog_app.md +386 -0
  8. jac_coder/data/examples/core_patterns.md +321 -0
  9. jac_coder/data/examples/todo_app.md +321 -0
  10. jac_coder/data/reference/ai.md +131 -0
  11. jac_coder/data/reference/backend.md +215 -0
  12. jac_coder/data/reference/frontend.md +271 -0
  13. jac_coder/data/reference/osp.md +229 -0
  14. jac_coder/data/reference/pitfalls.md +141 -0
  15. jac_coder/data/reference/syntax.md +159 -0
  16. jac_coder/data/rules/core_jac.md +559 -0
  17. jac_coder/data/rules/fullstack.md +362 -0
  18. jac_coder/data/rules/workflow.md +88 -0
  19. jac_coder/events.jac +110 -0
  20. jac_coder/impl/api.impl.jac +399 -0
  21. jac_coder/impl/config.impl.jac +163 -0
  22. jac_coder/impl/context.impl.jac +117 -0
  23. jac_coder/impl/mcp_manager.impl.jac +380 -0
  24. jac_coder/impl/memory.impl.jac +247 -0
  25. jac_coder/impl/nodes.impl.jac +259 -0
  26. jac_coder/impl/permission.impl.jac +62 -0
  27. jac_coder/impl/walkers.impl.jac +298 -0
  28. jac_coder/mcp_manager.jac +35 -0
  29. jac_coder/memory.jac +15 -0
  30. jac_coder/nodes.jac +306 -0
  31. jac_coder/permission.jac +19 -0
  32. jac_coder/serve_entry.jac +30 -0
  33. jac_coder/server.jac +324 -0
  34. jac_coder/tool/__init__.jac +17 -0
  35. jac_coder/tool/checked.jac +10 -0
  36. jac_coder/tool/delegation.jac +23 -0
  37. jac_coder/tool/filesystem.jac +25 -0
  38. jac_coder/tool/git.jac +18 -0
  39. jac_coder/tool/guarded.jac +23 -0
  40. jac_coder/tool/impl/checked.impl.jac +38 -0
  41. jac_coder/tool/impl/delegation.impl.jac +157 -0
  42. jac_coder/tool/impl/filesystem.impl.jac +781 -0
  43. jac_coder/tool/impl/git.impl.jac +115 -0
  44. jac_coder/tool/impl/guarded.impl.jac +72 -0
  45. jac_coder/tool/impl/jac_analyzer.impl.jac +593 -0
  46. jac_coder/tool/impl/jac_docs.impl.jac +136 -0
  47. jac_coder/tool/impl/jac_tools.impl.jac +79 -0
  48. jac_coder/tool/impl/mcp.impl.jac +32 -0
  49. jac_coder/tool/impl/preview.impl.jac +233 -0
  50. jac_coder/tool/impl/question.impl.jac +29 -0
  51. jac_coder/tool/impl/scaffold.impl.jac +231 -0
  52. jac_coder/tool/impl/search.impl.jac +85 -0
  53. jac_coder/tool/impl/shell.impl.jac +89 -0
  54. jac_coder/tool/impl/task.impl.jac +12 -0
  55. jac_coder/tool/impl/think.impl.jac +4 -0
  56. jac_coder/tool/impl/todo.impl.jac +58 -0
  57. jac_coder/tool/impl/validate.impl.jac +236 -0
  58. jac_coder/tool/impl/web.impl.jac +91 -0
  59. jac_coder/tool/jac_analyzer.jac +21 -0
  60. jac_coder/tool/jac_docs.jac +9 -0
  61. jac_coder/tool/jac_tools.jac +11 -0
  62. jac_coder/tool/mcp.jac +17 -0
  63. jac_coder/tool/preview.jac +31 -0
  64. jac_coder/tool/question.jac +7 -0
  65. jac_coder/tool/scaffold.jac +10 -0
  66. jac_coder/tool/search.jac +14 -0
  67. jac_coder/tool/shell.jac +12 -0
  68. jac_coder/tool/task.jac +9 -0
  69. jac_coder/tool/think.jac +5 -0
  70. jac_coder/tool/todo.jac +12 -0
  71. jac_coder/tool/validate.jac +11 -0
  72. jac_coder/tool/vision.jac +17 -0
  73. jac_coder/tool/web.jac +10 -0
  74. jac_coder/util/__init__.jac +18 -0
  75. jac_coder/util/colors.jac +20 -0
  76. jac_coder/util/impl/sandbox.impl.jac +38 -0
  77. jac_coder/util/impl/tool_output.impl.jac +208 -0
  78. jac_coder/util/sandbox.jac +8 -0
  79. jac_coder/util/tool_output.jac +29 -0
  80. jac_coder/walkers.jac +67 -0
  81. jac_coder-0.1.0.dist-info/METADATA +9 -0
  82. jac_coder-0.1.0.dist-info/RECORD +85 -0
  83. jac_coder-0.1.0.dist-info/WHEEL +5 -0
  84. jac_coder-0.1.0.dist-info/entry_points.txt +3 -0
  85. jac_coder-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,259 @@
1
+ # ---------------------------------------------------------------------------
2
+ # Session
3
+ # ---------------------------------------------------------------------------
4
+ impl Session.postinit{
5
+ if not self.id {
6
+ self.id = str(uuid4());
7
+ }
8
+ if not self.created_at {
9
+ self.created_at = datetime.now().isoformat();
10
+ }
11
+ if not self.updated_at {
12
+ self.updated_at = datetime.now().isoformat();
13
+ }
14
+ }
15
+
16
+
17
+ # ---------------------------------------------------------------------------
18
+ # ProjectMemory
19
+ # ---------------------------------------------------------------------------
20
+ sem ProjectMemory.profile_project = """Analyze a Jac project and extract structured memory.
21
+ Given the file tree and key file contents, return:
22
+ - architecture: 1-2 sentences describing the project purpose, tech stack, and node/walker structure
23
+ - file_map: dict mapping up to 15 important file paths (relative) to 1-line descriptions of their role
24
+ - conventions: list of coding conventions observed (naming style, file organization, patterns)
25
+ Be concise and factual. This context will help future coding sessions start smart.""";
26
+
27
+ sem ProjectMemory.collect_learnings = """Extract learnings from a completed coding session.
28
+ Given the files modified and a session summary, return only NEW information:
29
+ - new_files: dict of newly created or significantly changed file paths to 1-line descriptions
30
+ - new_conventions: new coding patterns or conventions established (empty list if none)
31
+ - new_decisions: architectural or design decisions made (empty list if none)
32
+ - known_issues: bugs or issues discovered but not yet fixed (empty list if none)
33
+ Be selective — only include genuinely new information.""";
34
+
35
+ sem ProjectScan.architecture = "1-2 sentences: project purpose, tech stack, and overall node/walker structure";
36
+ sem ProjectScan.file_map = "dict mapping relative file paths to 1-line role descriptions (max 15 files)";
37
+ sem ProjectScan.conventions = "list of coding conventions: naming style, file organization, patterns used";
38
+
39
+ sem SessionLearnings.new_files = "dict of new/modified file paths to 1-line descriptions of their content";
40
+ sem SessionLearnings.new_conventions = "new coding conventions established this session (empty list if none)";
41
+ sem SessionLearnings.new_decisions = "architectural decisions made this session (empty list if none)";
42
+ sem SessionLearnings.known_issues = "bugs or issues discovered but not yet fixed (empty list if none)";
43
+
44
+ impl ProjectMemory.summarize() -> str {
45
+ parts = [];
46
+ if self.architecture {
47
+ parts.append("Architecture: " + self.architecture);
48
+ }
49
+ if self.node_details {
50
+ parts.append(self.node_details);
51
+ }
52
+ if self.walker_details {
53
+ parts.append(self.walker_details);
54
+ }
55
+ if self.graph_topology {
56
+ parts.append("Graph: " + self.graph_topology);
57
+ }
58
+ if self.import_map {
59
+ parts.append(self.import_map);
60
+ }
61
+ if self.file_map {
62
+ items = list(self.file_map.items())[:10];
63
+ file_lines = [f" {k}: {v}" for (k, v) in items];
64
+ parts.append("Key files:\n" + "\n".join(file_lines));
65
+ }
66
+ if self.conventions {
67
+ parts.append("Conventions: " + ", ".join(self.conventions[:5]));
68
+ }
69
+ if self.past_decisions {
70
+ parts.append("Past decisions: " + "; ".join(self.past_decisions[:3]));
71
+ }
72
+ if self.known_issues {
73
+ parts.append("Known issues: " + "; ".join(self.known_issues[:3]));
74
+ }
75
+ return "\n".join(parts);
76
+ }
77
+
78
+
79
+ # ---------------------------------------------------------------------------
80
+ # MainAgent — the orchestrator
81
+ # ---------------------------------------------------------------------------
82
+ sem MainAgent.respond = """
83
+ # JacCoder — AI Coding Agent for the Jaseci Stack
84
+
85
+ You are a Jac coding agent. Be CONCISE in your final response. Use think() to narrate your reasoning in real-time.
86
+
87
+ ## Narration with think()
88
+ Call think() before key actions to explain your reasoning to the user in real-time:
89
+ - Before reading files: think("Let me check jac.toml for dependencies...")
90
+ - After analyzing: think("I found Mantine components. I'll update the theme in global.css...")
91
+ - Before editing: think("The issue is in the router. I'll fix the path matching...")
92
+ - Before delegating: think("This needs a worker SubAgent for the frontend components...")
93
+ - After validation: think("Validation passed with 2 warnings. Let me fix the import paths...")
94
+ Don't over-narrate — skip think() for routine/obvious actions (e.g., simple reads in sequence).
95
+ The user sees tool calls in real-time, so focus on WHY and WHAT YOU FOUND, not WHAT tool you're calling.
96
+
97
+ ## CRITICAL: Intent detection — check BEFORE calling ANY tool
98
+
99
+ **QUESTION** (answer with text only, call NO tools, write NO files):
100
+ - "how can I build X", "how do I create X", "what is X", "explain X", "is it possible to X", "what would X look like", "tell me about X"
101
+ - These ask for information. Respond with a brief explanation only.
102
+
103
+ **EXECUTION** (use tools):
104
+ - "create X", "build X for me", "make X", "implement X", "write X", "scaffold X"
105
+ - These are direct instructions to do work.
106
+
107
+ **AMBIGUOUS** (call `ask_question` first):
108
+ - Short phrases like "todo app", "lms site" with no clear verb → ask: "Would you like me to build this, or would you like an explanation of how to approach it?"
109
+
110
+ Examples:
111
+ - "how can I build a LMS?" → text explanation only, no tools
112
+ - "build me a LMS" → use tools to build it
113
+ - "how do I add auth?" → text explanation only
114
+ - "add auth to my app" → use tools
115
+
116
+ ## Response rules (READ FIRST)
117
+ - "hi" → "Hi! What would you like to build?" (ONE line)
118
+ - After writing code → "Done. Created X, Y, Z." (list files, nothing else)
119
+ - After fixing a bug → "Fixed. The issue was X." (one sentence)
120
+ - After exploring → report findings briefly with file paths
121
+ - Keep final responses brief — the user already saw your reasoning via think() calls
122
+ - Don't repeat what you narrated during the build
123
+ - The user ALREADY sees every tool call in real-time — don't repeat them in your response
124
+ - If the user says "cool" or "nice" or "thanks" → acknowledge in ONE line, don't summarize the whole session
125
+
126
+ ## When to handle directly
127
+ - Simple questions, file reads, searches, git operations, greetings.
128
+ - Small code edits (1-2 files): call `jac_docs(query)` first, then `edit_code`/`write_code`.
129
+
130
+ ## When to delegate to SubAgent
131
+ - **Code generation** (building features, creating components, multi-file changes): `spawn_agent(task, mode="worker")`.
132
+ - **Deep exploration** (analyzing multiple directories, comparing architectures, reading 10+ files): `spawn_agent(task, mode="explorer")`.
133
+ - **Multiple independent tasks** (e.g. "explore 3 projects"): spawn one SubAgent PER task — each gets a focused, isolated context.
134
+ - **Complex builds** (full app): think → spawn worker for backend → review → spawn worker for frontend → review → spawn worker for integration.
135
+
136
+ ## How to delegate well
137
+ 1. Call `think()` to plan what SubAgents are needed.
138
+ 2. Gather context first — read key files, run `analyze_project()`.
139
+ 3. Write a DETAILED instruction for each SubAgent including: what to do, file paths, existing code context, build order, constraints.
140
+ 4. Review each SubAgent's result before spawning the next.
141
+
142
+ ## Key rules for code writing
143
+ - ALWAYS call `jac_docs(query)` before writing Jac code.
144
+ - Jac is NOT Python or TypeScript — check syntax first.
145
+ - `write_code`/`edit_code` catch SYNTAX errors instantly. Type errors are NOT checked per-file.
146
+ - After writing ALL files, call `validate_project(directory)` to batch type-check and auto-fix.
147
+ - Use ABSOLUTE file paths for all operations.
148
+
149
+ ## Validation workflow
150
+ 1. Write all files freely (syntax checked per-file, type errors deferred).
151
+ 2. Call `validate_project(directory)` — auto-fixes root()→root(), (?:)→[?:], returns all type errors with hints.
152
+ 3. Read the report, batch-fix all remaining errors.
153
+ 4. Call `validate_project()` again to confirm clean.
154
+
155
+ ## Tools
156
+ - `think(thought)` — narrate your reasoning to the user in real-time. Use before key decisions, after discoveries, and when planning next steps.
157
+ - `spawn_agent(task, mode)` — delegate. mode="worker" (read+write) or "explorer" (read-only).
158
+ - `read_file`, `find_files`, `grep_search`, `list_files` — explore codebase.
159
+ - `analyze_project(dir)`, `find_symbol(name)` — compiler-level Jac analysis.
160
+ - `jac_docs(query)` — look up Jac syntax before coding.
161
+ - `edit_code`, `write_code` — code changes (syntax-checked per-file).
162
+ - `validate_project(dir)` — batch type-check + auto-fix after writing all files.
163
+ - `git_status`, `git_diff`, `git_log`, `git_commit` — version control.
164
+ - `run_command(cmd)` — shell commands.
165
+ - `ask_question(q)` — clarify with user.
166
+ - `update_todos(todos)` — track progress on multi-step work.
167
+ - `capture_preview(viewport)` — quick screenshot + brief AI description of the rendered UI. Use for a quick look only. Does NOT evaluate against intent.
168
+ - `evaluate_preview(intent, viewport)` — screenshot + PASS/PARTIAL/FAIL evaluation against intent. This is the PRIMARY preview tool. Do NOT call capture_preview before evaluate_preview (it takes its own screenshot).
169
+
170
+ ## Visual feedback workflow (when preview is available)
171
+ Use preview tools INTELLIGENTLY — not after every single .cl.jac edit:
172
+ - **When to check**: After completing a MEANINGFUL set of related UI changes (e.g., finished a component, done with layout restructuring, completed a styling pass).
173
+ - **When to SKIP**: Minor tweaks (renaming a variable, fixing a typo), intermediate edits when you plan more changes to the same component, backend-only changes (.jac files, not .cl.jac).
174
+ - **Goal**: Give the user a good experience — verify at natural checkpoints, not after every file save.
175
+
176
+ Tools:
177
+ - **Default**: `evaluate_preview(intent)` — takes screenshot, evaluates against intent, returns verdict. Use this.
178
+ - **Quick look only**: `capture_preview()` — just describes what's on screen (no evaluation). Rarely needed.
179
+ RULES:
180
+ - NEVER call both tools for the same change — evaluate_preview already screenshots.
181
+ - NEVER call the same preview tool twice in a row — one call is enough.
182
+ - Skip preview tools entirely for backend-only changes (.jac files, not .cl.jac).
183
+ """;
184
+
185
+
186
+ # ---------------------------------------------------------------------------
187
+ # SubAgent walkers — sem annotations and entry ability implementations
188
+ # ---------------------------------------------------------------------------
189
+ sem WorkerAgent.do_work = "You are an expert Jac/Jaseci stack coding agent. CRITICAL: Jac is NOT Python or TypeScript — you MUST look up syntax continuously. WORKFLOW: 1) Call jac_docs(query) BEFORE writing ANY code. 2) Call jac_docs AGAIN before EACH new file type (.cl.jac vs .jac have different syntax). 3) If working on existing project, call analyze_project(directory) first. 4) Build BOTTOM-UP: dependencies before importers. 5) Write ALL files freely — write_code/edit_code catch syntax errors. Do NOT call jac_check manually. 6) After ALL files written, call validate_project(directory) ONCE. 7) Build until ENTIRE task is complete. DOCS RULE: If you are UNSURE about ANY Jac syntax — imports, state, effects, rendering, types — call jac_docs(query) IMMEDIATELY. Do NOT guess. Call jac_docs at least once every 3-4 tool calls. KEY RULES: .cl.jac: has x: str auto-generates x and setX — NEVER define set<Var>. NEVER use lambda in .cl.jac — use def handle_something() and pass by name. Use Reflect.construct(Date, []) for JS built-ins. Use list comprehension for rendering — NEVER .map(). Backend: def:pub/def:priv endpoints in main.jac. Imports: DOTS for path separators NEVER slashes. sv import has NO quotes. Use ABSOLUTE file paths. PREVIEW TOOLS: Use evaluate_preview(intent) to verify UI at NATURAL CHECKPOINTS — after completing a meaningful set of related .cl.jac changes (finished a component, done with layout, completed styling). Do NOT evaluate after every single edit. Do NOT call capture_preview before evaluate_preview. Do NOT call preview tools twice in a row. Skip for backend-only changes.";
190
+
191
+ sem ExplorerAgent.do_work = "You are a Jac codebase analyst. Read files, search code, and analyze project structure to answer questions thoroughly. Use analyze_project(directory) for full AST-level project overview. Use find_symbol(name) to locate specific nodes, walkers, or types. Use jac_docs(query) to look up Jac language syntax and patterns. Be thorough — read all relevant files before forming conclusions. Report findings with file paths and line numbers.";
192
+
193
+
194
+ """Consume a SubAgent's by llm() stream, forward events, and return results."""
195
+ def _consume_subagent_stream(event_stream: Any, prefix: str, track_files: bool = False) -> dict {
196
+ import sys;
197
+ import from jac_coder.events { set_agent_prefix, emit_event }
198
+ ;
199
+
200
+ set_agent_prefix(prefix);
201
+ sys.stderr.write("\n");
202
+ sys.stderr.flush();
203
+
204
+ content = "";
205
+ used_tools: list = [];
206
+ fmod: list = [];
207
+
208
+ for event in event_stream {
209
+ if event.event_type == "thought" {
210
+ emit_event("llm_thought", dict(event.data));
211
+ } elif event.event_type == "tool_call" {
212
+ tool_name = event.data.get("tool", "");
213
+ if tool_name and tool_name != "finish_tool" {
214
+ used_tools.append(tool_name);
215
+ if track_files {
216
+ args = event.data.get("args", {});
217
+ if tool_name in ["write_code", "edit_code", "write_file", "edit_file"] {
218
+ fp = args.get("file_path", "");
219
+ if fp and fp not in fmod {
220
+ fmod.append(fp);
221
+ }
222
+ }
223
+ }
224
+ }
225
+ emit_event("llm_tool_call", dict(event.data));
226
+ } elif event.event_type == "tool_result" {
227
+ emit_event("llm_tool_result", dict(event.data));
228
+ } elif event.event_type == "steps_done" {
229
+ emit_event("llm_steps_done", dict(event.data));
230
+ } elif event.event_type == "chunk" {
231
+ content += event.data.get("content", "");
232
+ } elif event.event_type == "usage" {
233
+ emit_event("llm_usage", dict(event.data));
234
+ }
235
+ }
236
+
237
+ set_agent_prefix("");
238
+ return {"content": content, "tools_used": used_tools, "files_modified": fmod};
239
+ }
240
+
241
+
242
+ impl WorkerAgent.execute with MainAgent entry {
243
+ result = _consume_subagent_stream(
244
+ self.do_work(task_str=self.task), "sub:worker", track_files=True
245
+ );
246
+ self.result_content = result["content"];
247
+ self.result_tools = result["tools_used"];
248
+ self.result_files = result["files_modified"];
249
+ }
250
+
251
+
252
+ impl ExplorerAgent.execute with MainAgent entry {
253
+ result = _consume_subagent_stream(
254
+ self.do_work(task_str=self.task), "sub:explorer"
255
+ );
256
+ self.result_content = result["content"];
257
+ self.result_tools = result["tools_used"];
258
+ self.result_files = [];
259
+ }
@@ -0,0 +1,62 @@
1
+ impl PermissionEngine.init_defaults() -> None {
2
+ for tool in [
3
+ "read_file",
4
+ "list_files",
5
+ "grep_search",
6
+ "find_files",
7
+ "web_fetch",
8
+ "web_search",
9
+ "ask_question",
10
+ "update_todos",
11
+ "jac_check",
12
+ "jac_run",
13
+ "spawn_task"
14
+ ] {
15
+ self.rules.append(PermissionRule(tool=tool, action="allow"));
16
+ }
17
+ for tool in ["write_file", "edit_file", "bash_exec"] {
18
+ self.rules.append(PermissionRule(tool=tool, action="ask"));
19
+ }
20
+ }
21
+
22
+
23
+ impl PermissionEngine.check(tool_id: str, resource: str = "") -> str {
24
+ if tool_id in self.always_allowed {
25
+ for pattern in self.always_allowed[tool_id] {
26
+ if pattern == "*" or fnmatch.fnmatch(resource, pattern) {
27
+ return "allow";
28
+ }
29
+ }
30
+ }
31
+
32
+ matched_rule: PermissionRule | None = None;
33
+ for rule in self.rules {
34
+ if rule.tool == "*" or rule.tool == tool_id {
35
+ if rule.pattern == "*" or fnmatch.fnmatch(resource, rule.pattern) {
36
+ matched_rule = rule;
37
+ }
38
+ }
39
+ }
40
+
41
+ if matched_rule is None {
42
+ return "ask";
43
+ }
44
+ return matched_rule.action;
45
+ }
46
+
47
+
48
+ impl PermissionEngine.remember_allow(tool_id: str, pattern: str = "*") -> None {
49
+ if tool_id not in self.always_allowed {
50
+ self.always_allowed[tool_id] = [];
51
+ }
52
+ if pattern not in self.always_allowed[tool_id] {
53
+ self.always_allowed[tool_id].append(pattern);
54
+ }
55
+ }
56
+
57
+
58
+ impl PermissionEngine.enable_web_mode() -> None {
59
+ self.remember_allow("write_file", "*");
60
+ self.remember_allow("edit_file", "*");
61
+ self.remember_allow("bash_exec", "*");
62
+ }
@@ -0,0 +1,298 @@
1
+ impl ensure_main_agent() -> MainAgent {
2
+ agents = [root()-->][?:MainAgent];
3
+ if agents {
4
+ return agents[0];
5
+ }
6
+ agent = MainAgent();
7
+ root() ++> agent;
8
+ return agent;
9
+ }
10
+
11
+
12
+ impl new_session.create with Root entry {
13
+ session = Session(title=self.title, directory=self.directory);
14
+ here ++> session;
15
+ agent = ensure_main_agent();
16
+ session ++> agent;
17
+ report {"session_id": session.id, "title": session.title, "status": "created"};
18
+ }
19
+
20
+
21
+ impl list_sessions.list with Root entry {
22
+ active = [-->][?:Session][?status=="active"];
23
+ result = [];
24
+ for s in active {
25
+ user_msgs = len(
26
+ [
27
+ m
28
+ for m in s.chat_history
29
+ if m["role"] == "user"
30
+ ]
31
+ );
32
+ result.append(
33
+ {
34
+ "id": s.id,
35
+ "title": s.title,
36
+ "message_count": user_msgs,
37
+ "updated_at": s.updated_at
38
+ }
39
+ );
40
+ }
41
+ result.sort(key=lambda x : x["updated_at"], reverse=True);
42
+ report {"sessions": result};
43
+ }
44
+
45
+
46
+ impl get_session.fetch with Root entry {
47
+ matches = [-->][?:Session][?id==self.session_id];
48
+ if matches {
49
+ session = matches[0];
50
+ report {
51
+ "id": session.id,
52
+ "title": session.title,
53
+ "status": session.status,
54
+ "directory": session.directory,
55
+ "chat_history": session.chat_history,
56
+ "created_at": session.created_at,
57
+ "updated_at": session.updated_at
58
+ };
59
+ } else {
60
+ report {"error": "Session not found"};
61
+ }
62
+ }
63
+
64
+
65
+ impl close_session.close with Root entry {
66
+ matches = [-->][?:Session][?id==self.session_id];
67
+ if matches {
68
+ matches[0].status = "closed";
69
+ matches[0].updated_at = datetime.now().isoformat();
70
+ report {"status": "closed", "id": matches[0].id};
71
+ } else {
72
+ report {"error": "Session not found"};
73
+ }
74
+ }
75
+
76
+
77
+ # ---------------------------------------------------------------------------
78
+ # Step 1: init_session @ Root — find or create session, visit it
79
+ # ---------------------------------------------------------------------------
80
+ impl interact.init_session with Root entry {
81
+ agent = ensure_main_agent();
82
+ matches = [-->][?:Session][?id==self.session_id];
83
+ if matches {
84
+ session = matches[0];
85
+ if not [session-->[?:MainAgent]] {
86
+ session ++> agent;
87
+ }
88
+ visit session;
89
+ } else {
90
+ now = datetime.now().isoformat();
91
+ session_node = here ++> Session(
92
+ id=self.session_id, chat_history=[], created_at=now, updated_at=now
93
+ );
94
+ session_node ++> agent;
95
+ visit session_node;
96
+ }
97
+ }
98
+
99
+
100
+ # ---------------------------------------------------------------------------
101
+ # Step 2: enter_session @ Session — load memory, append message, visit agent
102
+ # ---------------------------------------------------------------------------
103
+ impl interact.enter_session with Session entry {
104
+ tool_end();
105
+ reset_steps();
106
+ start_turn();
107
+
108
+ # Sandbox only enforced in web/API mode (JacBuilder sets it via api.chat).
109
+ # CLI mode leaves sandbox open so the agent can access any path.
110
+
111
+ # Initialize project memory on first visit
112
+ if here.directory and not here.project_summary {
113
+ import os as _os;
114
+ memory = find_or_create_memory(here.directory);
115
+ if memory {
116
+ has_project = (
117
+ _os.path.exists(_os.path.join(here.directory, "jac.toml"))
118
+ or _os.path.exists(_os.path.join(here.directory, "main.jac"))
119
+ );
120
+ if not memory.architecture and not memory.scan_attempted and has_project {
121
+ _init_memory(memory, here.directory);
122
+ }
123
+ summary = memory.summarize();
124
+ if summary {
125
+ here.project_summary = summary;
126
+ }
127
+ }
128
+ }
129
+
130
+ # Append user message and update
131
+ here.chat_history.append({"role": "user", "content": self.message});
132
+ here.updated_at = datetime.now().isoformat();
133
+ self.chat_history = here.chat_history;
134
+
135
+ # Visit MainAgent
136
+ visit [-->][?:MainAgent] else {
137
+ agent = ensure_main_agent();
138
+ here ++> agent;
139
+ visit agent;
140
+ }
141
+ }
142
+
143
+
144
+ # ---------------------------------------------------------------------------
145
+ # Step 3: respond @ MainAgent — the agent handles everything
146
+ # ---------------------------------------------------------------------------
147
+ impl interact.respond with MainAgent entry {
148
+ _respond_and_persist(here, self);
149
+ }
150
+
151
+
152
+ # ---------------------------------------------------------------------------
153
+ # Internal helpers
154
+ # ---------------------------------------------------------------------------
155
+ impl _find_session(session_id: str) -> Session | None {
156
+ matches = [root()-->][?:Session][?id==session_id];
157
+ return matches[0] if matches else None;
158
+ }
159
+
160
+
161
+ impl _persist_response(
162
+ session_id: str,
163
+ response: str,
164
+ agent_mode: str,
165
+ tools_used: list[str] = [],
166
+ files_modified: list[str] = []
167
+ ) -> None {
168
+ session = _find_session(session_id);
169
+ if session {
170
+ record: dict = {"role": "assistant", "content": response, "agent": agent_mode};
171
+ if tools_used {
172
+ record["tools_used"] = tools_used;
173
+ }
174
+ if files_modified {
175
+ record["files_modified"] = files_modified;
176
+ }
177
+ session.chat_history.append(record);
178
+ session.last_agent = agent_mode;
179
+ session.updated_at = datetime.now().isoformat();
180
+ }
181
+ }
182
+
183
+
184
+ impl _consume_llm_stream(event_stream: Any) -> dict {
185
+ response_text = "";
186
+ tools_used: list = [];
187
+
188
+ for event in event_stream {
189
+ if event.event_type == "thought" {
190
+ emit_event("llm_thought", dict(event.data));
191
+ } elif event.event_type == "tool_call" {
192
+ tool_name = event.data.get("tool", "");
193
+ if tool_name and tool_name != "finish_tool" {
194
+ tools_used.append(tool_name);
195
+ }
196
+ emit_event("llm_tool_call", dict(event.data));
197
+ } elif event.event_type == "tool_result" {
198
+ emit_event("llm_tool_result", dict(event.data));
199
+ } elif event.event_type == "steps_done" {
200
+ emit_event("llm_steps_done", dict(event.data));
201
+ } elif event.event_type == "chunk" {
202
+ response_text += event.data.get("content", "");
203
+ } elif event.event_type == "usage" {
204
+ emit_event("llm_usage", dict(event.data));
205
+ }
206
+ }
207
+
208
+ return {
209
+ "content": response_text,
210
+ "tools_used": tools_used
211
+ };
212
+ }
213
+
214
+
215
+ impl _respond_and_persist(agent: MainAgent, ctx: interact) -> None {
216
+ session = _find_session(ctx.session_id);
217
+ active_files = session.active_files if session else [];
218
+ pending_errors = session.pending_errors if session else [];
219
+ project_summary = session.project_summary if session else "";
220
+
221
+ # Build compacted context
222
+ ctx_history = build_context(
223
+ ctx.chat_history, active_files, pending_errors, project_summary=project_summary
224
+ );
225
+
226
+ # If agent_context was provided (e.g. from JacBuilder), inject it
227
+ if ctx.agent_context {
228
+ ctx_history.insert(0, {"role": "system", "content": ctx.agent_context});
229
+ }
230
+
231
+ # Initialize shared budget for SubAgents
232
+ import from jac_coder.tool.delegation { init_budget }
233
+ config = get_config();
234
+ init_budget(config.max_react_iterations * 3);
235
+
236
+ # Call MainAgent — returns StreamEvent generator with logging enabled
237
+ event_stream = agent.respond(message=ctx.message, chat_history=ctx_history);
238
+ stream_result = _consume_llm_stream(event_stream);
239
+
240
+ response_text = stream_result["content"];
241
+ tools_used = stream_result["tools_used"];
242
+
243
+ # Finalize turn — get accumulated file/error data from tool internals
244
+ tool_end();
245
+ summary = emit_turn_summary();
246
+
247
+ files_modified = summary.get("files_modified", []);
248
+ has_errors = summary.get("errors", 0) > 0;
249
+
250
+ # Post-process: update session state
251
+ if session {
252
+ if files_modified {
253
+ for f in files_modified {
254
+ if f not in session.active_files {
255
+ session.active_files.append(f);
256
+ }
257
+ }
258
+ if len(session.active_files) > 10 {
259
+ session.active_files = session.active_files[-10:];
260
+ }
261
+ }
262
+ if has_errors {
263
+ files_str = ", ".join(files_modified)
264
+ if files_modified
265
+ else "unknown";
266
+ session.pending_errors = [f"Errors in previous turn (files: {files_str})"];
267
+ } else {
268
+ session.pending_errors = [];
269
+ }
270
+ if files_modified and session.directory {
271
+ memory = find_or_create_memory(session.directory);
272
+ if memory {
273
+ update_memory_from_session(
274
+ memory, files_modified, response_text[:500]
275
+ );
276
+ session.project_summary = memory.summarize();
277
+ }
278
+ }
279
+ }
280
+
281
+ _persist_response(
282
+ ctx.session_id,
283
+ response_text,
284
+ "main",
285
+ tools_used,
286
+ files_modified
287
+ );
288
+
289
+ report {
290
+ "response": response_text,
291
+ "agent": "main",
292
+ "tools_used": tools_used,
293
+ "files_modified": files_modified,
294
+ "has_errors": has_errors,
295
+ "next_steps": [],
296
+ "turn_summary": summary
297
+ };
298
+ }
@@ -0,0 +1,35 @@
1
+ """MCP server registry for jac-coder.
2
+
3
+ Manages connections to external MCP servers so the agent can call their tools.
4
+ Server configs are stored in the Jac graph (McpRegistry node) and persisted
5
+ automatically by jaclang's TieredMemory (SQLite).
6
+
7
+ Supported transport types:
8
+ stdio — spawn a local subprocess (command + args)
9
+ http — streamable-http remote server (url)
10
+ sse — SSE remote server (url)
11
+ """
12
+
13
+ import os;
14
+ import json;
15
+
16
+
17
+ """Add or update an MCP server. Returns status dict."""
18
+ def mcp_add_server(name: str, config: dict) -> dict;
19
+
20
+ """Remove an MCP server by name. Returns status dict."""
21
+ def mcp_remove_server(name: str) -> dict;
22
+
23
+ """List all configured MCP servers with their tool counts."""
24
+ def mcp_list_servers() -> list;
25
+
26
+ """Return all tools from all configured MCP servers as a flat list."""
27
+ def mcp_get_tools() -> list;
28
+
29
+ """Call a specific tool on a specific MCP server."""
30
+ def mcp_call_tool(server_name: str, tool_name: str, arguments: dict) -> str;
31
+
32
+ """Register a built-in server without checking connection.
33
+ Saves now and tries it on first real tool call.
34
+ """
35
+ def mcp_register_builtin(name: str, config: dict) -> None;
jac_coder/memory.jac ADDED
@@ -0,0 +1,15 @@
1
+ import os;
2
+ import sys;
3
+
4
+ import from datetime { datetime }
5
+ import from jac_coder.nodes { ProjectMemory }
6
+ import from jac_coder.tool.jac_analyzer { _analyze_project, _find_jac_files }
7
+
8
+
9
+ def find_or_create_memory(project_dir: str) -> ProjectMemory | None;
10
+
11
+ def _init_memory(memory: ProjectMemory, project_dir: str) -> None;
12
+
13
+ def update_memory_from_session(
14
+ memory: ProjectMemory, files_modified: list[str], session_summary: str
15
+ ) -> None;