jac-coder 0.2.1__tar.gz → 0.2.3__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.
- {jac_coder-0.2.1 → jac_coder-0.2.3}/PKG-INFO +1 -1
- {jac_coder-0.2.1 → jac_coder-0.2.3}/README.md +2 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/api.impl.jac +81 -49
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/api.jac +3 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/core/nodes.impl.jac +13 -5
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/core/nodes.jac +15 -1
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/core/walkers.impl.jac +51 -33
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/infra/config.impl.jac +1 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/infra/config.jac +1 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/infra/mcp_manager.impl.jac +52 -20
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/infra/mcp_manager.jac +7 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/runtime/cost_tracker.impl.jac +6 -0
- jac_coder-0.2.3/jac_coder/runtime/file_logger.jac +235 -0
- jac_coder-0.2.3/jac_coder/runtime/permission.impl.jac +171 -0
- jac_coder-0.2.3/jac_coder/runtime/permission.jac +55 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/runtime/prompt.jac +16 -10
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/runtime/skills.impl.jac +28 -4
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/server.jac +58 -12
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/skills/ROADMAP.md +1 -0
- jac_coder-0.2.3/jac_coder/skills/jac-cl-auth/SKILL.md +134 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/skills/jac-cl-components/SKILL.md +34 -10
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/skills/jac-cl-organization/SKILL.md +26 -1
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/skills/jac-cl-routing/SKILL.md +2 -5
- jac_coder-0.2.3/jac_coder/skills/jac-cl-styling/SKILL.md +51 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/skills/jac-core-cheatsheet/SKILL.md +2 -2
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/skills/jac-fullstack-patterns/SKILL.md +2 -1
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/skills/jac-node-edge-patterns/SKILL.md +24 -3
- jac_coder-0.2.3/jac_coder/skills/jac-npm-packages/SKILL.md +96 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/skills/jac-scaffold/SKILL.md +5 -6
- jac_coder-0.2.3/jac_coder/skills/jac-shadcn-components/SKILL.md +340 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/skills/jac-sv-auth/SKILL.md +9 -9
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/skills/jac-sv-endpoints/SKILL.md +8 -8
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/skills/jac-sv-persistence/SKILL.md +15 -15
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/skills/jac-types/SKILL.md +1 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/skills/jac-walker-patterns/SKILL.md +5 -5
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/tool/run/guarded.impl.jac +32 -2
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/tool/run/guarded.jac +4 -1
- jac_coder-0.2.3/jac_coder/tool/run/shell.impl.jac +206 -0
- jac_coder-0.2.3/jac_coder/tool/run/shell.jac +23 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder.egg-info/PKG-INFO +1 -1
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder.egg-info/SOURCES.txt +4 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/pyproject.toml +1 -1
- jac_coder-0.2.1/jac_coder/runtime/permission.impl.jac +0 -62
- jac_coder-0.2.1/jac_coder/runtime/permission.jac +0 -19
- jac_coder-0.2.1/jac_coder/skills/jac-cl-auth/SKILL.md +0 -93
- jac_coder-0.2.1/jac_coder/tool/run/shell.impl.jac +0 -152
- jac_coder-0.2.1/jac_coder/tool/run/shell.jac +0 -13
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/__init__.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/__init__.py +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/cli_entry.py +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/core/__init__.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/core/walkers.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/infra/__init__.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/infra/kv.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/lib/__init__.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/lib/coder.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/lib/coder.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/runtime/__init__.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/runtime/context.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/runtime/context.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/runtime/cost_tracker.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/runtime/events.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/runtime/memory.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/runtime/memory.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/runtime/prompt.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/runtime/skills.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/serve_entry.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/skills/jac-by-llm/SKILL.md +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/skills/jac-has-fields/SKILL.md +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/skills/jac-impl-files/SKILL.md +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/tool/__init__.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/tool/git.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/tool/git.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/tool/mcp.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/tool/mcp.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/tool/meta/__init__.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/tool/meta/delegation.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/tool/meta/delegation.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/tool/meta/question.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/tool/meta/question.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/tool/meta/task.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/tool/meta/task.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/tool/meta/think.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/tool/meta/think.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/tool/meta/todo.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/tool/meta/todo.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/tool/meta/validate.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/tool/meta/validate.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/tool/net/__init__.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/tool/net/preview.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/tool/net/preview.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/tool/net/web.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/tool/net/web.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/tool/read/__init__.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/tool/read/filesystem.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/tool/read/filesystem.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/tool/read/jac_analyzer.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/tool/read/jac_analyzer.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/tool/read/load_jac_skill.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/tool/read/load_jac_skill.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/tool/read/search.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/tool/read/search.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/tool/run/__init__.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/tool/run/jac_tools.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/tool/run/jac_tools.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/tool/write/__init__.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/tool/write/checked.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/tool/write/checked.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/util/__init__.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/util/colors.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/util/sandbox.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/util/sandbox.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/util/tool_output.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder/util/tool_output.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder.egg-info/dependency_links.txt +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder.egg-info/entry_points.txt +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder.egg-info/requires.txt +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/jac_coder.egg-info/top_level.txt +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.3}/setup.cfg +0 -0
|
@@ -156,3 +156,5 @@ jac-code/
|
|
|
156
156
|
├── vscode-jac-coder/ # VS Code extension (TypeScript)
|
|
157
157
|
└── docs/ # ARCHITECTURE, ROADMAP, PROGRESS, LIBRARY_MODE
|
|
158
158
|
```
|
|
159
|
+
|
|
160
|
+
<img width="2661" height="1091" alt="image" src="https://github.com/user-attachments/assets/a314087f-ae94-4c8b-8899-1e4ff3eb465c" />
|
|
@@ -244,13 +244,6 @@ impl chat(
|
|
|
244
244
|
# Memory initialization
|
|
245
245
|
_ensure_memory(session, work_dir);
|
|
246
246
|
|
|
247
|
-
# Add user message
|
|
248
|
-
user_msg_record: dict = {"role": "user", "content": message};
|
|
249
|
-
if images and len(images) > 0 {
|
|
250
|
-
user_msg_record["has_images"] = True;
|
|
251
|
-
user_msg_record["image_count"] = len(images);
|
|
252
|
-
}
|
|
253
|
-
session.chat_history.append(user_msg_record);
|
|
254
247
|
session.updated_at = datetime.now().isoformat();
|
|
255
248
|
|
|
256
249
|
# Get MainAgent — always resolve fresh (cached refs go stale in jac start)
|
|
@@ -260,15 +253,33 @@ impl chat(
|
|
|
260
253
|
config = get_config();
|
|
261
254
|
_init_spawn_budget(config.max_react_iterations * 3);
|
|
262
255
|
|
|
263
|
-
#
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
256
|
+
# Per-turn context gets folded into the user message; we can't add
|
|
257
|
+
# role=system dicts to session.chat_history without them persisting stale.
|
|
258
|
+
prefix_parts: list[str] = [];
|
|
259
|
+
|
|
260
|
+
if session.project_summary {
|
|
261
|
+
is_progress_file = session.project_summary.lstrip().startswith("#");
|
|
262
|
+
label = (
|
|
263
|
+
"Project progress (.jaccoder/progress.md)"
|
|
264
|
+
if is_progress_file
|
|
265
|
+
else "Project context"
|
|
266
|
+
);
|
|
267
|
+
prefix_parts.append(
|
|
268
|
+
f"[INTERNAL — do NOT present this to the user unless they ask about the project. This is your background knowledge for making informed decisions.]\n{label}:\n{session.project_summary}"
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
if session.active_files {
|
|
272
|
+
prefix_parts.append("Active files: " + ", ".join(session.active_files));
|
|
273
|
+
}
|
|
274
|
+
if session.pending_errors {
|
|
275
|
+
prefix_parts.append("Pending errors:\n" + "\n".join(session.pending_errors));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
mcp_msg = _get_mcp_context_msg();
|
|
279
|
+
if mcp_msg {
|
|
280
|
+
prefix_parts.append(str(mcp_msg.get("content", "")));
|
|
281
|
+
}
|
|
270
282
|
|
|
271
|
-
# Inject mode-aware workflow modules (dynamic prompt assembly)
|
|
272
283
|
import from jac_coder.runtime.prompt { select_prompt_modules as _select_modules }
|
|
273
284
|
workflow_modules = _select_modules(
|
|
274
285
|
last_mode_hint=session.last_mode_hint,
|
|
@@ -277,20 +288,28 @@ impl chat(
|
|
|
277
288
|
chat_history=session.chat_history
|
|
278
289
|
);
|
|
279
290
|
if workflow_modules {
|
|
280
|
-
|
|
291
|
+
prefix_parts.append(workflow_modules);
|
|
281
292
|
}
|
|
282
293
|
|
|
283
|
-
# Inject agent_context if provided (e.g. from JacBuilder)
|
|
284
294
|
if agent_context {
|
|
285
|
-
|
|
295
|
+
prefix_parts.append(agent_context);
|
|
286
296
|
}
|
|
287
297
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
298
|
+
if edit_mode == "plan" {
|
|
299
|
+
prefix_parts.append(
|
|
300
|
+
"You are in PLAN MODE. Your task is to produce a detailed, step-by-step plan "
|
|
301
|
+
"for the user's request. Do NOT write, edit, or run any files. "
|
|
302
|
+
"Describe every file you would create or modify, what changes you would make, "
|
|
303
|
+
"and why. The user will review your plan and click 'Execute Plan' to apply it."
|
|
304
|
+
);
|
|
292
305
|
}
|
|
293
306
|
|
|
307
|
+
composed_message = (
|
|
308
|
+
"\n\n---\n\n".join(prefix_parts) + "\n\n---\n\n" + message
|
|
309
|
+
if prefix_parts
|
|
310
|
+
else message
|
|
311
|
+
);
|
|
312
|
+
|
|
294
313
|
# Early exit if already aborted before LLM starts
|
|
295
314
|
if is_abort_requested() {
|
|
296
315
|
tool_end();
|
|
@@ -306,24 +325,6 @@ impl chat(
|
|
|
306
325
|
};
|
|
307
326
|
}
|
|
308
327
|
|
|
309
|
-
# Plan mode — inject instruction to write a plan only, no file writes.
|
|
310
|
-
# The UI will show "Execute Plan" when done; execution reruns with edit_mode="auto".
|
|
311
|
-
if edit_mode == "plan" {
|
|
312
|
-
plan_msg: dict = {
|
|
313
|
-
"role": "system",
|
|
314
|
-
"content": (
|
|
315
|
-
"You are in PLAN MODE. Your task is to produce a detailed, step-by-step plan "
|
|
316
|
-
"for the user's request. Do NOT write, edit, or run any files. "
|
|
317
|
-
"Describe every file you would create or modify, what changes you would make, "
|
|
318
|
-
"and why. The user will review your plan and click 'Execute Plan' to apply it."
|
|
319
|
-
)
|
|
320
|
-
};
|
|
321
|
-
ctx_with_plan: list[dict] = [plan_msg];
|
|
322
|
-
ctx_with_plan.extend(ctx_history);
|
|
323
|
-
ctx_history = ctx_with_plan;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
# Call MainAgent — returns StreamEvent generator with logging enabled
|
|
327
328
|
# If user attached images, convert to byllm Image object and pass as user_image
|
|
328
329
|
user_image = None;
|
|
329
330
|
if images and len(images) > 0 {
|
|
@@ -339,7 +340,10 @@ impl chat(
|
|
|
339
340
|
sys.stderr.write(f"[api] Failed to create Image from user attachment: {img_err}\n");
|
|
340
341
|
}
|
|
341
342
|
}
|
|
342
|
-
|
|
343
|
+
|
|
344
|
+
main_agent._conv = session.chat_history;
|
|
345
|
+
|
|
346
|
+
event_stream = main_agent.respond(message=composed_message, user_image=user_image);
|
|
343
347
|
stream_result = _consume_llm_stream(event_stream);
|
|
344
348
|
|
|
345
349
|
response_text = stream_result["content"];
|
|
@@ -379,16 +383,22 @@ impl chat(
|
|
|
379
383
|
}
|
|
380
384
|
}
|
|
381
385
|
|
|
382
|
-
#
|
|
386
|
+
# byLLM already appended the assistant/tool turns; patch UI metadata
|
|
387
|
+
# onto the trailing assistant entry. Skip if aborted.
|
|
383
388
|
if not is_abort_requested() {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
389
|
+
for idx in range(len(session.chat_history) - 1, -1, -1) {
|
|
390
|
+
message_entry = session.chat_history[idx];
|
|
391
|
+
if message_entry.get("role") == "assistant" and "agent" not in message_entry {
|
|
392
|
+
message_entry["agent"] = "main";
|
|
393
|
+
if tools_used {
|
|
394
|
+
message_entry["tools_used"] = tools_used;
|
|
395
|
+
}
|
|
396
|
+
if files_modified {
|
|
397
|
+
message_entry["files_modified"] = files_modified;
|
|
398
|
+
}
|
|
399
|
+
break;
|
|
400
|
+
}
|
|
390
401
|
}
|
|
391
|
-
session.chat_history.append(record);
|
|
392
402
|
}
|
|
393
403
|
session.last_agent = "main";
|
|
394
404
|
session.updated_at = datetime.now().isoformat();
|
|
@@ -514,6 +524,28 @@ impl api_get_model() -> dict {
|
|
|
514
524
|
return _get_model();
|
|
515
525
|
}
|
|
516
526
|
|
|
527
|
+
impl api_check_local_setup(alias: str) -> dict {
|
|
528
|
+
runtime: bool = False;
|
|
529
|
+
try {
|
|
530
|
+
import llama_cpp;
|
|
531
|
+
runtime = True;
|
|
532
|
+
} except ImportError { }
|
|
533
|
+
|
|
534
|
+
model: bool = False;
|
|
535
|
+
valid_alias: bool = False;
|
|
536
|
+
try {
|
|
537
|
+
import from byllm.local_runtime { LOCAL_MODELS }
|
|
538
|
+
import from byllm.model_cache { is_downloaded }
|
|
539
|
+
spec = LOCAL_MODELS.get(alias);
|
|
540
|
+
if spec is not None {
|
|
541
|
+
valid_alias = True;
|
|
542
|
+
model = is_downloaded(alias, spec);
|
|
543
|
+
}
|
|
544
|
+
} except Exception { }
|
|
545
|
+
|
|
546
|
+
return {"runtime": runtime, "model": model, "valid_alias": valid_alias};
|
|
547
|
+
}
|
|
548
|
+
|
|
517
549
|
|
|
518
550
|
# ---------------------------------------------------------------------------
|
|
519
551
|
# MCP management API
|
|
@@ -78,6 +78,9 @@ def api_set_model(model: str) -> dict;
|
|
|
78
78
|
"""Get current model info."""
|
|
79
79
|
def api_get_model() -> dict;
|
|
80
80
|
|
|
81
|
+
"""Check if local model runtime and model weights are ready for a given alias."""
|
|
82
|
+
def api_check_local_setup(alias: str) -> dict;
|
|
83
|
+
|
|
81
84
|
# --- MCP management API ---
|
|
82
85
|
"""Add or update an MCP server."""
|
|
83
86
|
def api_mcp_add(name: str, config: dict) -> dict;
|
|
@@ -140,11 +140,19 @@ Build breadth-first — get all files written and app running before polishing.
|
|
|
140
140
|
## Workflow
|
|
141
141
|
1. Read .jaccoder/progress.md if it exists, update it as you go.
|
|
142
142
|
2. analyze_project(dir) for existing projects.
|
|
143
|
-
3.
|
|
144
|
-
4.
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
143
|
+
3. Build bottom-up: services → hooks → components → layout.
|
|
144
|
+
4. **If `components/ui/` exists AND jac.toml has `[jac-shadcn]` (jac-shadcn project):**
|
|
145
|
+
- Load `jac-shadcn-components` skill BEFORE writing any UI.
|
|
146
|
+
- Load `jac-cl-styling` for conditional class and cn() patterns.
|
|
147
|
+
- `styles/global.css` and `jac.toml` are pre-configured — do NOT recreate or modify them.
|
|
148
|
+
- Import UI from `components/ui/` instead of writing raw primitive components. No inline styles.
|
|
149
|
+
**Otherwise (standard project):**
|
|
150
|
+
- jac.toml needs `[plugins.client.vite]` with tailwindcss. Run `jac install` after toml changes.
|
|
151
|
+
- styles/global.css: `@import "tailwindcss"`, @theme tokens. In main.jac: `cl import ".styles.global.css";`
|
|
152
|
+
- Load `jac-cl-styling` when writing Tailwind classes or dynamic styles.
|
|
153
|
+
- Load `jac-npm-packages` when adding third-party npm packages.
|
|
154
|
+
5. After all files: jac start --dev main.jac (background=True).
|
|
155
|
+
6. browser_validate(url) → follow FAIL action instructions.
|
|
148
156
|
|
|
149
157
|
Load the relevant `load_jac_skill(name)` from `<jac-skills-available>` BEFORE writing Jac. Tools catch obvious anti-patterns — trust BLOCKED messages.
|
|
150
158
|
""";
|
|
@@ -82,6 +82,16 @@ obj SessionLearnings {
|
|
|
82
82
|
# ---------------------------------------------------------------------------
|
|
83
83
|
node McpRegistry {
|
|
84
84
|
has servers: dict[str, dict] = {};
|
|
85
|
+
has disconnected_servers: list[str] = [];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# ---------------------------------------------------------------------------
|
|
90
|
+
# UserPermissionRules — persists per-user "always allow" decisions in the graph
|
|
91
|
+
# so they are visible across pods (B3 / horizontal scaling).
|
|
92
|
+
node UserPermissionRules {
|
|
93
|
+
has user_id: str = "",
|
|
94
|
+
always_allowed: dict[str, list[str]] = {};
|
|
85
95
|
}
|
|
86
96
|
|
|
87
97
|
|
|
@@ -145,7 +155,10 @@ node Session {
|
|
|
145
155
|
# MainAgent — the orchestrator node
|
|
146
156
|
# ---------------------------------------------------------------------------
|
|
147
157
|
node MainAgent {
|
|
148
|
-
|
|
158
|
+
# Bound to Session.chat_history by the walker before each call.
|
|
159
|
+
has _conv: list[dict] = [];
|
|
160
|
+
|
|
161
|
+
def respond(message: str, user_image: Image | None = None) -> str by llm(
|
|
149
162
|
tools=[
|
|
150
163
|
# Think — explicit reasoning before acting or delegating
|
|
151
164
|
think,
|
|
@@ -184,6 +197,7 @@ node MainAgent {
|
|
|
184
197
|
# MCP — call tools from connected MCP servers
|
|
185
198
|
mcp_call
|
|
186
199
|
],
|
|
200
|
+
conversation=self._conv,
|
|
187
201
|
on_iteration=_iteration_hook,
|
|
188
202
|
max_react_iterations=80,
|
|
189
203
|
temperature=0.2,
|
|
@@ -173,8 +173,7 @@ impl interact.enter_session with Session entry {
|
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
-
#
|
|
177
|
-
here.chat_history.append({"role": "user", "content": self.message});
|
|
176
|
+
# byLLM appends the user message from the `respond(message=...)` arg.
|
|
178
177
|
here.updated_at = datetime.now().isoformat();
|
|
179
178
|
self.chat_history = here.chat_history;
|
|
180
179
|
|
|
@@ -212,28 +211,27 @@ impl _persist_response(
|
|
|
212
211
|
files_modified: list[str] = [],
|
|
213
212
|
tool_records: list[dict] = []
|
|
214
213
|
) -> None {
|
|
214
|
+
# byLLM already appended the user/tool/assistant turns via _conv binding;
|
|
215
|
+
# we only patch JacCoder UI metadata onto the trailing assistant entry.
|
|
215
216
|
session = _find_session(session_id);
|
|
216
217
|
if not session {
|
|
217
218
|
return;
|
|
218
219
|
}
|
|
219
220
|
|
|
220
|
-
for
|
|
221
|
-
session.chat_history
|
|
222
|
-
|
|
223
|
-
"
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
221
|
+
for idx in range(len(session.chat_history) - 1, -1, -1) {
|
|
222
|
+
message_entry = session.chat_history[idx];
|
|
223
|
+
if message_entry.get("role") == "assistant" and "agent" not in message_entry {
|
|
224
|
+
message_entry["agent"] = agent_mode;
|
|
225
|
+
if tools_used {
|
|
226
|
+
message_entry["tools_used"] = tools_used;
|
|
227
|
+
}
|
|
228
|
+
if files_modified {
|
|
229
|
+
message_entry["files_modified"] = files_modified;
|
|
230
|
+
}
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
227
233
|
}
|
|
228
234
|
|
|
229
|
-
record: dict = {"role": "assistant", "content": response, "agent": agent_mode};
|
|
230
|
-
if tools_used {
|
|
231
|
-
record["tools_used"] = tools_used;
|
|
232
|
-
}
|
|
233
|
-
if files_modified {
|
|
234
|
-
record["files_modified"] = files_modified;
|
|
235
|
-
}
|
|
236
|
-
session.chat_history.append(record);
|
|
237
235
|
session.last_agent = agent_mode;
|
|
238
236
|
session.updated_at = datetime.now().isoformat();
|
|
239
237
|
}
|
|
@@ -314,45 +312,65 @@ impl _respond_and_persist(agent: MainAgent, ctx: interact) -> None {
|
|
|
314
312
|
pending_errors = session.pending_errors if session else [];
|
|
315
313
|
project_summary = session.project_summary if session else "";
|
|
316
314
|
|
|
317
|
-
#
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
315
|
+
# Per-turn context gets folded into the user message; we can't add
|
|
316
|
+
# role=system dicts to session.chat_history without them persisting stale.
|
|
317
|
+
prefix_parts: list[str] = [];
|
|
318
|
+
|
|
319
|
+
if project_summary {
|
|
320
|
+
is_progress_file = project_summary.lstrip().startswith("#");
|
|
321
|
+
label = (
|
|
322
|
+
"Project progress (.jaccoder/progress.md)"
|
|
323
|
+
if is_progress_file
|
|
324
|
+
else "Project context"
|
|
325
|
+
);
|
|
326
|
+
prefix_parts.append(
|
|
327
|
+
f"[INTERNAL — do NOT present this to the user unless they ask about the project. This is your background knowledge for making informed decisions.]\n{label}:\n{project_summary}"
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
if active_files {
|
|
331
|
+
prefix_parts.append("Active files: " + ", ".join(active_files));
|
|
332
|
+
}
|
|
333
|
+
if pending_errors {
|
|
334
|
+
prefix_parts.append("Pending errors:\n" + "\n".join(pending_errors));
|
|
335
|
+
}
|
|
321
336
|
|
|
322
|
-
# Inject the available-skills listing (names + descriptions only).
|
|
323
|
-
# The LLM picks which skills it needs and pulls bodies via load_jac_skill(name).
|
|
324
|
-
# Inserted FIRST so workflow_modules ends up above it in the final prompt,
|
|
325
|
-
# keeping the listing closer to the user message where attention is strongest.
|
|
326
337
|
import from jac_coder.runtime.skills { inject_skills_listing }
|
|
327
338
|
skill_listing = inject_skills_listing();
|
|
328
339
|
if skill_listing {
|
|
329
|
-
|
|
340
|
+
prefix_parts.append(skill_listing);
|
|
330
341
|
}
|
|
331
342
|
|
|
332
|
-
# Inject mode-aware workflow modules (dynamic prompt assembly)
|
|
333
343
|
import from jac_coder.runtime.prompt { select_prompt_modules }
|
|
334
344
|
workflow_modules = select_prompt_modules(
|
|
335
345
|
last_mode_hint=session.last_mode_hint if session else "",
|
|
336
346
|
message=ctx.message,
|
|
337
347
|
directory=session.directory if session else "",
|
|
338
|
-
chat_history=
|
|
348
|
+
chat_history=session.chat_history if session else []
|
|
339
349
|
);
|
|
340
350
|
if workflow_modules {
|
|
341
|
-
|
|
351
|
+
prefix_parts.append(workflow_modules);
|
|
342
352
|
}
|
|
343
353
|
|
|
344
|
-
# If agent_context was provided (e.g. from JacBuilder), inject it
|
|
345
354
|
if ctx.agent_context {
|
|
346
|
-
|
|
355
|
+
prefix_parts.append(ctx.agent_context);
|
|
347
356
|
}
|
|
348
357
|
|
|
358
|
+
composed_message = (
|
|
359
|
+
"\n\n---\n\n".join(prefix_parts) + "\n\n---\n\n" + ctx.message
|
|
360
|
+
if prefix_parts
|
|
361
|
+
else ctx.message
|
|
362
|
+
);
|
|
363
|
+
|
|
349
364
|
# Initialize shared budget for SubAgents
|
|
350
365
|
import from jac_coder.tool.meta.delegation { init_budget }
|
|
351
366
|
config = get_config();
|
|
352
367
|
init_budget(config.max_react_iterations * 3);
|
|
353
368
|
|
|
354
|
-
|
|
355
|
-
|
|
369
|
+
if session {
|
|
370
|
+
agent._conv = session.chat_history;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
event_stream = agent.respond(message=composed_message);
|
|
356
374
|
stream_result = _consume_llm_stream(event_stream);
|
|
357
375
|
|
|
358
376
|
response_text = stream_result["content"];
|
|
@@ -30,12 +30,20 @@ glob _sessions: dict = {}; # name -> ClientSession (open inside _mgr_loop)
|
|
|
30
30
|
glob _exit_stacks: dict = {}; # name -> AsyncExitStack (keeps transports alive)
|
|
31
31
|
glob _TOOLS_CACHE_KEY: str = "jc:mcp:tools";
|
|
32
32
|
glob _TOOLS_CACHE_TTL: int = 30; # seconds before tool list is re-fetched
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
|
|
34
|
+
impl _service_mode_active() -> bool {
|
|
35
|
+
return bool(
|
|
36
|
+
os.environ.get("JACCODER_WEB_MODE", "")
|
|
37
|
+
or os.environ.get("JACCODER_IDE_MODE", "")
|
|
38
|
+
);
|
|
39
|
+
}
|
|
35
40
|
|
|
36
41
|
|
|
37
42
|
"""Background thread: check PyPI for latest jac-mcp version (3s timeout, fails silently)."""
|
|
38
|
-
|
|
43
|
+
impl _check_jac_mcp_version() -> None {
|
|
44
|
+
if _service_mode_active() {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
39
47
|
try {
|
|
40
48
|
current: str = version("jac-mcp");
|
|
41
49
|
req = Request(
|
|
@@ -65,7 +73,13 @@ def _get_registry() -> Any {
|
|
|
65
73
|
target_anchor = edge_anchor.target;
|
|
66
74
|
target_anchor.populate();
|
|
67
75
|
if isinstance(target_anchor.archetype, McpRegistry) {
|
|
68
|
-
|
|
76
|
+
reg = target_anchor.archetype;
|
|
77
|
+
# Migration guard: nodes persisted before disconnected_servers was
|
|
78
|
+
# added to the schema won't have this attribute after deserialization.
|
|
79
|
+
if not hasattr(reg, "disconnected_servers") {
|
|
80
|
+
reg.disconnected_servers = [];
|
|
81
|
+
}
|
|
82
|
+
return reg;
|
|
69
83
|
}
|
|
70
84
|
} except Exception { }
|
|
71
85
|
}
|
|
@@ -286,31 +300,33 @@ impl mcp_add_server(name: str, config: dict) -> dict {
|
|
|
286
300
|
}
|
|
287
301
|
|
|
288
302
|
|
|
289
|
-
"""Close the connection and
|
|
303
|
+
"""Close the connection and mark the server disconnected in the graph."""
|
|
290
304
|
impl mcp_disconnect_server(name: str) -> dict {
|
|
291
305
|
# Disconnect only — close the active connection but keep the config.
|
|
292
306
|
# This applies to ALL servers (builtin and user-added) so they stay
|
|
293
307
|
# visible in the list and can be reconnected.
|
|
294
|
-
|
|
308
|
+
# Intent is persisted in McpRegistry.disconnected_servers so it survives
|
|
309
|
+
# pod restarts and is visible across all pods.
|
|
295
310
|
configs = _load_configs();
|
|
296
311
|
if name not in configs {
|
|
297
312
|
return {"error": f"Server '{name}' not found"};
|
|
298
313
|
}
|
|
299
|
-
|
|
314
|
+
reg = _get_registry();
|
|
315
|
+
if name not in reg.disconnected_servers {
|
|
316
|
+
reg.disconnected_servers = list(reg.disconnected_servers) + [name];
|
|
317
|
+
}
|
|
300
318
|
_submit(_close_connection(name));
|
|
301
319
|
kv_delete(_TOOLS_CACHE_KEY);
|
|
302
|
-
return {"status": "disconnected", "name": name};
|
|
320
|
+
return {"status": "disconnected", "name": name};
|
|
303
321
|
}
|
|
304
322
|
|
|
305
323
|
|
|
306
324
|
"""Reconnect a previously-disconnected MCP server."""
|
|
307
325
|
impl mcp_reconnect_server(name: str) -> dict {
|
|
308
|
-
global _disconnected_names;
|
|
309
326
|
configs = _load_configs();
|
|
310
327
|
if name not in configs {
|
|
311
328
|
return {"error": f"Server '{name}' not found"};
|
|
312
329
|
}
|
|
313
|
-
_disconnected_names.discard(name);
|
|
314
330
|
# Close any stale connection first, then reconnect via list_tools.
|
|
315
331
|
# Iterate configs to get a typed config value (subscript on untyped dict
|
|
316
332
|
# produces Unknown in the Jac type checker — the for pattern is used
|
|
@@ -321,9 +337,14 @@ impl mcp_reconnect_server(name: str) -> dict {
|
|
|
321
337
|
if n == name {
|
|
322
338
|
try {
|
|
323
339
|
tools = _submit(_async_list_tools(n, config));
|
|
340
|
+
# Only remove from disconnected_servers on success — keeps state
|
|
341
|
+
# consistent if connection fails (server stays disconnected).
|
|
342
|
+
reg = _get_registry();
|
|
343
|
+
if name in reg.disconnected_servers {
|
|
344
|
+
reg.disconnected_servers = [s for s in reg.disconnected_servers if s != name];
|
|
345
|
+
}
|
|
324
346
|
return {"status": "connected", "name": name};
|
|
325
347
|
} except Exception as e {
|
|
326
|
-
_disconnected_names.add(name);
|
|
327
348
|
return {"error": f"Failed to reconnect '{name}': {e}"};
|
|
328
349
|
}
|
|
329
350
|
}
|
|
@@ -334,15 +355,18 @@ impl mcp_reconnect_server(name: str) -> dict {
|
|
|
334
355
|
|
|
335
356
|
impl mcp_delete_server(name: str) -> dict {
|
|
336
357
|
# Permanently remove a user-added server from the registry.
|
|
337
|
-
# Built-in servers cannot be deleted
|
|
338
|
-
|
|
339
|
-
return {"error": f"Built-in server '{name}' cannot be deleted"};
|
|
340
|
-
}
|
|
358
|
+
# Built-in servers cannot be deleted — the builtin flag is stored as
|
|
359
|
+
# config["builtin"]=True inside McpRegistry.servers (graph-persisted),
|
|
341
360
|
configs = _load_configs();
|
|
342
361
|
if name not in configs {
|
|
343
362
|
return {"error": f"Server '{name}' not found"};
|
|
344
363
|
}
|
|
345
|
-
|
|
364
|
+
for (n, cfg) in configs.items() {
|
|
365
|
+
if n == name and cfg.get("builtin", False) {
|
|
366
|
+
return {"error": f"Built-in server '{name}' cannot be deleted"};
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
_submit(_close_connection(name));
|
|
346
370
|
configs.pop(name);
|
|
347
371
|
_save_configs(configs);
|
|
348
372
|
kv_delete(_TOOLS_CACHE_KEY);
|
|
@@ -353,6 +377,8 @@ impl mcp_delete_server(name: str) -> dict {
|
|
|
353
377
|
"""Return all registered servers with their connection status and tool counts."""
|
|
354
378
|
impl mcp_list_servers() -> list {
|
|
355
379
|
configs = _load_configs();
|
|
380
|
+
# Read disconnected intent from the graph — persisted, cross-pod consistent.
|
|
381
|
+
disconnected = _get_registry().disconnected_servers;
|
|
356
382
|
result: list = [];
|
|
357
383
|
for (name, config) in configs.items() {
|
|
358
384
|
entry: dict = {
|
|
@@ -363,7 +389,7 @@ impl mcp_list_servers() -> list {
|
|
|
363
389
|
"latest_version": _jac_mcp_update_info.get("latest", "") if name == "jac-mcp" else ""
|
|
364
390
|
};
|
|
365
391
|
# Skip auto-connect for servers the user intentionally disconnected
|
|
366
|
-
if name in
|
|
392
|
+
if name in disconnected {
|
|
367
393
|
entry["status"] = "disconnected";
|
|
368
394
|
entry["tool_count"] = 0;
|
|
369
395
|
entry["tools"] = [];
|
|
@@ -388,16 +414,16 @@ impl mcp_list_servers() -> list {
|
|
|
388
414
|
|
|
389
415
|
"""Save a built-in server to the registry without a connectivity check."""
|
|
390
416
|
impl mcp_register_builtin(name: str, config: dict) -> None {
|
|
391
|
-
global _builtin_names;
|
|
392
|
-
_builtin_names.add(name);
|
|
393
417
|
configs = _load_configs();
|
|
394
418
|
# Always update builtin config — the binary path can change between installs
|
|
395
419
|
# (e.g. pipx venv vs workspace venv). Stale paths cause connection errors.
|
|
420
|
+
# builtin=True is stored in the config dict inside McpRegistry.servers so
|
|
421
|
+
# it is graph-persisted and cross-pod consistent — no RAM set needed.
|
|
396
422
|
config["builtin"] = True;
|
|
397
423
|
configs[name] = config;
|
|
398
424
|
_save_configs(configs);
|
|
399
425
|
kv_delete(_TOOLS_CACHE_KEY); # Invalidate cross-pod cache — server set changed
|
|
400
|
-
if name == "jac-mcp" {
|
|
426
|
+
if name == "jac-mcp" and not _service_mode_active() {
|
|
401
427
|
threading.Thread(target=_check_jac_mcp_version, daemon=True).start();
|
|
402
428
|
}
|
|
403
429
|
}
|
|
@@ -411,8 +437,14 @@ impl mcp_get_tools() -> list {
|
|
|
411
437
|
}
|
|
412
438
|
|
|
413
439
|
configs = _load_configs();
|
|
440
|
+
# Read disconnected intent from the graph so tools from user-disconnected
|
|
441
|
+
# servers are never surfaced to the agent (bug fix: previously ignored).
|
|
442
|
+
disconnected = _get_registry().disconnected_servers;
|
|
414
443
|
all_tools: list = [];
|
|
415
444
|
for (name, config) in configs.items() {
|
|
445
|
+
if name in disconnected {
|
|
446
|
+
continue; # Respect user's disconnect intent — skip this server
|
|
447
|
+
}
|
|
416
448
|
try {
|
|
417
449
|
all_tools.extend(_submit(_async_list_tools(name, config)));
|
|
418
450
|
} except Exception as e {
|
|
@@ -43,3 +43,10 @@ def mcp_call_tool(server_name: str, tool_name: str, arguments: dict) -> str;
|
|
|
43
43
|
Saves now and tries it on first real tool call.
|
|
44
44
|
"""
|
|
45
45
|
def mcp_register_builtin(name: str, config: dict) -> None;
|
|
46
|
+
|
|
47
|
+
"""Return True when this process is serving multiple end-users (web/IDE).
|
|
48
|
+
Used to gate per-process best-effort tasks that only make sense in CLI."""
|
|
49
|
+
def _service_mode_active() -> bool;
|
|
50
|
+
|
|
51
|
+
"""Background thread: check PyPI for latest jac-mcp version. No-op in service mode."""
|
|
52
|
+
def _check_jac_mcp_version() -> None;
|
|
@@ -206,6 +206,12 @@ impl reset() -> None {
|
|
|
206
206
|
|
|
207
207
|
|
|
208
208
|
impl save_to_file(filepath: str = "") -> str {
|
|
209
|
+
# In web/IDE mode many users share the same server, so writing one user's
|
|
210
|
+
# cost to a local file would overwrite another user's data. Skip silently.
|
|
211
|
+
# In CLI mode the file is safe and useful.
|
|
212
|
+
if os.environ.get("JACCODER_WEB_MODE", "") or os.environ.get("JACCODER_IDE_MODE", "") {
|
|
213
|
+
return "";
|
|
214
|
+
}
|
|
209
215
|
if not filepath {
|
|
210
216
|
filepath = "jaccoder_cost.json";
|
|
211
217
|
}
|