@lazyingart/agintiflow 0.8.12 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -53,6 +53,10 @@ aginti login deepseek
53
53
  # inside chat, use /login or /auth
54
54
  # or non-interactively:
55
55
  printf '%s' "$DEEPSEEK_API_KEY" | aginti keys set deepseek --stdin
56
+
57
+ # optional image-generation auxiliary skill:
58
+ aginti login grsai
59
+ # inside chat, use /auxilliary grsai or /auxiliary grsai
56
60
  ```
57
61
 
58
62
  Start an interactive Codex-style CLI chat from any project folder:
@@ -67,6 +71,10 @@ Inside chat, type normal requests such as `write a small Python CLI app with tes
67
71
 
68
72
  For code edits, AgInTiFlow routes patch/refactor/database-style tasks to DeepSeek v4 pro by default and exposes `apply_patch` as a deterministic workspace tool. It supports exact replacements, Codex-style patch envelopes, and unified diffs, with preflight checks, path guardrails, hashes, and compact per-file diffs. See [docs/patch-tools.md](docs/patch-tools.md).
69
73
 
74
+ For larger repositories, use `--profile large-codebase` or choose **Large codebase engineering** in the web UI. This routes to DeepSeek v4 pro, starts with `inspect_project`, then uses search/read/patch/check loops inspired by Codex, Copilot SDK, Claude Code, Gemini CLI, Qwen, and Claw Code. See [docs/large-codebase-engineering.md](docs/large-codebase-engineering.md).
75
+
76
+ For raster image work, AgInTiFlow has an optional `image_generation` skill backed by the `generate_image` tool and a local `GRSAI` key. The skill tells DeepSeek when image generation is appropriate; the tool calls GRS AI Nano Banana, saves manifests/images under `artifacts/images`, and sends the result to the canvas. See [docs/auxiliary-image-generation.md](docs/auxiliary-image-generation.md).
77
+
70
78
  Launch the local web UI from an installed package:
71
79
 
72
80
  ```bash
@@ -0,0 +1,62 @@
1
+ # Auxiliary Image Generation
2
+
3
+ AgInTiFlow separates **skills** from **tools**:
4
+
5
+ - A skill is instruction and routing context that teaches the agent when a capability is useful.
6
+ - A tool is the deterministic callable function that performs the action and writes auditable artifacts.
7
+
8
+ The optional image-generation skill is `image_generation`. Its tool is `generate_image`.
9
+
10
+ ## Setup
11
+
12
+ Store the GRS AI key project-locally:
13
+
14
+ ```bash
15
+ aginti login grsai
16
+ # or
17
+ printf '%s' "$GRSAI" | aginti keys set grsai --stdin
18
+ ```
19
+
20
+ Inside interactive chat, use either spelling:
21
+
22
+ ```text
23
+ /auxilliary grsai
24
+ /auxiliary grsai
25
+ ```
26
+
27
+ The key is saved in `.aginti/.env` as `GRSAI` with `0600` permissions. The CLI and web app only report whether the key exists; they never return the raw value.
28
+
29
+ ## Runtime Flow
30
+
31
+ For image, cover, poster, illustration, photo, or logo-concept requests, the model can call:
32
+
33
+ ```json
34
+ {
35
+ "prompt": "A polished cyan robot painting a circuit-board river, high-end product illustration",
36
+ "outputDir": "artifacts/images/robot-cover",
37
+ "outputStem": "robot-cover",
38
+ "aspectRatio": "1:1",
39
+ "imageSize": "2K"
40
+ }
41
+ ```
42
+
43
+ The tool uses the GRS AI Nano Banana API:
44
+
45
+ - `POST https://grsaiapi.com/v1/draw/nano-banana`
46
+ - `POST https://grsaiapi.com/v1/draw/result`
47
+ - `Authorization: Bearer <GRSAI>`
48
+
49
+ Saved workspace artifacts:
50
+
51
+ - `prompt.txt`
52
+ - `request_payload.redacted.json`
53
+ - `submit_response.json`
54
+ - `result_response.json`
55
+ - `task_manifest.json`
56
+ - generated image files, for example `image.png`
57
+
58
+ Generated images are sent to the canvas automatically when available.
59
+
60
+ ## Guardrails
61
+
62
+ Output paths must stay inside the project workspace. Secret paths, `.git`, `node_modules` writes, and oversized reference images are blocked. Reference images may be workspace files, HTTPS URLs, or data URLs.
@@ -0,0 +1,49 @@
1
+ # Large Codebase Engineering
2
+
3
+ AgInTiFlow now treats large or complicated coding tasks as a different operating mode from short file edits.
4
+
5
+ ## What Was Borrowed
6
+
7
+ Local agent references informed the design:
8
+
9
+ - Codex-style editing: read/search first, apply deterministic patches, record diffs and hashes, then run checks.
10
+ - Copilot-style SDK surfaces: structured tools, session persistence, plan/history/workspace APIs, and explicit permission hooks.
11
+ - Claude/Claw-style safety: project-local status, container-first execution, read-only operations by default, and clear failure recovery.
12
+ - Gemini/Qwen-style extensibility: capability discovery through profiles and tools rather than hardcoding one model behavior.
13
+
14
+ ## Skill vs Tool
15
+
16
+ The `large-codebase` profile is a skill: it changes the model’s engineering behavior. It tells DeepSeek v4 pro to orient first, plan minimally, patch incrementally, and verify.
17
+
18
+ The `inspect_project` function is a tool: it deterministically scans the workspace and returns:
19
+
20
+ - top-level files and directories
21
+ - manifest files such as `package.json`, `pyproject.toml`, `Cargo.toml`, and `go.mod`
22
+ - package scripts
23
+ - likely source and test directories
24
+ - language and extension counts
25
+ - recommended files to read next
26
+
27
+ ## Recommended Loop
28
+
29
+ For complicated tasks, the agent should follow this loop:
30
+
31
+ 1. `inspect_project` to map the repository.
32
+ 2. `read_file` on `AGENTS.md`, `README.md`, and manifests.
33
+ 3. `search_files` for symbols, tests, errors, routes, or config names.
34
+ 4. `read_file` only on the files needed for the change.
35
+ 5. `apply_patch` in small coherent batches.
36
+ 6. `run_command` for the narrowest relevant check first.
37
+ 7. Broaden checks only after the focused check passes.
38
+
39
+ ## CLI And Web Parity
40
+
41
+ Both CLI and web use the same task profile registry and the same model/tool schemas. Use either:
42
+
43
+ ```bash
44
+ aginti --profile large-codebase "fix the failing tests"
45
+ ```
46
+
47
+ or choose **Large codebase engineering** in the web task-profile dropdown.
48
+
49
+ Smart routing sends this profile to DeepSeek v4 pro even when the user prompt is short.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.8.12",
3
+ "version": "0.10.0",
4
4
  "type": "module",
5
5
  "description": "AgInTiFlow is a resumable Playwright website-control agent with OpenAI-compatible tool calling.",
6
6
  "license": "Apache-2.0",
@@ -41,6 +41,7 @@
41
41
  "scripts/install-docker-ubuntu.sh",
42
42
  "scripts/setup-agent-toolchain-docker.sh",
43
43
  "scripts/real-deepseek-capabilities.js",
44
+ "scripts/smoke-auxiliary-tools.js",
44
45
  "scripts/smoke-cli-chat.js",
45
46
  "scripts/smoke-coding-tools.js",
46
47
  "scripts/smoke-capabilities.js",
@@ -61,12 +62,13 @@
61
62
  "check": "node --check run.js && node --check web.js && node --check bin/aginti-cli.js && node --check src/*.js",
62
63
  "setup:toolchain-docker": "scripts/setup-agent-toolchain-docker.sh",
63
64
  "smoke:coding-tools": "node scripts/smoke-coding-tools.js",
65
+ "smoke:auxiliary-tools": "node scripts/smoke-auxiliary-tools.js",
64
66
  "smoke:cli-chat": "node scripts/smoke-cli-chat.js",
65
67
  "smoke:toolchain-docker": "node scripts/smoke-toolchain-docker.js",
66
68
  "smoke:inbox": "node scripts/smoke-inbox.js",
67
69
  "smoke:web-api": "node scripts/smoke-web-api.js",
68
70
  "real:deepseek": "node scripts/real-deepseek-capabilities.js",
69
- "test": "npm run check && npm run smoke:web-api && npm run smoke:coding-tools && npm run smoke:capabilities && npm run smoke:cli-chat && npm run smoke:inbox",
71
+ "test": "npm run check && npm run smoke:web-api && npm run smoke:coding-tools && npm run smoke:auxiliary-tools && npm run smoke:capabilities && npm run smoke:cli-chat && npm run smoke:inbox",
70
72
  "pack:dry-run": "npm pack --dry-run",
71
73
  "smoke:capabilities": "node scripts/smoke-capabilities.js"
72
74
  },
package/public/app.js CHANGED
@@ -24,7 +24,7 @@ const translations = {
24
24
  setupHelp:
25
25
  "DeepSeek/OpenAI keys are missing. Use mock mode, export an env var, or save a project-local DeepSeek key.",
26
26
  setupEnvHelp:
27
- "Env vars: DEEPSEEK_API_KEY, OPENAI_API_KEY, or LLM_API_KEY. Mock mode remains available for local tests.",
27
+ "Env vars: DEEPSEEK_API_KEY, OPENAI_API_KEY, LLM_API_KEY, and optional GRSAI for image generation. Mock mode remains available for local tests.",
28
28
  setupProviderLabel: "Provider",
29
29
  setupKeyLabel: "API key",
30
30
  saveKeyButton: "Save local key",
@@ -68,6 +68,7 @@ const translations = {
68
68
  headlessLabel: "Headless browser",
69
69
  shellToolLabel: "Enable shell tool",
70
70
  fileToolLabel: "Enable file tools",
71
+ auxiliaryToolLabel: "Enable auxiliary skills",
71
72
  wrapperToolLabel: "Enable agent wrappers",
72
73
  preferredWrapperLabel: "Preferred wrapper",
73
74
  dockerSandboxLabel: "Use Docker sandbox",
@@ -648,6 +649,7 @@ const logsEl = document.querySelector("#logs");
648
649
  const runMetaEl = document.querySelector("#run-meta");
649
650
  const stopRunButton = document.querySelector("#stop-run");
650
651
  const keyStatusEl = document.querySelector("#key-status");
652
+ const allowAuxiliaryToolsField = document.querySelector("#allowAuxiliaryTools");
651
653
  const allowWrapperToolsField = document.querySelector("#allowWrapperTools");
652
654
  const preferredWrapperField = document.querySelector("#preferredWrapper");
653
655
  const wrapperStatusEl = document.querySelector("#wrapper-status");
@@ -744,7 +746,9 @@ function renderKeyStatus(status = lastKeyStatus) {
744
746
  if (!status) return;
745
747
  keyStatusEl.textContent = `${t("keysLabel")}: OpenAI ${
746
748
  status.openai ? t("availableLabel") : t("missingLabel")
747
- } · DeepSeek ${status.deepseek ? t("availableLabel") : t("missingLabel")} · ${t("mockLabel")} ${
749
+ } · DeepSeek ${status.deepseek ? t("availableLabel") : t("missingLabel")} · GRS AI ${
750
+ status.grsai ? t("availableLabel") : t("missingLabel")
751
+ } · ${t("mockLabel")} ${
748
752
  status.mock ? t("availableLabel") : t("missingLabel")
749
753
  }`;
750
754
  if (setupCardEl) setupCardEl.hidden = Boolean(status.openai || status.deepseek);
@@ -771,6 +775,12 @@ function renderTaskProfiles(selected = "auto") {
771
775
  taskProfileField.value = profiles.some((profile) => profile.id === selected) ? selected : "auto";
772
776
  }
773
777
 
778
+ function recommendedMaxStepsForProfile(profile = "auto") {
779
+ if (profile === "large-codebase") return 36;
780
+ if (profile === "latex") return 30;
781
+ return 24;
782
+ }
783
+
774
784
  function renderWrapperStatus(wrappers = lastWrappers) {
775
785
  lastWrappers = wrappers || [];
776
786
  if (lastWrappers.length === 0) {
@@ -979,6 +989,7 @@ function formPayload() {
979
989
  headless: document.querySelector("#headless").checked,
980
990
  allowShellTool: document.querySelector("#allowShellTool").checked,
981
991
  allowFileTools: document.querySelector("#allowFileTools").checked,
992
+ allowAuxiliaryTools: allowAuxiliaryToolsField?.checked ?? true,
982
993
  allowWrapperTools: allowWrapperToolsField.checked,
983
994
  preferredWrapper: preferredWrapperField.value,
984
995
  taskProfile: taskProfileField?.value || "auto",
@@ -1954,7 +1965,14 @@ sandboxModeField.addEventListener("change", updatePackageWarning);
1954
1965
  packageInstallPolicyField.addEventListener("change", updatePackageWarning);
1955
1966
  allowWrapperToolsField.addEventListener("change", () => renderWrapperStatus());
1956
1967
  preferredWrapperField.addEventListener("change", () => renderWrapperStatus());
1957
- taskProfileField?.addEventListener("change", schedulePreferenceSave);
1968
+ taskProfileField?.addEventListener("change", () => {
1969
+ const maxStepsField = document.querySelector("#maxSteps");
1970
+ const recommended = recommendedMaxStepsForProfile(taskProfileField.value);
1971
+ if (maxStepsField && Number(maxStepsField.value || 0) < recommended) {
1972
+ maxStepsField.value = String(recommended);
1973
+ }
1974
+ schedulePreferenceSave();
1975
+ });
1958
1976
 
1959
1977
  saveApiKeyButton?.addEventListener("click", async () => {
1960
1978
  const provider = setupProviderField.value || "deepseek";
@@ -2167,6 +2185,7 @@ async function loadConfig() {
2167
2185
  packageInstallPolicyField.value = prefs.packageInstallPolicy || "allow";
2168
2186
  document.querySelector("#allowShellTool").checked = prefs.allowShellTool ?? true;
2169
2187
  document.querySelector("#allowFileTools").checked = prefs.allowFileTools ?? true;
2188
+ if (allowAuxiliaryToolsField) allowAuxiliaryToolsField.checked = prefs.allowAuxiliaryTools ?? true;
2170
2189
  allowWrapperToolsField.checked = prefs.allowWrapperTools ?? false;
2171
2190
  preferredWrapperField.value = prefs.preferredWrapper || "codex";
2172
2191
  document.querySelector("#dockerSandboxImage").value = prefs.dockerSandboxImage || "agintiflow-sandbox:latest";
package/public/index.html CHANGED
@@ -57,6 +57,7 @@
57
57
  <select id="setup-provider">
58
58
  <option value="deepseek">DeepSeek</option>
59
59
  <option value="openai">OpenAI</option>
60
+ <option value="grsai">GRS AI image</option>
60
61
  </select>
61
62
  </label>
62
63
  <label>
@@ -175,6 +176,11 @@
175
176
  <span class="switch" aria-hidden="true"></span>
176
177
  <span data-i18n="fileToolLabel">Enable file tools</span>
177
178
  </label>
179
+ <label class="switch-label">
180
+ <input id="allowAuxiliaryTools" name="allowAuxiliaryTools" type="checkbox" checked />
181
+ <span class="switch" aria-hidden="true"></span>
182
+ <span data-i18n="auxiliaryToolLabel">Enable auxiliary skills</span>
183
+ </label>
178
184
  <label class="switch-label">
179
185
  <input id="allowPasswords" name="allowPasswords" type="checkbox" />
180
186
  <span class="switch" aria-hidden="true"></span>
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env node
2
+ import fs from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { runAgent } from "../src/agent-runner.js";
7
+ import { generateImage, listAuxiliarySkills } from "../src/auxiliary-tools.js";
8
+ import { resolveRuntimeConfig } from "../src/config.js";
9
+ import { providerKeyStatus, setProviderKey } from "../src/project.js";
10
+ import { SessionStore } from "../src/session-store.js";
11
+
12
+ const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
13
+ const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "agintiflow-auxiliary-"));
14
+ const runtimeDir = path.join(tempRoot, "runtime");
15
+ const workspace = path.join(tempRoot, "workspace");
16
+ await fs.mkdir(workspace, { recursive: true });
17
+
18
+ function assert(condition, message) {
19
+ if (!condition) throw new Error(message);
20
+ }
21
+
22
+ try {
23
+ await setProviderKey(workspace, "grsai", "test-grsai-key");
24
+ const keyStatus = providerKeyStatus(workspace);
25
+ assert(keyStatus.grsai, "GRSAI key status was not detected");
26
+ assert(keyStatus.envVars.grsai.includes("GRSAI"), "GRSAI env var name was not reported");
27
+ assert(listAuxiliarySkills().some((skill) => skill.id === "image_generation"), "image_generation skill missing");
28
+
29
+ const dryRun = await generateImage(
30
+ {
31
+ prompt: "A small cyan robot holding a paintbrush, clean bright product illustration.",
32
+ outputDir: "artifacts/images/dry-run",
33
+ outputStem: "robot",
34
+ dryRun: true,
35
+ },
36
+ {
37
+ commandCwd: workspace,
38
+ allowFileTools: true,
39
+ }
40
+ );
41
+ assert(dryRun.ok && dryRun.dryRun, "generate_image dry run failed");
42
+ await fs.access(path.join(workspace, "artifacts/images/dry-run/task_manifest.json"));
43
+ const payloadText = await fs.readFile(path.join(workspace, "artifacts/images/dry-run/request_payload.redacted.json"), "utf8");
44
+ assert(payloadText.includes("nano-banana-2"), "redacted image payload was not written");
45
+
46
+ const blocked = await generateImage(
47
+ {
48
+ prompt: "blocked",
49
+ outputDir: ".env/images",
50
+ dryRun: true,
51
+ },
52
+ {
53
+ commandCwd: workspace,
54
+ allowFileTools: true,
55
+ }
56
+ );
57
+ assert(blocked.blocked, "generate_image did not block sensitive output path");
58
+
59
+ const config = resolveRuntimeConfig(
60
+ {
61
+ provider: "mock",
62
+ routingMode: "manual",
63
+ model: "mock-agent",
64
+ goal: "Generate an image of a panda astronaut.",
65
+ commandCwd: workspace,
66
+ allowFileTools: true,
67
+ allowAuxiliaryTools: true,
68
+ maxSteps: 4,
69
+ },
70
+ {
71
+ baseDir: runtimeDir,
72
+ packageDir: repoRoot,
73
+ provider: "mock",
74
+ }
75
+ );
76
+ const run = await runAgent(config);
77
+ const store = new SessionStore(config.sessionsDir, run.sessionId);
78
+ const events = await store.loadEvents();
79
+ assert(events.some((event) => event.type === "tool.completed" && event.data?.toolName === "generate_image"), "mock run did not call generate_image");
80
+ await fs.access(path.join(workspace, "artifacts/images/mock-image/task_manifest.json"));
81
+
82
+ console.log(
83
+ JSON.stringify(
84
+ {
85
+ ok: true,
86
+ workspace,
87
+ checks: ["grsai_key_status", "image_skill_listed", "generate_image_dry_run", "generate_image_guardrail", "mock_agent_image_tool"],
88
+ },
89
+ null,
90
+ 2
91
+ )
92
+ );
93
+ } finally {
94
+ await fs.rm(tempRoot, { recursive: true, force: true });
95
+ }
@@ -86,6 +86,53 @@ try {
86
86
  goal: "patch this large codebase and migrate the database tests",
87
87
  });
88
88
  assert(/pro/i.test(patchRoute.model), "patch/refactor task did not route to DeepSeek pro");
89
+ const largeProfileRoute = selectModelRoute({
90
+ routingMode: "smart",
91
+ provider: "deepseek",
92
+ goal: "fix this bug",
93
+ taskProfile: "large-codebase",
94
+ });
95
+ assert(/pro/i.test(largeProfileRoute.model), "large-codebase profile did not route to DeepSeek pro");
96
+
97
+ await fs.mkdir(path.join(workspace, "src"), { recursive: true });
98
+ await fs.mkdir(path.join(workspace, "test"), { recursive: true });
99
+ await fs.writeFile(
100
+ path.join(workspace, "package.json"),
101
+ JSON.stringify(
102
+ {
103
+ name: "agintiflow-inspect-smoke",
104
+ scripts: {
105
+ test: "node --test test/index.test.js",
106
+ check: "node --check src/index.js",
107
+ },
108
+ },
109
+ null,
110
+ 2
111
+ ),
112
+ "utf8"
113
+ );
114
+ await fs.writeFile(path.join(workspace, "src/index.js"), "export function answer() { return 42; }\n", "utf8");
115
+ await fs.writeFile(path.join(workspace, "test/index.test.js"), "import test from 'node:test';\n", "utf8");
116
+ const inspected = await executeWorkspaceTool(
117
+ "inspect_project",
118
+ { path: ".", maxDepth: 4, limit: 200 },
119
+ {
120
+ commandCwd: workspace,
121
+ allowFileTools: true,
122
+ }
123
+ );
124
+ assert(inspected.ok, "inspect_project failed");
125
+ assert(inspected.manifestFiles.some((item) => item.path === "package.json"), "inspect_project did not find package.json");
126
+ assert(inspected.packageScripts.some((item) => item.name === "test"), "inspect_project did not extract package scripts");
127
+ assert(inspected.sourceDirs.some((item) => item.path === "src"), "inspect_project did not identify src directory");
128
+ assert(inspected.testFiles.some((item) => item.path === "test/index.test.js"), "inspect_project did not identify test file");
129
+ assert(inspected.recommendedReads.includes("package.json"), "inspect_project did not recommend package.json");
130
+
131
+ const inspectRun = await runMock("Inspect this large codebase and recommend next reads.", "coding-inspect");
132
+ assert(
133
+ inspectRun.events.some((event) => event.type === "tool.completed" && event.data?.toolName === "inspect_project"),
134
+ "mock large-codebase run did not use inspect_project"
135
+ );
89
136
 
90
137
  const writeRun = await runMock("Create notes/hello.md with a short coding smoke message.", "coding-write");
91
138
  const written = await fs.readFile(path.join(workspace, "notes/hello.md"), "utf8");
@@ -216,6 +263,9 @@ try {
216
263
  checks: [
217
264
  "deepseek_history_repair",
218
265
  "deepseek_pro_patch_route",
266
+ "large_profile_pro_route",
267
+ "inspect_project",
268
+ "mock_inspect_project",
219
269
  "write_file",
220
270
  "duplicate_write_failed",
221
271
  "resume_session_write",
@@ -17,6 +17,7 @@ import { redactSensitiveText, redactValue } from "./redaction.js";
17
17
  import { executeWorkspaceTool, resolveWorkspacePath, summarizeWorkspaceTools, WORKSPACE_TOOL_NAMES } from "./workspace-tools.js";
18
18
  import { normalizeCanvasPayload } from "./artifact-tunnel.js";
19
19
  import { getTaskProfile } from "./task-profiles.js";
20
+ import { generateImage, listAuxiliarySkills } from "./auxiliary-tools.js";
20
21
 
21
22
  const exec = promisify(execCallback);
22
23
  const BROWSER_TOOLS = new Set(["open_url", "open_workspace_file", "preview_workspace", "click", "type", "scroll", "press", "back"]);
@@ -271,16 +272,21 @@ function createInitialState(config, sessionId) {
271
272
  : "A host shell command tool is available under the configured trust policy."
272
273
  : "No shell command tool is available.",
273
274
  config.allowFileTools
274
- ? `Workspace file tools are available in ${config.commandCwd}: list_files, read_file, search_files, write_file, apply_patch, open_workspace_file, and preview_workspace. apply_patch supports exact single-file replacements plus Codex-style/unified multi-file patches; prefer it for source edits after reading/searching the relevant context. Always use workspace-relative paths such as plot_fx.svg or docs/report.tex, never absolute host paths. Secret paths, .git internals, node_modules writes, and huge files are blocked. For generated local websites/pages, use open_workspace_file or preview_workspace instead of starting a localhost server inside Docker.`
275
+ ? `Workspace file tools are available in ${config.commandCwd}: inspect_project, list_files, read_file, search_files, write_file, apply_patch, open_workspace_file, and preview_workspace. For large or unfamiliar repositories, call inspect_project first, then search/read exact files before editing. apply_patch supports exact single-file replacements plus Codex-style/unified multi-file patches; prefer it for source edits after reading/searching the relevant context. Always use workspace-relative paths such as plot_fx.svg or docs/report.tex, never absolute host paths. Secret paths, .git internals, node_modules writes, and huge files are blocked. For generated local websites/pages, use open_workspace_file or preview_workspace instead of starting a localhost server inside Docker.`
275
276
  : "No workspace file tools are available.",
276
277
  config.allowWrapperTools
277
278
  ? `External coding-agent wrappers are available as advisory tools only. Use the selected wrapper only: ${normalizeWrapperName(config.preferredWrapper)}. Wrapper status: ${wrapperStatusText()}.`
278
279
  : "External coding-agent wrappers are disabled.",
280
+ config.allowAuxiliaryTools
281
+ ? `Auxiliary skills are available: ${listAuxiliarySkills()
282
+ .map((skill) => `${skill.id} via ${skill.toolName} (${skill.available ? "key available" : `needs ${skill.keyName}`})`)
283
+ .join(", ")}. Use generate_image for real raster image/photo/illustration/cover/poster/logo requests when appropriate; if the key is missing, ask the user to run /auxilliary grsai or aginti login grsai.`
284
+ : "Auxiliary skills are disabled for this run.",
279
285
  `Task profile: ${taskProfile.label}. ${taskProfile.prompt}`,
280
286
  "A frontend canvas/artifacts tunnel exists. Use send_to_canvas when important markdown, diffs, screenshots, images, or workspace files should be highlighted in the UI. It is optional and ordinary final text can still go directly to finish.",
281
287
  "For visual-output requests such as draw, plot, graph, chart, diagram, figure, image, or visualization, proactively publish a canvas artifact even when the user does not mention canvas. If workspace file tools are enabled, prefer creating a small SVG or markdown artifact and call send_to_canvas with selected=true.",
282
- "Work like a practical coding agent: inspect when useful, patch code with apply_patch, run safe checks when they add confidence, iterate on failures, and keep outputs inside the workspace.",
283
- "For large projects, decompose into useful files and milestones, implement a coherent minimal version first, then iterate with checks rather than only describing what you would do.",
288
+ "Work like a practical coding agent: orient with inspect_project/search/read, patch code with apply_patch, run safe checks when they add confidence, iterate on failures, and keep outputs inside the workspace.",
289
+ "For large projects, decompose into useful files and milestones, identify entry points/tests/contracts first, implement a coherent minimal version, then iterate with checks rather than only describing what you would do.",
284
290
  "For website/app/code/LaTeX/Python/C/shell tasks, create or edit real workspace files, run available build/compile/test commands, and surface artifacts through the canvas when useful.",
285
291
  "For research or web-search tasks, use browser tools or safe shell network tools when the current policy allows; cite or save useful sources in workspace notes when the task needs traceability.",
286
292
  "Use the canvas tunnel for outputs the user would likely want to inspect visually, such as figures, PDFs, screenshots, images, important markdown, or generated files.",
@@ -304,15 +310,20 @@ function createInitialState(config, sessionId) {
304
310
  : `Shell working directory: ${config.commandCwd}`
305
311
  : "",
306
312
  config.allowFileTools
307
- ? `Workspace file tools enabled in: ${config.commandCwd}. Use workspace-relative paths. Use apply_patch for code edits; it accepts exact replacements or Codex-style/unified multi-file patches. Local preview tools available: open_workspace_file and preview_workspace.`
313
+ ? `Workspace file tools enabled in: ${config.commandCwd}. Use inspect_project first for large/unfamiliar codebases. Use workspace-relative paths. Use apply_patch for code edits; it accepts exact replacements or Codex-style/unified multi-file patches. Local preview tools available: open_workspace_file and preview_workspace.`
308
314
  : "",
309
315
  config.allowWrapperTools
310
316
  ? `Agent wrappers: selected=${normalizeWrapperName(config.preferredWrapper)}; ${wrapperStatusText()}`
311
317
  : "",
318
+ config.allowAuxiliaryTools
319
+ ? `Auxiliary skills: ${listAuxiliarySkills()
320
+ .map((skill) => `${skill.id}:${skill.available ? "available" : "missing-key"}`)
321
+ .join(" ")}`
322
+ : "",
312
323
  `Task profile: ${taskProfile.label}. ${taskProfile.prompt}`,
313
324
  "Canvas/artifacts tunnel: available through send_to_canvas for optional frontend rendering.",
314
325
  "Visual-output requests should produce a canvas artifact without requiring the user to ask for canvas explicitly.",
315
- "Use file, shell, browser, canvas, and wrapper tools when they are useful; choose the workflow from the user's request.",
326
+ "Use file, shell, browser, canvas, and wrapper tools when they are useful; choose the workflow from the user's request. For complicated engineering tasks, keep a tight loop: inspect, choose minimal files, patch, run focused checks, repair, then summarize.",
316
327
  "Do not stop at a plan when tools can accomplish the request. Continue through implementation, checks, artifact selection, and finish.",
317
328
  "Use the configured sandbox and package policy for environment or system-maintenance work.",
318
329
  ]
@@ -419,7 +430,7 @@ function applyContinuationPrompt(state, config, observers) {
419
430
  : `Shell working directory: ${config.commandCwd}`
420
431
  : "",
421
432
  config.allowFileTools
422
- ? `Workspace file tools enabled in: ${config.commandCwd}. Use workspace-relative paths. For generated local files/sites, use open_workspace_file or preview_workspace.`
433
+ ? `Workspace file tools enabled in: ${config.commandCwd}. Use inspect_project first for large or unfamiliar codebases, then search/read exact files before editing. Use workspace-relative paths. Use apply_patch for code edits; it accepts exact replacements or Codex-style/unified multi-file patches. For generated local files/sites, use open_workspace_file or preview_workspace.`
423
434
  : "",
424
435
  config.allowWrapperTools
425
436
  ? `Agent wrappers: selected=${normalizeWrapperName(config.preferredWrapper)}; ${wrapperStatusText()}`
@@ -504,6 +515,15 @@ function sanitizeToolArgs(toolName, args) {
504
515
  replace: typeof args.replace === "string" ? redactSensitiveText(args.replace).slice(0, 160) : safeArgs.replace,
505
516
  };
506
517
  }
518
+ if (toolName === "generate_image") {
519
+ return {
520
+ ...safeArgs,
521
+ prompt: typeof args.prompt === "string" ? `[${Buffer.byteLength(args.prompt, "utf8")} bytes sha256=${hashForLog(args.prompt)}]` : safeArgs.prompt,
522
+ referenceImages: Array.isArray(args.referenceImages)
523
+ ? args.referenceImages.map((item) => (String(item || "").startsWith("data:") ? `[data-uri ${String(item).length} chars]` : redactSensitiveText(item)))
524
+ : safeArgs.referenceImages,
525
+ };
526
+ }
507
527
  return safeArgs;
508
528
  }
509
529
 
@@ -611,7 +631,7 @@ async function captureSyntheticSnapshot(store, step, config) {
611
631
  : `Shell tool available in: ${config.commandCwd}`
612
632
  : "Shell tool disabled.",
613
633
  config.allowFileTools
614
- ? `Workspace file tools available in: ${config.commandCwd}. Use workspace-relative paths. Use apply_patch for code edits; it supports exact single-file replacement and multi-file Codex-style/unified patches.`
634
+ ? `Workspace file tools available in: ${config.commandCwd}. Use inspect_project first for large or unfamiliar codebases, then search/read exact files before editing. Use workspace-relative paths. Use apply_patch for code edits; it supports exact single-file replacement and multi-file Codex-style/unified patches.`
615
635
  : "Workspace file tools disabled.",
616
636
  config.allowWrapperTools
617
637
  ? `Agent wrappers available: selected=${normalizeWrapperName(config.preferredWrapper)}; ${wrapperStatusText()}`
@@ -795,6 +815,7 @@ async function executeTool(browserState, toolCall, snapshot, config, store, obse
795
815
  await new Promise((resolve) => setTimeout(resolve, Number.isFinite(args.ms) ? Number(args.ms) : 1000));
796
816
  }
797
817
  break;
818
+ case "inspect_project":
798
819
  case "list_files":
799
820
  case "read_file":
800
821
  case "search_files":
@@ -874,6 +895,67 @@ async function executeTool(browserState, toolCall, snapshot, config, store, obse
874
895
  observers.event("tool.completed", result);
875
896
  return result;
876
897
  }
898
+ case "generate_image": {
899
+ const imageResult = await generateImage(args, config);
900
+ const result = {
901
+ ok: Boolean(imageResult.ok),
902
+ toolName: "generate_image",
903
+ args: safeArgs,
904
+ ...imageResult,
905
+ };
906
+ const eventResult = sanitizeToolResult(result);
907
+ await store.appendEvent("tool.completed", eventResult);
908
+ observers.event("tool.completed", eventResult);
909
+
910
+ if (result.ok) {
911
+ const generated = {
912
+ path: result.path,
913
+ imagePaths: result.imagePaths || [],
914
+ manifestPath: result.manifestPath || "",
915
+ promptPath: result.promptPath || "",
916
+ requestPayloadPath: result.requestPayloadPath || "",
917
+ commandCwd: config.commandCwd,
918
+ };
919
+ await store.appendEvent("image.generated", generated);
920
+ observers.event("image.generated", generated);
921
+
922
+ const selectedPath = result.imagePaths?.[0] || result.manifestPath || "";
923
+ if (selectedPath) {
924
+ const normalized = normalizeCanvasPayload(
925
+ {
926
+ title: result.imagePaths?.length ? "Generated image" : "Image generation payload",
927
+ kind: result.imagePaths?.length ? "image" : "json",
928
+ path: selectedPath,
929
+ note: result.summary || "Generated image artifact.",
930
+ selected: Boolean(result.imagePaths?.length),
931
+ },
932
+ config
933
+ );
934
+ if (normalized.ok) {
935
+ const canvasItem = {
936
+ ...normalized.payload,
937
+ toolName: "generate_image",
938
+ commandCwd: config.commandCwd,
939
+ };
940
+ await store.appendEvent("canvas.item", canvasItem);
941
+ observers.event("canvas.item", canvasItem);
942
+ if (canvasItem.selected) {
943
+ await store.appendEvent("canvas.selected", {
944
+ artifactId: canvasItem.artifactId,
945
+ title: canvasItem.title,
946
+ source: "generate_image",
947
+ });
948
+ observers.event("canvas.selected", {
949
+ artifactId: canvasItem.artifactId,
950
+ title: canvasItem.title,
951
+ source: "generate_image",
952
+ });
953
+ }
954
+ }
955
+ }
956
+ }
957
+ return result;
958
+ }
877
959
  case "send_to_canvas": {
878
960
  const normalized = normalizeCanvasPayload(args, config);
879
961
  if (!normalized.ok) {