jac-coder 0.2.1__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {jac_coder-0.2.1 → jac_coder-0.2.2}/PKG-INFO +1 -1
- {jac_coder-0.2.1 → jac_coder-0.2.2}/README.md +2 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/api.impl.jac +59 -49
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/core/nodes.jac +15 -1
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/core/walkers.impl.jac +51 -33
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/infra/mcp_manager.impl.jac +45 -19
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/infra/mcp_manager.jac +7 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/runtime/cost_tracker.impl.jac +6 -0
- jac_coder-0.2.2/jac_coder/runtime/permission.impl.jac +171 -0
- jac_coder-0.2.2/jac_coder/runtime/permission.jac +55 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/runtime/prompt.jac +16 -10
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/runtime/skills.impl.jac +28 -4
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/server.jac +44 -11
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/skills/jac-cl-components/SKILL.md +1 -0
- jac_coder-0.2.2/jac_coder/skills/jac-cl-styling/SKILL.md +49 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/skills/jac-core-cheatsheet/SKILL.md +2 -1
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/skills/jac-fullstack-patterns/SKILL.md +1 -1
- jac_coder-0.2.2/jac_coder/skills/jac-npm-packages/SKILL.md +94 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/skills/jac-scaffold/SKILL.md +5 -6
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/tool/run/guarded.impl.jac +32 -2
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/tool/run/guarded.jac +4 -1
- jac_coder-0.2.2/jac_coder/tool/run/shell.impl.jac +206 -0
- jac_coder-0.2.2/jac_coder/tool/run/shell.jac +23 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder.egg-info/PKG-INFO +1 -1
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder.egg-info/SOURCES.txt +2 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/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/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.2}/jac_coder/__init__.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/__init__.py +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/api.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/cli_entry.py +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/core/__init__.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/core/nodes.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/core/walkers.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/infra/__init__.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/infra/config.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/infra/config.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/infra/kv.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/lib/__init__.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/lib/coder.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/lib/coder.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/runtime/__init__.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/runtime/context.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/runtime/context.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/runtime/cost_tracker.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/runtime/events.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/runtime/memory.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/runtime/memory.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/runtime/prompt.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/runtime/skills.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/serve_entry.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/skills/ROADMAP.md +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/skills/jac-by-llm/SKILL.md +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/skills/jac-cl-auth/SKILL.md +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/skills/jac-cl-organization/SKILL.md +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/skills/jac-cl-routing/SKILL.md +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/skills/jac-has-fields/SKILL.md +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/skills/jac-impl-files/SKILL.md +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/skills/jac-node-edge-patterns/SKILL.md +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/skills/jac-sv-auth/SKILL.md +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/skills/jac-sv-endpoints/SKILL.md +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/skills/jac-sv-persistence/SKILL.md +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/skills/jac-types/SKILL.md +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/skills/jac-walker-patterns/SKILL.md +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/tool/__init__.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/tool/git.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/tool/git.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/tool/mcp.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/tool/mcp.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/tool/meta/__init__.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/tool/meta/delegation.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/tool/meta/delegation.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/tool/meta/question.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/tool/meta/question.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/tool/meta/task.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/tool/meta/task.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/tool/meta/think.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/tool/meta/think.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/tool/meta/todo.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/tool/meta/todo.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/tool/meta/validate.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/tool/meta/validate.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/tool/net/__init__.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/tool/net/preview.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/tool/net/preview.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/tool/net/web.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/tool/net/web.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/tool/read/__init__.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/tool/read/filesystem.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/tool/read/filesystem.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/tool/read/jac_analyzer.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/tool/read/jac_analyzer.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/tool/read/load_jac_skill.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/tool/read/load_jac_skill.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/tool/read/search.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/tool/read/search.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/tool/run/__init__.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/tool/run/jac_tools.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/tool/run/jac_tools.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/tool/write/__init__.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/tool/write/checked.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/tool/write/checked.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/util/__init__.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/util/colors.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/util/sandbox.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/util/sandbox.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/util/tool_output.impl.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder/util/tool_output.jac +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder.egg-info/dependency_links.txt +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder.egg-info/entry_points.txt +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder.egg-info/requires.txt +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/jac_coder.egg-info/top_level.txt +0 -0
- {jac_coder-0.2.1 → jac_coder-0.2.2}/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();
|
|
@@ -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(
|
|
@@ -286,31 +294,33 @@ impl mcp_add_server(name: str, config: dict) -> dict {
|
|
|
286
294
|
}
|
|
287
295
|
|
|
288
296
|
|
|
289
|
-
"""Close the connection and
|
|
297
|
+
"""Close the connection and mark the server disconnected in the graph."""
|
|
290
298
|
impl mcp_disconnect_server(name: str) -> dict {
|
|
291
299
|
# Disconnect only — close the active connection but keep the config.
|
|
292
300
|
# This applies to ALL servers (builtin and user-added) so they stay
|
|
293
301
|
# visible in the list and can be reconnected.
|
|
294
|
-
|
|
302
|
+
# Intent is persisted in McpRegistry.disconnected_servers so it survives
|
|
303
|
+
# pod restarts and is visible across all pods.
|
|
295
304
|
configs = _load_configs();
|
|
296
305
|
if name not in configs {
|
|
297
306
|
return {"error": f"Server '{name}' not found"};
|
|
298
307
|
}
|
|
299
|
-
|
|
308
|
+
reg = _get_registry();
|
|
309
|
+
if name not in reg.disconnected_servers {
|
|
310
|
+
reg.disconnected_servers = list(reg.disconnected_servers) + [name];
|
|
311
|
+
}
|
|
300
312
|
_submit(_close_connection(name));
|
|
301
313
|
kv_delete(_TOOLS_CACHE_KEY);
|
|
302
|
-
return {"status": "disconnected", "name": name};
|
|
314
|
+
return {"status": "disconnected", "name": name};
|
|
303
315
|
}
|
|
304
316
|
|
|
305
317
|
|
|
306
318
|
"""Reconnect a previously-disconnected MCP server."""
|
|
307
319
|
impl mcp_reconnect_server(name: str) -> dict {
|
|
308
|
-
global _disconnected_names;
|
|
309
320
|
configs = _load_configs();
|
|
310
321
|
if name not in configs {
|
|
311
322
|
return {"error": f"Server '{name}' not found"};
|
|
312
323
|
}
|
|
313
|
-
_disconnected_names.discard(name);
|
|
314
324
|
# Close any stale connection first, then reconnect via list_tools.
|
|
315
325
|
# Iterate configs to get a typed config value (subscript on untyped dict
|
|
316
326
|
# produces Unknown in the Jac type checker — the for pattern is used
|
|
@@ -321,9 +331,14 @@ impl mcp_reconnect_server(name: str) -> dict {
|
|
|
321
331
|
if n == name {
|
|
322
332
|
try {
|
|
323
333
|
tools = _submit(_async_list_tools(n, config));
|
|
334
|
+
# Only remove from disconnected_servers on success — keeps state
|
|
335
|
+
# consistent if connection fails (server stays disconnected).
|
|
336
|
+
reg = _get_registry();
|
|
337
|
+
if name in reg.disconnected_servers {
|
|
338
|
+
reg.disconnected_servers = [s for s in reg.disconnected_servers if s != name];
|
|
339
|
+
}
|
|
324
340
|
return {"status": "connected", "name": name};
|
|
325
341
|
} except Exception as e {
|
|
326
|
-
_disconnected_names.add(name);
|
|
327
342
|
return {"error": f"Failed to reconnect '{name}': {e}"};
|
|
328
343
|
}
|
|
329
344
|
}
|
|
@@ -334,15 +349,18 @@ impl mcp_reconnect_server(name: str) -> dict {
|
|
|
334
349
|
|
|
335
350
|
impl mcp_delete_server(name: str) -> dict {
|
|
336
351
|
# 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
|
-
}
|
|
352
|
+
# Built-in servers cannot be deleted — the builtin flag is stored as
|
|
353
|
+
# config["builtin"]=True inside McpRegistry.servers (graph-persisted),
|
|
341
354
|
configs = _load_configs();
|
|
342
355
|
if name not in configs {
|
|
343
356
|
return {"error": f"Server '{name}' not found"};
|
|
344
357
|
}
|
|
345
|
-
|
|
358
|
+
for (n, cfg) in configs.items() {
|
|
359
|
+
if n == name and cfg.get("builtin", False) {
|
|
360
|
+
return {"error": f"Built-in server '{name}' cannot be deleted"};
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
_submit(_close_connection(name));
|
|
346
364
|
configs.pop(name);
|
|
347
365
|
_save_configs(configs);
|
|
348
366
|
kv_delete(_TOOLS_CACHE_KEY);
|
|
@@ -353,6 +371,8 @@ impl mcp_delete_server(name: str) -> dict {
|
|
|
353
371
|
"""Return all registered servers with their connection status and tool counts."""
|
|
354
372
|
impl mcp_list_servers() -> list {
|
|
355
373
|
configs = _load_configs();
|
|
374
|
+
# Read disconnected intent from the graph — persisted, cross-pod consistent.
|
|
375
|
+
disconnected = _get_registry().disconnected_servers;
|
|
356
376
|
result: list = [];
|
|
357
377
|
for (name, config) in configs.items() {
|
|
358
378
|
entry: dict = {
|
|
@@ -363,7 +383,7 @@ impl mcp_list_servers() -> list {
|
|
|
363
383
|
"latest_version": _jac_mcp_update_info.get("latest", "") if name == "jac-mcp" else ""
|
|
364
384
|
};
|
|
365
385
|
# Skip auto-connect for servers the user intentionally disconnected
|
|
366
|
-
if name in
|
|
386
|
+
if name in disconnected {
|
|
367
387
|
entry["status"] = "disconnected";
|
|
368
388
|
entry["tool_count"] = 0;
|
|
369
389
|
entry["tools"] = [];
|
|
@@ -388,16 +408,16 @@ impl mcp_list_servers() -> list {
|
|
|
388
408
|
|
|
389
409
|
"""Save a built-in server to the registry without a connectivity check."""
|
|
390
410
|
impl mcp_register_builtin(name: str, config: dict) -> None {
|
|
391
|
-
global _builtin_names;
|
|
392
|
-
_builtin_names.add(name);
|
|
393
411
|
configs = _load_configs();
|
|
394
412
|
# Always update builtin config — the binary path can change between installs
|
|
395
413
|
# (e.g. pipx venv vs workspace venv). Stale paths cause connection errors.
|
|
414
|
+
# builtin=True is stored in the config dict inside McpRegistry.servers so
|
|
415
|
+
# it is graph-persisted and cross-pod consistent — no RAM set needed.
|
|
396
416
|
config["builtin"] = True;
|
|
397
417
|
configs[name] = config;
|
|
398
418
|
_save_configs(configs);
|
|
399
419
|
kv_delete(_TOOLS_CACHE_KEY); # Invalidate cross-pod cache — server set changed
|
|
400
|
-
if name == "jac-mcp" {
|
|
420
|
+
if name == "jac-mcp" and not _service_mode_active() {
|
|
401
421
|
threading.Thread(target=_check_jac_mcp_version, daemon=True).start();
|
|
402
422
|
}
|
|
403
423
|
}
|
|
@@ -411,8 +431,14 @@ impl mcp_get_tools() -> list {
|
|
|
411
431
|
}
|
|
412
432
|
|
|
413
433
|
configs = _load_configs();
|
|
434
|
+
# Read disconnected intent from the graph so tools from user-disconnected
|
|
435
|
+
# servers are never surfaced to the agent (bug fix: previously ignored).
|
|
436
|
+
disconnected = _get_registry().disconnected_servers;
|
|
414
437
|
all_tools: list = [];
|
|
415
438
|
for (name, config) in configs.items() {
|
|
439
|
+
if name in disconnected {
|
|
440
|
+
continue; # Respect user's disconnect intent — skip this server
|
|
441
|
+
}
|
|
416
442
|
try {
|
|
417
443
|
all_tools.extend(_submit(_async_list_tools(name, config)));
|
|
418
444
|
} 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
|
}
|