@lazyingart/agintiflow 0.20.25 → 0.20.39

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 (63) hide show
  1. package/README.md +9 -4
  2. package/docs/agent-runtime-pipe.md +5 -4
  3. package/docs/cli-i18n.md +1 -1
  4. package/docs/project-agent-lessons.md +1 -1
  5. package/docs/real-deepseek-capabilities.md +1 -1
  6. package/docs/self-development-supervision.md +1 -2
  7. package/docs/skills-and-tools.md +1 -1
  8. package/docs/supervised-capability-curriculum.md +111 -0
  9. package/package.json +9 -3
  10. package/public/app.js +4 -0
  11. package/references/backend-frontend-artifact-tunnel.md +3 -3
  12. package/references/general-task-capability-taxonomy.md +109 -0
  13. package/references/supervision-student-recursive-training.md +116 -0
  14. package/scripts/smoke-auto-update.js +96 -0
  15. package/scripts/smoke-auxiliary-tools.js +1 -0
  16. package/scripts/smoke-canvas-artifacts.js +67 -0
  17. package/scripts/smoke-capabilities.js +51 -2
  18. package/scripts/smoke-cli-chat.js +2 -0
  19. package/scripts/smoke-coding-tools.js +41 -0
  20. package/scripts/smoke-inbox.js +40 -1
  21. package/scripts/smoke-skills.js +53 -1
  22. package/scripts/smoke-toolchain-docker.js +1 -0
  23. package/scripts/smoke-web-api.js +6 -1
  24. package/skills/android/SKILL.md +2 -1
  25. package/skills/code-review/SKILL.md +24 -0
  26. package/skills/data-analysis/SKILL.md +29 -0
  27. package/skills/database/SKILL.md +27 -0
  28. package/skills/devops-deployment/SKILL.md +29 -0
  29. package/skills/docs-knowledge/SKILL.md +28 -0
  30. package/skills/dotnet-csharp/SKILL.md +24 -0
  31. package/skills/education-tutorial/SKILL.md +26 -0
  32. package/skills/go/SKILL.md +23 -0
  33. package/skills/ios-swift/SKILL.md +26 -0
  34. package/skills/java-jvm/SKILL.md +25 -0
  35. package/skills/php/SKILL.md +24 -0
  36. package/skills/presentation-slides/SKILL.md +26 -0
  37. package/skills/qa-testing/SKILL.md +27 -0
  38. package/skills/ruby/SKILL.md +24 -0
  39. package/skills/rust/SKILL.md +23 -0
  40. package/skills/security-review/SKILL.md +28 -0
  41. package/skills/supervision-student/SKILL.md +67 -0
  42. package/skills/writing-editing/SKILL.md +28 -0
  43. package/src/agent-runner.js +49 -7
  44. package/src/artifact-tunnel.js +75 -4
  45. package/src/auto-update.js +389 -0
  46. package/src/capabilities.js +6 -1
  47. package/src/cli.js +129 -14
  48. package/src/config.js +6 -2
  49. package/src/engineering-guidance.js +12 -2
  50. package/src/interactive-cli.js +42 -5
  51. package/src/model-client.js +2 -2
  52. package/src/model-routing.js +3 -1
  53. package/src/parallel-scouts.js +17 -1
  54. package/src/project.js +200 -20
  55. package/src/session-index.js +135 -0
  56. package/src/session-store.js +67 -3
  57. package/src/skill-library.js +16 -2
  58. package/src/sqlite.js +19 -0
  59. package/src/static-preview-server.js +8 -1
  60. package/src/task-profiles.js +318 -5
  61. package/src/web-db.js +58 -6
  62. package/src/workspace-tools.js +1 -0
  63. package/web.js +37 -11
package/src/cli.js CHANGED
@@ -15,20 +15,25 @@ import { startInteractiveCli } from "./interactive-cli.js";
15
15
  import { SessionStore } from "./session-store.js";
16
16
  import {
17
17
  doctorReport,
18
+ ensureProjectSessionStorage,
18
19
  initProject,
19
20
  listProjectSessions,
21
+ renameProjectSession,
20
22
  providerKeyStatus,
21
23
  setProviderKey,
22
24
  showProjectSession,
25
+ sessionStoreOptions,
23
26
  } from "./project.js";
24
27
  import { listTaskProfiles } from "./task-profiles.js";
25
28
  import { recommendedMaxStepsForTask } from "./engineering-guidance.js";
26
29
  import { normalizeAuthProvider, promptHidden, runAuthWizard, shouldPromptForDeepSeek } from "./auth-onboarding.js";
27
30
  import { listSkills, selectSkillsForGoal } from "./skill-library.js";
28
31
  import { languageLabel, resolveLanguage } from "./i18n.js";
32
+ import { maybeAutoUpdate } from "./auto-update.js";
29
33
  import fs from "node:fs/promises";
30
34
  import path from "node:path";
31
35
  import { fileURLToPath } from "node:url";
36
+ import readline from "node:readline/promises";
32
37
 
33
38
  const packageDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
34
39
  const packageJson = JSON.parse(await fs.readFile(path.join(packageDir, "package.json"), "utf8"));
@@ -94,6 +99,7 @@ export function parseArgs(argv) {
94
99
  latex: false,
95
100
  image: false,
96
101
  language: "",
102
+ autoUpdate: undefined,
97
103
  };
98
104
 
99
105
  const parts = [];
@@ -129,6 +135,14 @@ export function parseArgs(argv) {
129
135
  }
130
136
  continue;
131
137
  }
138
+ if (arg === "--no-auto-update") {
139
+ result.autoUpdate = false;
140
+ continue;
141
+ }
142
+ if (arg === "--auto-update") {
143
+ result.autoUpdate = true;
144
+ continue;
145
+ }
132
146
  if (arg === "--start-url") {
133
147
  result.startUrl = readOption(argv, i);
134
148
  i += 1;
@@ -364,7 +378,7 @@ export function parseArgs(argv) {
364
378
 
365
379
  function printUsage() {
366
380
  console.log(
367
- 'Usage: aginti [chat] OR aginti web [--port 3210] OR aginti models OR aginti skills [query] OR aginti auth [deepseek|openai|qwen|venice|grsai] OR aginti resume [latest|<session-id>] ["prompt"] OR aginti queue <session-id> "message" OR aginti [--language en|ja|zh-Hans|zh-Hant|ko|fr|es|ar|vi|de|ru] [--image] [--latex] [--routing smart|fast|complex|manual] [--provider deepseek|openai|qwen|venice|mock] [--model MODEL] [--route-model MODEL] [--main-model MODEL] [--spare-model MODEL --spare-reasoning medium] [--aux-provider grsai|venice --aux-model MODEL] [--sandbox-mode host|docker-readonly|docker-workspace] [--package-install-policy block|prompt|allow] [--approve-package-installs] [--allow-shell|--no-shell] [--allow-file-tools|--no-file-tools] [--web-search|--no-web-search] [--parallel-scouts|--no-parallel-scouts --scout-count 1..10] [--allow-auxiliary-tools|--no-auxiliary-tools] [--allow-wrappers --wrapper codex --wrapper-model gpt-5.5] [--list-models|--list-routes] "your task"'
381
+ 'Usage: aginti [chat] OR aginti web [--port 3210] OR aginti update OR aginti models OR aginti skills [query] OR aginti auth [deepseek|openai|qwen|venice|grsai] OR aginti resume [latest|<session-id>] ["prompt"] OR aginti queue <session-id> "message" OR aginti [--no-auto-update] [--language en|ja|zh-Hans|zh-Hant|ko|fr|es|ar|vi|de|ru] [--image] [--latex] [--routing smart|fast|complex|manual] [--provider deepseek|openai|qwen|venice|mock] [--model MODEL] [--route-model MODEL] [--main-model MODEL] [--spare-model MODEL --spare-reasoning medium] [--aux-provider grsai|venice --aux-model MODEL] [--sandbox-mode host|docker-readonly|docker-workspace] [--package-install-policy block|prompt|allow] [--approve-package-installs] [--allow-shell|--no-shell] [--allow-file-tools|--no-file-tools] [--web-search|--no-web-search] [--parallel-scouts|--no-parallel-scouts --scout-count 1..10] [--allow-auxiliary-tools|--no-auxiliary-tools] [--allow-wrappers --wrapper codex --wrapper-model gpt-5.5] [--list-models|--list-routes] "your task"'
368
382
  );
369
383
  console.log(`Languages: ${["en", "ja", "zh-Hans", "zh-Hant", "ko", "fr", "es", "ar", "vi", "de", "ru"].map((code) => `${code}=${languageLabel(code)}`).join(", ")}`);
370
384
  }
@@ -379,29 +393,46 @@ function providerLabel(provider) {
379
393
  }
380
394
 
381
395
  function agentDefaults(args) {
396
+ const envSandboxMode = process.env.SANDBOX_MODE || "";
397
+ const envPackageInstallPolicy = process.env.PACKAGE_INSTALL_POLICY || "";
398
+ const envUseDockerSandbox = process.env.USE_DOCKER_SANDBOX;
399
+ const envScoutCount = Number(process.env.AGINTI_SCOUT_COUNT);
400
+ const taskProfile = args.taskProfile || process.env.AGINTI_TASK_PROFILE || (args.latex ? "latex" : "auto");
382
401
  const defaults = {
383
402
  ...args,
403
+ provider: args.provider || process.env.AGENT_PROVIDER || "",
404
+ routingMode: args.routingMode || process.env.AGENT_ROUTING_MODE || "smart",
405
+ routeProvider: args.routeProvider || process.env.AGINTI_ROUTE_PROVIDER || "",
406
+ routeModel: args.routeModel || process.env.AGINTI_ROUTE_MODEL || "",
407
+ mainProvider: args.mainProvider || process.env.AGINTI_MAIN_PROVIDER || "",
408
+ mainModel: args.mainModel || process.env.AGINTI_MAIN_MODEL || "",
409
+ spareProvider: args.spareProvider || process.env.AGINTI_SPARE_PROVIDER || "",
410
+ spareModel: args.spareModel || process.env.AGINTI_SPARE_MODEL || "",
411
+ spareReasoning: args.spareReasoning || process.env.AGINTI_SPARE_REASONING || "",
412
+ taskProfile,
384
413
  language: resolveLanguage(args.language || process.env.AGINTI_LANGUAGE || ""),
385
414
  allowShellTool: args.allowShellTool ?? true,
386
415
  allowFileTools: args.allowFileTools ?? true,
387
416
  allowAuxiliaryTools: args.allowAuxiliaryTools ?? true,
388
417
  allowWebSearch: args.allowWebSearch ?? true,
389
418
  allowParallelScouts: args.allowParallelScouts ?? true,
390
- parallelScoutCount: args.parallelScoutCount || 3,
391
- sandboxMode: args.sandboxMode || "docker-workspace",
392
- packageInstallPolicy: args.packageInstallPolicy || "allow",
393
- useDockerSandbox: args.useDockerSandbox ?? true,
419
+ parallelScoutCount: args.parallelScoutCount || (Number.isFinite(envScoutCount) && envScoutCount > 0 ? envScoutCount : 3),
420
+ sandboxMode: args.sandboxMode || envSandboxMode || "docker-workspace",
421
+ packageInstallPolicy: args.packageInstallPolicy || envPackageInstallPolicy || "allow",
422
+ useDockerSandbox:
423
+ args.useDockerSandbox ??
424
+ (envUseDockerSandbox === undefined ? true : String(envUseDockerSandbox).toLowerCase() !== "false"),
394
425
  maxSteps:
395
426
  args.maxSteps ||
396
427
  recommendedMaxStepsForTask({
397
428
  goal: args.goal || "",
398
- taskProfile: args.taskProfile || (args.latex ? "latex" : "auto"),
429
+ taskProfile,
399
430
  }),
400
431
  };
401
432
 
402
433
  if (defaults.sandboxMode === "host") {
403
434
  defaults.useDockerSandbox = false;
404
- defaults.packageInstallPolicy = args.packageInstallPolicy || "prompt";
435
+ defaults.packageInstallPolicy = args.packageInstallPolicy || envPackageInstallPolicy || "prompt";
405
436
  }
406
437
 
407
438
  return defaults;
@@ -469,7 +500,7 @@ function printInitResult(result) {
469
500
  console.log(`AgInTiFlow project initialized: ${result.projectRoot}`);
470
501
  console.log(`instructions=${result.instructionsPath}`);
471
502
  console.log(`control=${result.controlDir}`);
472
- console.log(`sessions=${result.sessionsDir}`);
503
+ console.log(`projectSessions=${result.sessionsDir}`);
473
504
  console.log(`created=${result.created.length} updated=${result.updated.length} skipped=${result.skipped.length}`);
474
505
  }
475
506
 
@@ -479,7 +510,8 @@ function printDoctorReport(report) {
479
510
  console.log(`platform=${report.platform.label} family=${report.platform.linuxFamily || report.platform.platform}`);
480
511
  console.log(`project=${report.project.root}`);
481
512
  console.log(`instructions=${report.project.instructionsPath} present=${report.project.instructionsPresent}`);
482
- console.log(`sessions=${report.project.sessionsDir}`);
513
+ console.log(`projectSessions=${report.project.sessionsDir}`);
514
+ console.log(`globalSessions=${report.project.globalSessionsDir}`);
483
515
  console.log(`sessionDb=${report.project.sessionDbPath}`);
484
516
  console.log(
485
517
  `keys: deepseek=${report.keys.deepseek ? "available" : "missing"} openai=${
@@ -569,7 +601,7 @@ function printAuthWizardResult(result) {
569
601
  }
570
602
 
571
603
  async function handleSessionsCommand(argv) {
572
- const [verb = "list", sessionId = ""] = argv;
604
+ const [verb = "list", sessionId = "", ...rest] = argv;
573
605
  if (verb === "list") {
574
606
  const sessions = await listProjectSessions(process.cwd(), 80);
575
607
  if (sessions.length === 0) {
@@ -593,14 +625,49 @@ async function handleSessionsCommand(argv) {
593
625
  return;
594
626
  }
595
627
 
596
- console.error("Usage: aginti sessions list OR aginti sessions show <session-id>");
628
+ if (verb === "rename") {
629
+ const title = rest.join(" ").trim();
630
+ if (!sessionId || !title) {
631
+ console.error('Usage: aginti sessions rename <session-id> "new title"');
632
+ process.exit(1);
633
+ }
634
+ const result = await renameProjectSession(process.cwd(), sessionId, title);
635
+ console.log(`renamed ${result.sessionId}: ${result.title}`);
636
+ return;
637
+ }
638
+
639
+ console.error('Usage: aginti sessions list OR aginti sessions show <session-id> OR aginti sessions rename <session-id> "title"');
597
640
  process.exit(1);
598
641
  }
599
642
 
643
+ async function promptSelectSession(sessions) {
644
+ if (!process.stdin.isTTY || !process.stdout.isTTY) return sessions[0]?.sessionId || "";
645
+ console.log("Select a session to resume:");
646
+ sessions.slice(0, 20).forEach((session, index) => {
647
+ const title = session.title || session.goal || "(untitled)";
648
+ console.log(
649
+ `${index + 1}. ${session.sessionId} ${session.provider || "unknown"}/${session.model || "unknown"} ${session.updatedAt || ""} ${title.slice(0, 90)}`
650
+ );
651
+ });
652
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
653
+ try {
654
+ const answer = await rl.question("Session number: ");
655
+ const index = Number(answer.trim()) - 1;
656
+ return sessions[index]?.sessionId || "";
657
+ } finally {
658
+ rl.close();
659
+ }
660
+ }
661
+
600
662
  async function resolveResumeSessionId(sessionId) {
601
663
  if (sessionId && sessionId !== "latest") return sessionId;
602
- const sessions = await listProjectSessions(process.cwd(), 1);
603
- if (sessions[0]?.sessionId) return sessions[0].sessionId;
664
+ const sessions = await listProjectSessions(process.cwd(), 50);
665
+ if (sessionId === "latest" || sessions.length <= 1 || !process.stdin.isTTY || !process.stdout.isTTY) {
666
+ if (sessions[0]?.sessionId) return sessions[0].sessionId;
667
+ } else {
668
+ const selected = await promptSelectSession(sessions);
669
+ if (selected) return selected;
670
+ }
604
671
  throw new Error("No project-local sessions found. Run `aginti sessions list` to check this folder.");
605
672
  }
606
673
 
@@ -612,12 +679,31 @@ async function handleQueueCommand(argv) {
612
679
  process.exit(1);
613
680
  }
614
681
 
615
- const store = new SessionStore(path.resolve(process.cwd(), ".sessions"), sessionId);
682
+ const paths = await ensureProjectSessionStorage(process.cwd());
683
+ const store = new SessionStore(paths.globalSessionsDir, sessionId, sessionStoreOptions(process.cwd(), sessionId));
616
684
  await store.appendInbox(content, { source: "cli" });
617
685
  await store.appendEvent("conversation.queued_input", { prompt: content, source: "cli" }).catch(() => {});
618
686
  console.log(`queued message for ${sessionId}`);
619
687
  }
620
688
 
689
+ async function handleStorageCommand(argv) {
690
+ const [verb = "status", ...rest] = argv;
691
+ if (verb !== "migrate" && verb !== "status") {
692
+ console.error("Usage: aginti storage migrate [project-root ...] OR aginti storage status");
693
+ process.exit(1);
694
+ }
695
+
696
+ const roots = verb === "migrate" && rest.length > 0 ? rest : [process.cwd()];
697
+ for (const root of roots) {
698
+ const projectRoot = path.resolve(root);
699
+ const paths = await ensureProjectSessionStorage(projectRoot);
700
+ const sessions = await listProjectSessions(projectRoot, 1000);
701
+ console.log(
702
+ `${verb === "migrate" ? "migrated" : "storage"} project=${paths.root} projectSessions=${paths.sessionsDir} globalSessions=${paths.globalSessionsDir} sessions=${sessions.length}`
703
+ );
704
+ }
705
+ }
706
+
621
707
  export async function main(argv = process.argv.slice(2)) {
622
708
  if (argv[0] === "help" || argv.includes("--help") || argv.includes("-h")) {
623
709
  printUsage();
@@ -629,6 +715,30 @@ export async function main(argv = process.argv.slice(2)) {
629
715
  return;
630
716
  }
631
717
 
718
+ if (argv[0] === "update" || argv[0] === "upgrade") {
719
+ const updateResult = await maybeAutoUpdate({
720
+ argv,
721
+ force: true,
722
+ manual: true,
723
+ packageDir,
724
+ packageName: packageJson.name,
725
+ packageVersion: packageJson.version,
726
+ restart: false,
727
+ });
728
+ if (updateResult.error) process.exit(1);
729
+ return;
730
+ }
731
+
732
+ const autoUpdateResult = await maybeAutoUpdate({
733
+ argv,
734
+ force: argv.includes("--auto-update"),
735
+ packageDir,
736
+ packageName: packageJson.name,
737
+ packageVersion: packageJson.version,
738
+ restart: true,
739
+ });
740
+ if (autoUpdateResult.restarted) process.exit(autoUpdateResult.exitCode ?? 0);
741
+
632
742
  if (argv[0] === "init") {
633
743
  printInitResult(await initProject(process.cwd()));
634
744
  return;
@@ -706,6 +816,11 @@ export async function main(argv = process.argv.slice(2)) {
706
816
  return;
707
817
  }
708
818
 
819
+ if (argv[0] === "storage") {
820
+ await handleStorageCommand(argv.slice(1));
821
+ return;
822
+ }
823
+
709
824
  if (argv[0] === "models" || argv[0] === "model") {
710
825
  printModels();
711
826
  return;
package/src/config.js CHANGED
@@ -3,7 +3,7 @@ import crypto from "node:crypto";
3
3
  import { getModelRoleDefaults, getProviderDefaults, normalizeRoutingMode, selectModelRoute } from "./model-routing.js";
4
4
  import { normalizePackageInstallPolicy, normalizeSandboxMode } from "./command-policy.js";
5
5
  import { normalizeWrapperName } from "./tool-wrappers.js";
6
- import { loadProjectEnv, resolveProjectRoot } from "./project.js";
6
+ import { loadProjectEnv, projectPaths, resolveProjectRoot } from "./project.js";
7
7
  import { normalizeTaskProfile } from "./task-profiles.js";
8
8
  import { recommendedMaxStepsForTask } from "./engineering-guidance.js";
9
9
  import { resolveLanguage } from "./i18n.js";
@@ -32,6 +32,7 @@ function parseList(value) {
32
32
 
33
33
  export function resolveRuntimeConfig(args, overrides = {}) {
34
34
  const baseDir = resolveProjectRoot(overrides.baseDir || process.cwd());
35
+ const paths = projectPaths(baseDir);
35
36
  loadProjectEnv(baseDir);
36
37
  const requestedProvider =
37
38
  overrides.provider ||
@@ -155,7 +156,10 @@ export function resolveRuntimeConfig(args, overrides = {}) {
155
156
  useDockerSandbox: sandboxMode !== "host",
156
157
  dockerSandboxImage: overrides.dockerSandboxImage || process.env.DOCKER_SANDBOX_IMAGE || "agintiflow-sandbox:latest",
157
158
  commandCwd: path.resolve(overrides.commandCwd || args.commandCwd || process.env.COMMAND_CWD || baseDir),
158
- sessionsDir: path.resolve(baseDir, ".sessions"),
159
+ sessionsDir: paths.globalSessionsDir,
160
+ projectSessionsDir: paths.sessionsDir,
161
+ sessionDbPath: paths.sessionDbPath,
162
+ globalSessionIndexPath: paths.globalSessionIndexPath,
159
163
  onLog: overrides.onLog,
160
164
  onEvent: overrides.onEvent,
161
165
  };
@@ -11,7 +11,7 @@ const LANGUAGE_HINTS = [
11
11
  id: "python",
12
12
  pattern: /\b(python|pytest|pip|uv|poetry|conda|venv|jupyter|fastapi|django|flask|pandas|numpy)\b/i,
13
13
  text:
14
- "Python: inspect pyproject/requirements, prefer project-local venv/uv/conda or Docker, run python -m pytest or focused module checks, avoid global package installs on host.",
14
+ "Python: inspect pyproject/requirements, prefer project-local venv/uv/conda or Docker, run python -m pytest or focused module checks, avoid global package installs on host, and check Python caches recursively with find . -type d -name __pycache__ -o -name '*.pyc' before claiming none exist.",
15
15
  },
16
16
  {
17
17
  id: "rust",
@@ -29,7 +29,7 @@ const LANGUAGE_HINTS = [
29
29
  id: "android",
30
30
  pattern: /\b(android|apk|adb|avd|emulator|gradle wrapper|gradlew|android sdk|kotlin android|jetpack|compose)\b/i,
31
31
  text:
32
- "Android: inspect git status, Gradle/settings/manifests, ANDROID_HOME/SDK paths, Java/Kotlin versions, adb devices, and emulator/AVD list first. Never start host sudo or host OS package installs; use the existing SDK, a project-local Gradle wrapper, user-writable caches, or return a precise setup report. Build, install with adb, launch with am, verify with logcat/screencap when possible, then commit.",
32
+ "Android: inspect git status, Gradle/settings/manifests, ANDROID_HOME/SDK paths, Java/Kotlin versions, adb devices, and emulator/AVD list first. Never start host sudo or host OS package installs; use the existing SDK, a project-local Gradle wrapper, user-writable caches, or return a precise setup report. Build, install with adb, launch with am, verify with logcat/screencap when possible, save screenshots under a durable workspace path, then commit.",
33
33
  },
34
34
  {
35
35
  id: "java-jvm",
@@ -55,6 +55,12 @@ const LANGUAGE_HINTS = [
55
55
  text:
56
56
  "Git: always run git status --short and git diff --stat before commit/push. Commit only requested changes, use a clear message, run git fetch before push when remote state matters, prefer git pull --ff-only, and stop to ask if there are conflicts, unrelated dirty files, divergent branches, or ambiguous merge choices.",
57
57
  },
58
+ {
59
+ id: "supervision",
60
+ pattern: /\b(supervise|supervisor|student agent|homework|curriculum|self[- ]?supervision|train the agent|agent training|monitor tmux)\b/i,
61
+ text:
62
+ "Supervision: do not simply believe the student agent. Define acceptance criteria, give normal user-level prompts, monitor tmux/session logs, verify artifacts and git state externally, record evidence, and turn repeated failures into durable profile, skill, tool, policy, or test improvements.",
63
+ },
58
64
  {
59
65
  id: "r-stats",
60
66
  pattern: /\b(rstats|r language|cmdstanr|stan|renv|tidyverse|shiny)\b/i,
@@ -92,7 +98,11 @@ export function engineeringGuidanceForTask(goal = "", taskProfile = "auto") {
92
98
  "Build a compact context pack before major edits: project instructions, manifests/scripts, git status/diff, relevant symbols/search hits, target files, and the narrowest checks. Do not paste whole trees or huge files into model context.",
93
99
  "For system repair, act like a doctor: gather evidence first, avoid silent destructive host changes, prefer Docker or project-local scripts for installs, and make every stronger action explicit in logs.",
94
100
  "Never send sudo passwords or wait at interactive password prompts. If host-level permission is truly required, stop that path, explain the blocker, and provide a manual command instead of hanging.",
101
+ "Shell commands already start in the configured workspace. Prefer workspace-relative commands such as python3 scripts/check.py or cd subdir && make; do not prefix commands with absolute host cd paths unless the tool explicitly requires it.",
102
+ "When you create or fix scripts, reports, tables, generated files, or analysis outputs, inspect the actual output. If it contains obvious duplicates, noisy rows, stale names, broken markdown, or contradictions, patch the source/output and rerun the check rather than merely explaining the defect in the report.",
95
103
  "Before claiming a coding task is finished, run git status --short when git is available. Leave the worktree clean, or explicitly report and justify each remaining untracked/unstaged artifact.",
104
+ "Do not claim there are no transient artifacts unless you checked recursively for the relevant stack, such as find . -type d -name __pycache__ -o -name '*.pyc' for Python. A clean git status means tracked work is clean; ignored caches such as __pycache__ may still exist and should be removed or described accurately if relevant.",
105
+ "For generated screenshots, images, PDFs, reports, archives, and app packages, choose a descriptive non-conflicting workspace path when the user did not specify one. Verify the file still exists after cleanup before claiming it was saved.",
96
106
  ];
97
107
 
98
108
  if (matched.length > 0) {
@@ -3,7 +3,15 @@ import { emitKeypressEvents } from "node:readline";
3
3
  import { stdin as input, stdout as output } from "node:process";
4
4
  import { runAgent } from "./agent-runner.js";
5
5
  import { loadConfig } from "./config.js";
6
- import { initProject, listProjectSessions, projectPaths, providerKeyStatus, readProjectInstructions } from "./project.js";
6
+ import {
7
+ initProject,
8
+ listProjectSessions,
9
+ projectPaths,
10
+ providerKeyStatus,
11
+ readProjectInstructions,
12
+ renameProjectSession,
13
+ sessionStoreOptions,
14
+ } from "./project.js";
7
15
  import { normalizePackageInstallPolicy, normalizeSandboxMode } from "./command-policy.js";
8
16
  import { defaultMaxStepsForProfile, normalizeTaskProfile } from "./task-profiles.js";
9
17
  import { recommendedMaxStepsForTask } from "./engineering-guidance.js";
@@ -61,6 +69,7 @@ const SLASH_COMMANDS = [
61
69
  "/new",
62
70
  "/resume",
63
71
  "/sessions",
72
+ "/rename",
64
73
  "/skills",
65
74
  "/skill",
66
75
  "/profile",
@@ -624,6 +633,7 @@ function printHelp() {
624
633
  ` ${command("/auxiliary [status|grsai|venice|model [provider/model]|on|off|image]", "Manage optional auxiliary skills, including image generation.", "helpAuxiliary")}`,
625
634
  ` ${command("/new", "Start a fresh session on the next message.", "helpNew")}`,
626
635
  ` ${command("/resume <session-id>", "Continue a saved session.", "helpResume")}`,
636
+ ` ${command("/rename [title|auto]", "Rename the current session.", "helpRename")}`,
627
637
  ` ${command("/sessions", "List recent sessions in this project.", "helpSessions")}`,
628
638
  ` ${command("/skills [query]", "List Markdown skills selected for a topic.", "helpSkills")}`,
629
639
  ` ${command("/profile <name>", "Set task profile, e.g. code, website, latex, maintenance.", "helpProfile")}`,
@@ -1564,8 +1574,17 @@ function isAbortError(error) {
1564
1574
  }
1565
1575
 
1566
1576
  function formatSessionLine(session) {
1567
- const goal = session.goal ? ` ${session.goal.slice(0, 72)}` : "";
1568
- return `${session.sessionId} ${session.provider || "unknown"}/${session.model || "unknown"} ${session.updatedAt || ""}${goal}`.trim();
1577
+ const title = session.title || session.goal || "";
1578
+ const preview = title ? ` ${title.slice(0, 72)}` : "";
1579
+ return `${session.sessionId} ${session.provider || "unknown"}/${session.model || "unknown"} ${session.updatedAt || ""}${preview}`.trim();
1580
+ }
1581
+
1582
+ function autoSessionTitle(state) {
1583
+ const route = `${state.routeProvider || state.provider || "auto"}/${state.routeModel || state.model || "auto"}`;
1584
+ const main = `${state.mainProvider || state.provider || "auto"}/${state.mainModel || state.model || "auto"}`;
1585
+ const goal = String(state.activeGoal || "").replace(/\s+/g, " ").trim();
1586
+ const prefix = state.taskProfile && state.taskProfile !== "auto" ? state.taskProfile : "auto";
1587
+ return [prefix, `route ${route}`, `main ${main}`, goal.slice(0, 36)].filter(Boolean).join(" · ").slice(0, 90);
1569
1588
  }
1570
1589
 
1571
1590
  async function latestSession() {
@@ -1582,7 +1601,8 @@ function printHistoryEntry(entry) {
1582
1601
 
1583
1602
  async function printResumeHistory(state, { limit = 0 } = {}) {
1584
1603
  if (!state.sessionId) return;
1585
- const store = new SessionStore(projectPaths(process.cwd()).sessionsDir, state.sessionId);
1604
+ const paths = projectPaths(process.cwd());
1605
+ const store = new SessionStore(paths.globalSessionsDir, state.sessionId, sessionStoreOptions(process.cwd(), state.sessionId));
1586
1606
  const saved = await store.loadState().catch(() => null);
1587
1607
  const events = await store.loadEvents().catch(() => []);
1588
1608
  const chat = Array.isArray(saved?.chat) ? saved.chat.filter((entry) => entry?.content) : [];
@@ -2583,6 +2603,20 @@ async function handleCommand(line, state, packageDir) {
2583
2603
  }
2584
2604
  return true;
2585
2605
  }
2606
+ if (command === "rename") {
2607
+ if (!state.sessionId) {
2608
+ printAgentMessage("No active session to rename. Start or resume a session first.");
2609
+ return true;
2610
+ }
2611
+ const title = value === "auto" || !value ? autoSessionTitle(state) : value;
2612
+ try {
2613
+ const result = await renameProjectSession(process.cwd(), state.sessionId, title);
2614
+ printSystemLine(`session=${result.sessionId} title=${result.title}`);
2615
+ } catch (error) {
2616
+ printAgentMessage(error instanceof Error ? error.message : String(error));
2617
+ }
2618
+ return true;
2619
+ }
2586
2620
  if (command === "skills" || command === "skill") {
2587
2621
  const skills = value
2588
2622
  ? selectSkillsForGoal(value, { taskProfile: state.taskProfile, limit: 12, includeBody: false })
@@ -2914,7 +2948,10 @@ async function runPrompt(prompt, state, packageDir) {
2914
2948
  state.activeGoal = compactLine(prompt, 84);
2915
2949
  state.lastEvent = "";
2916
2950
 
2917
- const store = new SessionStore(config.sessionsDir, state.sessionId);
2951
+ const store = new SessionStore(config.sessionsDir, state.sessionId, {
2952
+ projectRoot: config.baseDir,
2953
+ projectSessionsDir: config.projectSessionsDir,
2954
+ });
2918
2955
  const liveInput = new LiveRunInput({ state, store, controller });
2919
2956
  const liveStarted = liveInput.start();
2920
2957
  const detachInterrupts = liveStarted ? () => {} : attachRunInterrupts(controller);
@@ -1020,7 +1020,7 @@ export async function requestNextStep(client, config, messages) {
1020
1020
  function: {
1021
1021
  name: "send_to_canvas",
1022
1022
  description:
1023
- "Send an optional artifact notification to the frontend canvas/artifacts tunnel. Use for important markdown/text, generated images, screenshots, figures, diffs, or workspace file paths the user should preview. Proactively use this for draw/plot/graph/chart/diagram/figure requests even when the user did not mention canvas. This does not replace finish.",
1023
+ "Send an optional artifact notification to the frontend canvas/artifacts tunnel. Use for important markdown/text, generated images, screenshots, figures, diffs, or workspace file paths the user should preview. Workspace files sent here are copied into session artifacts for durable preview, but if the user asked to save/capture/generate a file, keep a descriptive non-conflicting copy in the working directory too. Proactively use this for draw/plot/graph/chart/diagram/figure requests even when the user did not mention canvas. This does not replace finish.",
1024
1024
  parameters: {
1025
1025
  type: "object",
1026
1026
  properties: {
@@ -1031,7 +1031,7 @@ export async function requestNextStep(client, config, messages) {
1031
1031
  description: "Renderer hint. Use image/file with path, markdown/text/json/diff with content.",
1032
1032
  },
1033
1033
  content: { type: "string", description: "Inline text or markdown content to render." },
1034
- path: { type: "string", description: "Optional workspace-relative file path to preview." },
1034
+ path: { type: "string", description: "Optional existing workspace-relative file path to preview and persist into the session artifact store." },
1035
1035
  note: { type: "string", description: "Short notification message for the artifact explorer." },
1036
1036
  selected: { type: "boolean", description: "Whether the frontend should select this item immediately." },
1037
1037
  },
@@ -776,7 +776,9 @@ export function modelsForProviderGroup(groupId) {
776
776
  export function scoreTaskComplexity(goal = "", taskProfile = "auto") {
777
777
  const text = String(goal).toLowerCase();
778
778
  let score = text.length > 600 ? 2 : text.length > 240 ? 1 : 0;
779
- if (["large-codebase", "engineering", "codebase"].includes(String(taskProfile || "").toLowerCase())) score += 3;
779
+ const profile = String(taskProfile || "").toLowerCase();
780
+ if (["large-codebase", "engineering", "codebase", "code", "qa", "database", "devops", "security"].includes(profile)) score += 3;
781
+ if (["app", "data", "paper", "research", "latex", "github", "maintenance", "supervision"].includes(profile)) score += 2;
780
782
  for (const keyword of COMPLEXITY_KEYWORDS) {
781
783
  if (text.includes(keyword)) score += 1;
782
784
  }
@@ -69,7 +69,23 @@ function shouldUseComplexScouts(config, state) {
69
69
  const goal = String(config.goal || state.goal || "");
70
70
  return (
71
71
  Number(config.routeComplexityScore || 0) >= 3 ||
72
- ["large-codebase", "maintenance", "code"].includes(profile) ||
72
+ [
73
+ "large-codebase",
74
+ "maintenance",
75
+ "code",
76
+ "qa",
77
+ "database",
78
+ "devops",
79
+ "security",
80
+ "github",
81
+ "java",
82
+ "ios",
83
+ "go",
84
+ "rust",
85
+ "dotnet",
86
+ "php",
87
+ "ruby",
88
+ ].includes(profile) ||
73
89
  /\b(large|complex|complicated|debug|bug|failing|system|install|setup|codebase|repo|multi[- ]file|architecture|refactor|migration)\b/i.test(
74
90
  goal
75
91
  )