@lazyingart/agintiflow 0.12.1 → 0.12.3
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 +3 -1
- package/docs/large-codebase-engineering.md +4 -2
- package/package.json +1 -1
- package/scripts/smoke-capabilities.js +5 -1
- package/scripts/smoke-cli-chat.js +32 -2
- package/src/agent-runner.js +44 -7
- package/src/capabilities.js +6 -2
- package/src/cli.js +2 -0
- package/src/interactive-cli.js +402 -29
- package/src/model-client.js +13 -1
- package/src/parallel-scouts.js +3 -0
- package/src/project.js +65 -1
- package/src/task-profiles.js +12 -12
- package/src/workspace-tools.js +3 -2
package/README.md
CHANGED
|
@@ -67,7 +67,9 @@ aginti
|
|
|
67
67
|
aginti chat
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
-
Inside chat, type normal requests such as `write a small Python CLI app with tests`. The default is Docker workspace mode with approved package installs, so coding, plotting, and LaTeX tasks can set up project-local tools without touching the host. Use `/help` for commands, `/login` or `/auth` to paste a provider key, `/latex on` for PDF work, `/docker off` only when you intentionally want host mode, `/sessions` to list project runs, and `/resume latest` or `/resume <session-id>` to continue work. Type `/` then Tab for command completion. `Ctrl+J` inserts a new line in the colored input panel,
|
|
70
|
+
Inside chat, type normal requests such as `write a small Python CLI app with tests`. The default is Docker workspace mode with approved package installs, so coding, plotting, and LaTeX tasks can set up project-local tools without touching the host. Use `/help` for commands, `/login` or `/auth` to paste a provider key, `/instructions` to inspect `AGINTI.md`, `/latex on` for PDF work, `/docker off` only when you intentionally want host mode, `/sessions` to list project runs, and `/resume latest` or `/resume <session-id>` to continue work. Type `/` then Tab for command completion. `Ctrl+J` inserts a new line in the colored input panel, Enter sends, arrow keys move through wrapped multiline input, and `Ctrl+A`/`Ctrl+E` jump to the current line start/end. Assistant responses render common Markdown, including headings, inline code, bold text, lists, quotes, code fences, and tables. Esc or Ctrl+C stops the active run cleanly and prints the resume command.
|
|
71
|
+
|
|
72
|
+
`aginti init` creates `AGINTI.md` at the project root. This is the editable project-instruction file for both CLI and web runs, similar to `AGENTS.md` or project memory in other agents. Keep durable preferences, commands, and constraints there, but never secrets. You can edit it manually or ask in chat, for example: `update AGINTI.md to remember that this project uses pytest and npm run check`.
|
|
71
73
|
|
|
72
74
|
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).
|
|
73
75
|
|
|
@@ -11,7 +11,7 @@ Local agent references informed the design:
|
|
|
11
11
|
- Claude/Claw-style safety: project-local status, container-first execution, read-only operations by default, and clear failure recovery.
|
|
12
12
|
- Gemini/Qwen-style extensibility: capability discovery through profiles and tools rather than hardcoding one model behavior.
|
|
13
13
|
- Claw-style `doctor` discipline: check health and environment before treating system symptoms as code bugs.
|
|
14
|
-
- Claude/Codex-style context discipline: read instructions, manifests, entry points, and failing tests before touching broad files.
|
|
14
|
+
- Claude/Codex-style context discipline: read project instructions, manifests, entry points, and failing tests before touching broad files.
|
|
15
15
|
|
|
16
16
|
## Skill vs Tool
|
|
17
17
|
|
|
@@ -31,7 +31,7 @@ The `inspect_project` function is a tool: it deterministically scans the workspa
|
|
|
31
31
|
For complicated tasks, the agent should follow this loop:
|
|
32
32
|
|
|
33
33
|
1. `inspect_project` to map the repository.
|
|
34
|
-
2. `read_file` on `AGENTS.md`, `README.md`, and manifests.
|
|
34
|
+
2. `read_file` on `AGINTI.md`, `AGENTS.md`, `README.md`, and manifests.
|
|
35
35
|
3. `search_files` for symbols, tests, errors, routes, or config names.
|
|
36
36
|
4. `read_file` only on the files needed for the change.
|
|
37
37
|
5. `apply_patch` in small coherent batches.
|
|
@@ -54,6 +54,8 @@ Smart routing sends this profile to DeepSeek v4 pro even when the user prompt is
|
|
|
54
54
|
|
|
55
55
|
The web app still defaults to **Auto**. Auto does not mean weak. When the prompt mentions a large repo, system bug, failing tests, setup, install, migration, or a known language stack, AgInTiFlow adds engineering guidance and raises the step budget automatically.
|
|
56
56
|
|
|
57
|
+
Profiles are skill bias, not restrictions. `auto` is the broad general agent; `code`, `latex`, `website`, `maintenance`, and other profiles add stronger habits for that task type while still allowing the agent to use files, shell, browser, web search, canvas, and sandbox tools when they help.
|
|
58
|
+
|
|
57
59
|
Examples:
|
|
58
60
|
|
|
59
61
|
```bash
|
package/package.json
CHANGED
|
@@ -29,9 +29,12 @@ async function runCli(args) {
|
|
|
29
29
|
|
|
30
30
|
try {
|
|
31
31
|
await runCli(["init"]);
|
|
32
|
+
const agintiMd = await fs.readFile(path.join(tempRoot, "AGINTI.md"), "utf8");
|
|
33
|
+
assert(agintiMd.includes("Project instructions for AgInTiFlow agents."), "init did not create AGINTI.md");
|
|
32
34
|
const capabilities = JSON.parse(await runCli(["capabilities", "--json"]));
|
|
33
35
|
assert(capabilities.project.root === tempRoot, "capabilities did not use cwd as project root");
|
|
34
36
|
assert(capabilities.project.commandCwd === tempRoot, "capabilities did not default commandCwd to project root");
|
|
37
|
+
assert(capabilities.project.instructionsPresent, "capabilities did not report AGINTI.md");
|
|
35
38
|
assert(capabilities.project.sharedSessionFolder, "capabilities did not report shared session folder");
|
|
36
39
|
assert(capabilities.keys?.mock === true, "capabilities did not report mock availability");
|
|
37
40
|
assert(
|
|
@@ -69,13 +72,14 @@ try {
|
|
|
69
72
|
|
|
70
73
|
const doctor = JSON.parse(await runCli(["doctor", "--capabilities", "--json"]));
|
|
71
74
|
assert(doctor.project.root === tempRoot, "doctor --capabilities used the wrong project root");
|
|
75
|
+
assert(doctor.project.instructionsPresent, "doctor --capabilities did not report AGINTI.md");
|
|
72
76
|
|
|
73
77
|
console.log(
|
|
74
78
|
JSON.stringify(
|
|
75
79
|
{
|
|
76
80
|
ok: true,
|
|
77
81
|
projectRoot: tempRoot,
|
|
78
|
-
checks: ["capabilities-cli", "doctor-capabilities", "maintenance-policy", "trusted-docker-policy"],
|
|
82
|
+
checks: ["aginti-md-init", "capabilities-cli", "doctor-capabilities", "maintenance-policy", "trusted-docker-policy"],
|
|
79
83
|
},
|
|
80
84
|
null,
|
|
81
85
|
2
|
|
@@ -4,7 +4,7 @@ import fs from "node:fs/promises";
|
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
-
import { stripMarkdown } from "../src/interactive-cli.js";
|
|
7
|
+
import { buildPromptLayout, stripMarkdown } from "../src/interactive-cli.js";
|
|
8
8
|
|
|
9
9
|
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
10
10
|
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "agintiflow-cli-chat-"));
|
|
@@ -70,6 +70,36 @@ try {
|
|
|
70
70
|
throw new Error("terminal markdown renderer dropped table content");
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
const promptLayout = buildPromptLayout(`${"x".repeat(180)}\nsecond line`, 95, 80, 24);
|
|
74
|
+
const visibleLengths = promptLayout.renderedRows.map((line) =>
|
|
75
|
+
line.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, "").length
|
|
76
|
+
);
|
|
77
|
+
if (promptLayout.rows.length < 4 || Math.max(...visibleLengths) > 79) {
|
|
78
|
+
throw new Error("terminal prompt layout did not wrap long multiline input safely");
|
|
79
|
+
}
|
|
80
|
+
if (promptLayout.cursorRow < 0 || promptLayout.cursorColumn < 0) {
|
|
81
|
+
throw new Error("terminal prompt layout returned an invalid cursor location");
|
|
82
|
+
}
|
|
83
|
+
const hugePromptLayout = buildPromptLayout(Array.from({ length: 30 }, (_unused, index) => `line ${index + 1}`).join("\n"), 120, 80, 20);
|
|
84
|
+
if (hugePromptLayout.renderedRows.length > 12 || !hugePromptLayout.renderedRows.some((line) => line.includes("earlier input row"))) {
|
|
85
|
+
throw new Error("terminal prompt layout did not bound redraw size for large prompts");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
await runCli(["init"], "");
|
|
89
|
+
const instructions = await fs.readFile(path.join(tempRoot, "AGINTI.md"), "utf8");
|
|
90
|
+
if (!instructions.includes("Project instructions for AgInTiFlow agents.")) {
|
|
91
|
+
throw new Error("init did not create AGINTI.md");
|
|
92
|
+
}
|
|
93
|
+
const instructionsResult = await runChat("/instructions\n/exit\n");
|
|
94
|
+
if (!instructionsResult.stdout.includes("AGINTI.md") || !instructionsResult.stdout.includes("Project instructions")) {
|
|
95
|
+
throw new Error("interactive /instructions did not show AGINTI.md");
|
|
96
|
+
}
|
|
97
|
+
await runChat("remember that this project prefers pytest smoke tests in AGINTI.md\n/exit\n");
|
|
98
|
+
const updatedInstructions = await fs.readFile(path.join(tempRoot, "AGINTI.md"), "utf8");
|
|
99
|
+
if (!updatedInstructions.includes("pytest smoke tests")) {
|
|
100
|
+
throw new Error("chat did not update AGINTI.md project instructions in mock mode");
|
|
101
|
+
}
|
|
102
|
+
|
|
73
103
|
const result = await runChat("Create notes/interactive.md with a short CLI chat smoke message\n/exit\n");
|
|
74
104
|
const written = await fs.readFile(path.join(tempRoot, "notes/interactive.md"), "utf8");
|
|
75
105
|
if (!written.includes("Created by AgInTiFlow mock mode.")) {
|
|
@@ -92,7 +122,7 @@ try {
|
|
|
92
122
|
{
|
|
93
123
|
ok: true,
|
|
94
124
|
projectRoot: tempRoot,
|
|
95
|
-
checks: ["markdown-render", "interactive-chat", "mock-file-write", "run-status", "resume-latest"],
|
|
125
|
+
checks: ["markdown-render", "prompt-layout", "aginti-md", "instructions-command", "instructions-chat-edit", "interactive-chat", "mock-file-write", "run-status", "resume-latest"],
|
|
96
126
|
},
|
|
97
127
|
null,
|
|
98
128
|
2
|
package/src/agent-runner.js
CHANGED
|
@@ -21,6 +21,7 @@ import { generateImage, listAuxiliarySkills } from "./auxiliary-tools.js";
|
|
|
21
21
|
import { engineeringGuidanceForTask } from "./engineering-guidance.js";
|
|
22
22
|
import { searchWeb } from "./web-search.js";
|
|
23
23
|
import { runParallelScouts, shouldRunParallelScouts } from "./parallel-scouts.js";
|
|
24
|
+
import { readProjectInstructions } from "./project.js";
|
|
24
25
|
|
|
25
26
|
const exec = promisify(execCallback);
|
|
26
27
|
const BROWSER_TOOLS = new Set(["open_url", "open_workspace_file", "preview_workspace", "click", "type", "scroll", "press", "back"]);
|
|
@@ -41,6 +42,20 @@ function throwIfAborted(config) {
|
|
|
41
42
|
}
|
|
42
43
|
}
|
|
43
44
|
|
|
45
|
+
function formatProjectInstructions(instructions) {
|
|
46
|
+
if (!instructions?.exists) {
|
|
47
|
+
return "Project instructions file: AGINTI.md is not present. If the user wants durable project preferences, create or update AGINTI.md in the workspace.";
|
|
48
|
+
}
|
|
49
|
+
const suffix = instructions.truncated ? "\n[AGINTI.md was truncated for context. Read the file if more detail is needed.]" : "";
|
|
50
|
+
return [
|
|
51
|
+
`Project instructions from AGINTI.md (${instructions.path}):`,
|
|
52
|
+
redactSensitiveText(instructions.content).trim() || "(empty)",
|
|
53
|
+
suffix,
|
|
54
|
+
]
|
|
55
|
+
.filter(Boolean)
|
|
56
|
+
.join("\n");
|
|
57
|
+
}
|
|
58
|
+
|
|
44
59
|
function isAbortError(error, config = {}) {
|
|
45
60
|
return Boolean(
|
|
46
61
|
config.abortSignal?.aborted ||
|
|
@@ -233,10 +248,12 @@ export function repairModelMessageHistory(state, config = {}) {
|
|
|
233
248
|
};
|
|
234
249
|
}
|
|
235
250
|
|
|
236
|
-
function createInitialState(config, sessionId) {
|
|
251
|
+
async function createInitialState(config, sessionId) {
|
|
237
252
|
const now = new Date().toISOString();
|
|
238
253
|
const taskProfile = getTaskProfile(config.taskProfile);
|
|
239
254
|
const engineeringGuidance = engineeringGuidanceForTask(config.goal, config.taskProfile);
|
|
255
|
+
const projectInstructions = await readProjectInstructions(config.baseDir || config.commandCwd || process.cwd());
|
|
256
|
+
const projectInstructionContext = formatProjectInstructions(projectInstructions);
|
|
240
257
|
return {
|
|
241
258
|
sessionId,
|
|
242
259
|
createdAt: now,
|
|
@@ -249,6 +266,12 @@ function createInitialState(config, sessionId) {
|
|
|
249
266
|
stepsCompleted: 0,
|
|
250
267
|
meta: {
|
|
251
268
|
lastUrl: "",
|
|
269
|
+
projectInstructions: {
|
|
270
|
+
path: projectInstructions.path,
|
|
271
|
+
exists: projectInstructions.exists,
|
|
272
|
+
truncated: projectInstructions.truncated,
|
|
273
|
+
loadedAt: now,
|
|
274
|
+
},
|
|
252
275
|
},
|
|
253
276
|
chat: [
|
|
254
277
|
{
|
|
@@ -270,13 +293,15 @@ function createInitialState(config, sessionId) {
|
|
|
270
293
|
"Prefer short, deliberate actions over guessing.",
|
|
271
294
|
"Never navigate outside the allowed domains when an allowlist exists.",
|
|
272
295
|
"Avoid destructive actions, purchases, account changes, and sensitive workflows.",
|
|
296
|
+
projectInstructionContext,
|
|
297
|
+
"Treat AGINTI.md as durable project memory and operating instructions for this project. The user can edit it manually or ask you in chat to update it; use workspace file tools for that and never store secrets there.",
|
|
273
298
|
config.allowShellTool
|
|
274
299
|
? config.useDockerSandbox
|
|
275
300
|
? `A shell command tool is available inside Docker sandbox mode ${config.sandboxMode}. Docker workspace mode with approved package installs supports broader setup and network commands. The project is mounted at /workspace and the persistent agent toolchain is mounted at /aginti-env with caches under /aginti-cache.`
|
|
276
301
|
: "A host shell command tool is available under the configured trust policy."
|
|
277
302
|
: "No shell command tool is available.",
|
|
278
303
|
config.allowFileTools
|
|
279
|
-
? `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
|
|
304
|
+
? `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 AGINTI.md/AGENTS.md/README/manifests as relevant 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.`
|
|
280
305
|
: "No workspace file tools are available.",
|
|
281
306
|
config.allowWrapperTools
|
|
282
307
|
? `External coding-agent wrappers are available as advisory tools only. Use the selected wrapper only: ${normalizeWrapperName(config.preferredWrapper)}. Wrapper status: ${wrapperStatusText()}.`
|
|
@@ -321,8 +346,9 @@ function createInitialState(config, sessionId) {
|
|
|
321
346
|
: `Shell working directory: ${config.commandCwd}`
|
|
322
347
|
: "",
|
|
323
348
|
config.allowFileTools
|
|
324
|
-
? `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.`
|
|
349
|
+
? `Workspace file tools enabled in: ${config.commandCwd}. Use inspect_project first for large/unfamiliar codebases. Read AGINTI.md/AGENTS.md/README/manifests when relevant. 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.`
|
|
325
350
|
: "",
|
|
351
|
+
projectInstructions.exists ? "AGINTI.md project instructions are loaded into system context for this run." : "AGINTI.md is not present unless you create it.",
|
|
326
352
|
config.allowWrapperTools
|
|
327
353
|
? `Agent wrappers: selected=${normalizeWrapperName(config.preferredWrapper)}; ${wrapperStatusText()}`
|
|
328
354
|
: "",
|
|
@@ -420,11 +446,19 @@ function appendChatEntry(state, role, content) {
|
|
|
420
446
|
});
|
|
421
447
|
}
|
|
422
448
|
|
|
423
|
-
function applyContinuationPrompt(state, config, observers) {
|
|
449
|
+
async function applyContinuationPrompt(state, config, observers) {
|
|
424
450
|
if (!config.resume || !config.goal) return;
|
|
425
451
|
|
|
426
452
|
const taskProfile = getTaskProfile(config.taskProfile);
|
|
427
453
|
const engineeringGuidance = engineeringGuidanceForTask(config.goal, config.taskProfile);
|
|
454
|
+
const projectInstructions = await readProjectInstructions(config.baseDir || config.commandCwd || process.cwd());
|
|
455
|
+
state.meta = state.meta || {};
|
|
456
|
+
state.meta.projectInstructions = {
|
|
457
|
+
path: projectInstructions.path,
|
|
458
|
+
exists: projectInstructions.exists,
|
|
459
|
+
truncated: projectInstructions.truncated,
|
|
460
|
+
loadedAt: new Date().toISOString(),
|
|
461
|
+
};
|
|
428
462
|
ensureChatState(state);
|
|
429
463
|
state.goal = config.goal;
|
|
430
464
|
state.provider = config.provider;
|
|
@@ -445,13 +479,15 @@ function applyContinuationPrompt(state, config, observers) {
|
|
|
445
479
|
: `Shell working directory: ${config.commandCwd}`
|
|
446
480
|
: "",
|
|
447
481
|
config.allowFileTools
|
|
448
|
-
? `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.`
|
|
482
|
+
? `Workspace file tools enabled in: ${config.commandCwd}. Use inspect_project first for large or unfamiliar codebases, then search/read exact files before editing. Read AGINTI.md/AGENTS.md/README/manifests when relevant. 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.`
|
|
449
483
|
: "",
|
|
450
484
|
config.allowWrapperTools
|
|
451
485
|
? `Agent wrappers: selected=${normalizeWrapperName(config.preferredWrapper)}; ${wrapperStatusText()}`
|
|
452
486
|
: "",
|
|
453
487
|
`Task profile: ${taskProfile.label}. ${taskProfile.prompt}`,
|
|
454
488
|
engineeringGuidance,
|
|
489
|
+
formatProjectInstructions(projectInstructions),
|
|
490
|
+
"AGINTI.md is editable project memory. If the user asks to remember a preference or update instructions, patch AGINTI.md rather than hiding that preference in session-only chat.",
|
|
455
491
|
]
|
|
456
492
|
.filter(Boolean)
|
|
457
493
|
.join("\n"),
|
|
@@ -1091,7 +1127,7 @@ export async function runAgent(config) {
|
|
|
1091
1127
|
}
|
|
1092
1128
|
|
|
1093
1129
|
if (!state) {
|
|
1094
|
-
state = createInitialState(config, sessionId);
|
|
1130
|
+
state = await createInitialState(config, sessionId);
|
|
1095
1131
|
await store.appendEvent("session.created", {
|
|
1096
1132
|
sessionId,
|
|
1097
1133
|
provider: config.provider,
|
|
@@ -1103,7 +1139,7 @@ export async function runAgent(config) {
|
|
|
1103
1139
|
await store.saveState(state);
|
|
1104
1140
|
} else {
|
|
1105
1141
|
await store.appendEvent("session.resumed", { sessionId });
|
|
1106
|
-
applyContinuationPrompt(state, config, observers);
|
|
1142
|
+
await applyContinuationPrompt(state, config, observers);
|
|
1107
1143
|
await store.saveState(state);
|
|
1108
1144
|
}
|
|
1109
1145
|
|
|
@@ -1263,6 +1299,7 @@ export async function runAgent(config) {
|
|
|
1263
1299
|
commandCwd: config.commandCwd,
|
|
1264
1300
|
plan: state.plan || "",
|
|
1265
1301
|
suggestedStartUrl: config.startUrl || "",
|
|
1302
|
+
projectInstructions: state.meta.projectInstructions || null,
|
|
1266
1303
|
canvasArtifactsAvailable: true,
|
|
1267
1304
|
taskProfile: getTaskProfile(config.taskProfile),
|
|
1268
1305
|
})}`,
|
package/src/capabilities.js
CHANGED
|
@@ -4,7 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import { classifyCommand, evaluateCommandPolicy } from "./command-policy.js";
|
|
5
5
|
import { getDockerSandboxStatus } from "./docker-sandbox.js";
|
|
6
6
|
import { getModelPresets } from "./model-routing.js";
|
|
7
|
-
import { listProjectSessions, projectPaths, providerKeyStatus } from "./project.js";
|
|
7
|
+
import { listProjectSessions, projectPaths, providerKeyStatus, readProjectInstructions } from "./project.js";
|
|
8
8
|
import { listTaskProfiles } from "./task-profiles.js";
|
|
9
9
|
import { listAgentWrappers } from "./tool-wrappers.js";
|
|
10
10
|
import { listAuxiliarySkills } from "./auxiliary-tools.js";
|
|
@@ -110,7 +110,7 @@ function trustedDockerPolicyChecks(config) {
|
|
|
110
110
|
export async function buildCapabilityReport(projectRoot, packageVersion, config) {
|
|
111
111
|
const paths = projectPaths(projectRoot);
|
|
112
112
|
const keyStatus = providerKeyStatus(projectRoot);
|
|
113
|
-
const [node, npm, python, conda, r, pdflatex, latexmk, dockerStatus, sessions] = await Promise.all([
|
|
113
|
+
const [node, npm, python, conda, r, pdflatex, latexmk, dockerStatus, sessions, instructions] = await Promise.all([
|
|
114
114
|
commandAvailable("node", ["--version"]),
|
|
115
115
|
commandAvailable("npm", ["--version"]),
|
|
116
116
|
commandAvailable("python3", ["--version"]),
|
|
@@ -120,6 +120,7 @@ export async function buildCapabilityReport(projectRoot, packageVersion, config)
|
|
|
120
120
|
commandAvailable("latexmk", ["--version"]),
|
|
121
121
|
getDockerSandboxStatus(config).catch((error) => ({ ok: false, error: error.message })),
|
|
122
122
|
listProjectSessions(projectRoot, 12),
|
|
123
|
+
readProjectInstructions(projectRoot, { maxBytes: 1 }),
|
|
123
124
|
]);
|
|
124
125
|
|
|
125
126
|
const npmPrefixPolicy = evaluateCommandPolicy("npm --prefix round9-node-app test", config);
|
|
@@ -167,6 +168,8 @@ export async function buildCapabilityReport(projectRoot, packageVersion, config)
|
|
|
167
168
|
project: {
|
|
168
169
|
root: paths.root,
|
|
169
170
|
commandCwd: path.resolve(config.commandCwd),
|
|
171
|
+
instructionsPath: paths.agintiInstructionsPath,
|
|
172
|
+
instructionsPresent: instructions.exists,
|
|
170
173
|
sessionsDir: paths.sessionsDir,
|
|
171
174
|
sessionDbPath: paths.sessionDbPath,
|
|
172
175
|
sharedSessionFolder: path.resolve(config.sessionsDir) === path.resolve(paths.sessionsDir),
|
|
@@ -230,6 +233,7 @@ export function printCapabilityReport(report) {
|
|
|
230
233
|
console.log(`AgInTiFlow capabilities ${report.package.version}`);
|
|
231
234
|
console.log(`project=${report.project.root}`);
|
|
232
235
|
console.log(`cwd=${report.project.commandCwd}`);
|
|
236
|
+
console.log(`instructions=${report.project.instructionsPath} present=${report.project.instructionsPresent}`);
|
|
233
237
|
console.log(`sessions=${report.project.sessionsDir}`);
|
|
234
238
|
console.log(`sessionDb=${report.project.sessionDbPath}`);
|
|
235
239
|
console.log(`sharedSessions=${report.project.sharedSessionFolder}`);
|
package/src/cli.js
CHANGED
|
@@ -322,6 +322,7 @@ function printProfiles() {
|
|
|
322
322
|
|
|
323
323
|
function printInitResult(result) {
|
|
324
324
|
console.log(`AgInTiFlow project initialized: ${result.projectRoot}`);
|
|
325
|
+
console.log(`instructions=${result.instructionsPath}`);
|
|
325
326
|
console.log(`control=${result.controlDir}`);
|
|
326
327
|
console.log(`sessions=${result.sessionsDir}`);
|
|
327
328
|
console.log(`created=${result.created.length} updated=${result.updated.length} skipped=${result.skipped.length}`);
|
|
@@ -331,6 +332,7 @@ function printDoctorReport(report) {
|
|
|
331
332
|
console.log(`AgInTiFlow ${report.package.version} (npm latest: ${report.package.npmLatest})`);
|
|
332
333
|
console.log(`node=${report.node.version} ok=${report.node.ok}`);
|
|
333
334
|
console.log(`project=${report.project.root}`);
|
|
335
|
+
console.log(`instructions=${report.project.instructionsPath} present=${report.project.instructionsPresent}`);
|
|
334
336
|
console.log(`sessions=${report.project.sessionsDir}`);
|
|
335
337
|
console.log(`sessionDb=${report.project.sessionDbPath}`);
|
|
336
338
|
console.log(
|
package/src/interactive-cli.js
CHANGED
|
@@ -3,7 +3,7 @@ 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, providerKeyStatus, setProviderKey } from "./project.js";
|
|
6
|
+
import { initProject, listProjectSessions, providerKeyStatus, readProjectInstructions, setProviderKey } from "./project.js";
|
|
7
7
|
import { normalizePackageInstallPolicy, normalizeSandboxMode } from "./command-policy.js";
|
|
8
8
|
import { defaultMaxStepsForProfile, normalizeTaskProfile } from "./task-profiles.js";
|
|
9
9
|
import { recommendedMaxStepsForTask } from "./engineering-guidance.js";
|
|
@@ -34,6 +34,8 @@ const SLASH_COMMANDS = [
|
|
|
34
34
|
"/status",
|
|
35
35
|
"/login",
|
|
36
36
|
"/auth",
|
|
37
|
+
"/instructions",
|
|
38
|
+
"/memory",
|
|
37
39
|
"/auxilliary",
|
|
38
40
|
"/auxiliary",
|
|
39
41
|
"/new",
|
|
@@ -53,6 +55,7 @@ const SLASH_COMMANDS = [
|
|
|
53
55
|
"/web",
|
|
54
56
|
"/exit",
|
|
55
57
|
];
|
|
58
|
+
const promptHistory = [];
|
|
56
59
|
|
|
57
60
|
function color(value, ...codes) {
|
|
58
61
|
if (!useColor || codes.length === 0) return String(value);
|
|
@@ -71,6 +74,18 @@ function terminalWidth() {
|
|
|
71
74
|
return Math.max(Number(output.columns) || 80, 40);
|
|
72
75
|
}
|
|
73
76
|
|
|
77
|
+
function terminalHeight() {
|
|
78
|
+
return Math.max(Number(output.rows) || 24, 10);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function editorWidth(width = terminalWidth()) {
|
|
82
|
+
return Math.max(Number(width) - 1, 39);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function promptViewportRows(height = terminalHeight()) {
|
|
86
|
+
return Math.max(Math.min(Math.floor(Number(height) * 0.42), 10), 4);
|
|
87
|
+
}
|
|
88
|
+
|
|
74
89
|
function visualLength(value) {
|
|
75
90
|
return stripAnsi(value).length;
|
|
76
91
|
}
|
|
@@ -80,10 +95,11 @@ function padVisible(value, width) {
|
|
|
80
95
|
return `${value}${" ".repeat(padding)}`;
|
|
81
96
|
}
|
|
82
97
|
|
|
83
|
-
function panelLine(content = "", bgCode = ansi.systemBg) {
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
98
|
+
function panelLine(content = "", bgCode = ansi.systemBg, width = editorWidth()) {
|
|
99
|
+
const raw = String(content || "");
|
|
100
|
+
const safeContent = visualLength(raw) > width ? stripAnsi(raw).slice(0, width) : raw;
|
|
101
|
+
if (!useColor) return padVisible(safeContent, width);
|
|
102
|
+
const padded = padVisible(safeContent, width).replaceAll(ansi.reset, `${ansi.reset}${bgCode}`);
|
|
87
103
|
return `${bgCode}${padded}${ansi.reset}`;
|
|
88
104
|
}
|
|
89
105
|
|
|
@@ -113,6 +129,10 @@ function commandSuggestions(line = "") {
|
|
|
113
129
|
return SLASH_COMMANDS.filter((command) => command.startsWith(trimmed)).slice(0, 8);
|
|
114
130
|
}
|
|
115
131
|
|
|
132
|
+
function clamp(value, min, max) {
|
|
133
|
+
return Math.min(Math.max(value, min), max);
|
|
134
|
+
}
|
|
135
|
+
|
|
116
136
|
export function stripMarkdown(text) {
|
|
117
137
|
const lines = String(text || "").split(/\r?\n/);
|
|
118
138
|
let inFence = false;
|
|
@@ -319,6 +339,8 @@ function printHelp() {
|
|
|
319
339
|
" /status Show active route, workspace, sandbox, and session.",
|
|
320
340
|
" /login [deepseek|openai|grsai] Paste and save a project-local API key.",
|
|
321
341
|
" /auth [deepseek|openai|grsai] Alias for /login.",
|
|
342
|
+
" /instructions Show AGINTI.md project instructions status.",
|
|
343
|
+
" /memory Alias for /instructions.",
|
|
322
344
|
" /auxilliary [status|grsai|on|off|image]",
|
|
323
345
|
" Manage optional auxiliary skills, including GRS AI image generation.",
|
|
324
346
|
" /new Start a fresh session on the next message.",
|
|
@@ -344,29 +366,236 @@ function printHelp() {
|
|
|
344
366
|
);
|
|
345
367
|
}
|
|
346
368
|
|
|
347
|
-
function
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
369
|
+
function logicalLinesWithOffsets(buffer = "") {
|
|
370
|
+
const lines = String(buffer).split("\n");
|
|
371
|
+
let offset = 0;
|
|
372
|
+
return lines.map((line, index) => {
|
|
373
|
+
const start = offset;
|
|
374
|
+
const end = start + line.length;
|
|
375
|
+
offset = end + 1;
|
|
376
|
+
return {
|
|
377
|
+
text: line,
|
|
378
|
+
start,
|
|
379
|
+
end,
|
|
380
|
+
hasNewline: index < lines.length - 1,
|
|
381
|
+
};
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function promptVisibleWindow(rows, cursorRow, height = terminalHeight()) {
|
|
386
|
+
const maxRows = promptViewportRows(height);
|
|
387
|
+
if (rows.length <= maxRows) {
|
|
388
|
+
return { start: 0, end: rows.length, topHidden: 0, bottomHidden: 0 };
|
|
351
389
|
}
|
|
390
|
+
const half = Math.floor(maxRows / 2);
|
|
391
|
+
const start = clamp(cursorRow - half, 0, rows.length - maxRows);
|
|
392
|
+
const end = start + maxRows;
|
|
393
|
+
return {
|
|
394
|
+
start,
|
|
395
|
+
end,
|
|
396
|
+
topHidden: start,
|
|
397
|
+
bottomHidden: rows.length - end,
|
|
398
|
+
};
|
|
399
|
+
}
|
|
352
400
|
|
|
353
|
-
|
|
354
|
-
const
|
|
355
|
-
const
|
|
401
|
+
export function buildPromptLayout(buffer = "", cursor = 0, width = terminalWidth(), height = terminalHeight()) {
|
|
402
|
+
const safeBuffer = String(buffer || "");
|
|
403
|
+
const safeCursor = clamp(Number(cursor) || 0, 0, safeBuffer.length);
|
|
404
|
+
const lineWidth = editorWidth(width);
|
|
356
405
|
const firstPrefix = " user ";
|
|
357
406
|
const nextPrefix = " ... ";
|
|
358
|
-
const
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
407
|
+
const firstInnerWidth = Math.max(lineWidth - firstPrefix.length, 8);
|
|
408
|
+
const nextInnerWidth = Math.max(lineWidth - nextPrefix.length, 8);
|
|
409
|
+
const rows = [];
|
|
410
|
+
|
|
411
|
+
for (const [lineIndex, line] of logicalLinesWithOffsets(safeBuffer).entries()) {
|
|
412
|
+
let localOffset = 0;
|
|
413
|
+
const text = line.text;
|
|
414
|
+
if (!text) {
|
|
415
|
+
rows.push({
|
|
416
|
+
prefix: lineIndex === 0 ? firstPrefix : nextPrefix,
|
|
417
|
+
text: "",
|
|
418
|
+
start: line.start,
|
|
419
|
+
end: line.start,
|
|
420
|
+
innerWidth: lineIndex === 0 ? firstInnerWidth : nextInnerWidth,
|
|
421
|
+
lineStart: line.start,
|
|
422
|
+
lineEnd: line.end,
|
|
423
|
+
hasNewline: line.hasNewline,
|
|
424
|
+
});
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
while (localOffset < text.length) {
|
|
429
|
+
const prefix = lineIndex === 0 && localOffset === 0 ? firstPrefix : nextPrefix;
|
|
430
|
+
const innerWidth = prefix === firstPrefix ? firstInnerWidth : nextInnerWidth;
|
|
431
|
+
const chunk = text.slice(localOffset, localOffset + innerWidth);
|
|
432
|
+
rows.push({
|
|
433
|
+
prefix,
|
|
434
|
+
text: chunk,
|
|
435
|
+
start: line.start + localOffset,
|
|
436
|
+
end: line.start + localOffset + chunk.length,
|
|
437
|
+
innerWidth,
|
|
438
|
+
lineStart: line.start,
|
|
439
|
+
lineEnd: line.end,
|
|
440
|
+
hasNewline: line.hasNewline,
|
|
441
|
+
});
|
|
442
|
+
localOffset += chunk.length;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (rows.length === 0) {
|
|
447
|
+
rows.push({
|
|
448
|
+
prefix: firstPrefix,
|
|
449
|
+
text: "",
|
|
450
|
+
start: 0,
|
|
451
|
+
end: 0,
|
|
452
|
+
innerWidth: firstInnerWidth,
|
|
453
|
+
lineStart: 0,
|
|
454
|
+
lineEnd: 0,
|
|
455
|
+
hasNewline: false,
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const last = rows[rows.length - 1];
|
|
460
|
+
if (safeCursor === safeBuffer.length && last.end === safeCursor && last.text.length >= last.innerWidth) {
|
|
461
|
+
rows.push({
|
|
462
|
+
prefix: nextPrefix,
|
|
463
|
+
text: "",
|
|
464
|
+
start: safeCursor,
|
|
465
|
+
end: safeCursor,
|
|
466
|
+
innerWidth: nextInnerWidth,
|
|
467
|
+
lineStart: safeCursor,
|
|
468
|
+
lineEnd: safeCursor,
|
|
469
|
+
hasNewline: false,
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
let cursorRow = rows.length - 1;
|
|
474
|
+
let cursorColumn = rows[cursorRow].prefix.length;
|
|
475
|
+
for (let index = 0; index < rows.length; index += 1) {
|
|
476
|
+
const row = rows[index];
|
|
477
|
+
const next = rows[index + 1];
|
|
478
|
+
if (safeCursor < row.end) {
|
|
479
|
+
cursorRow = index;
|
|
480
|
+
cursorColumn = row.prefix.length + safeCursor - row.start;
|
|
481
|
+
break;
|
|
482
|
+
}
|
|
483
|
+
if (safeCursor === row.end) {
|
|
484
|
+
if (next && next.start === safeCursor && row.text.length >= row.innerWidth) continue;
|
|
485
|
+
cursorRow = index;
|
|
486
|
+
cursorColumn = row.prefix.length + safeCursor - row.start;
|
|
487
|
+
break;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const suggestions = commandSuggestions(safeBuffer.split("\n")[0] || "");
|
|
492
|
+
const emptyHint = "type a request, /help, Enter to send, Ctrl+J for newline";
|
|
493
|
+
const visible = promptVisibleWindow(rows, cursorRow, height);
|
|
494
|
+
const renderedRows = [];
|
|
495
|
+
let renderedCursorRow = cursorRow - visible.start;
|
|
496
|
+
|
|
497
|
+
if (visible.topHidden > 0) {
|
|
498
|
+
renderedRows.push(panelLine(` ... ${visible.topHidden} earlier input row${visible.topHidden === 1 ? "" : "s"}`, ansi.systemBg, lineWidth));
|
|
499
|
+
renderedCursorRow += 1;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
for (const row of rows.slice(visible.start, visible.end)) {
|
|
503
|
+
const content = safeBuffer ? `${row.prefix}${row.text}` : `${row.prefix}${emptyHint}`;
|
|
504
|
+
renderedRows.push(panelLine(content, ansi.userBg, lineWidth));
|
|
364
505
|
}
|
|
506
|
+
|
|
507
|
+
if (visible.bottomHidden > 0) {
|
|
508
|
+
renderedRows.push(panelLine(` ... ${visible.bottomHidden} later input row${visible.bottomHidden === 1 ? "" : "s"}`, ansi.systemBg, lineWidth));
|
|
509
|
+
}
|
|
510
|
+
|
|
365
511
|
if (suggestions.length > 0) {
|
|
366
|
-
|
|
512
|
+
renderedRows.push(panelLine(` hint ${suggestions.join(" ")}`, ansi.systemBg, lineWidth));
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
return {
|
|
516
|
+
rows,
|
|
517
|
+
renderedRows,
|
|
518
|
+
cursorRow: renderedCursorRow,
|
|
519
|
+
absoluteCursorRow: cursorRow,
|
|
520
|
+
cursorColumn: clamp(cursorColumn, 0, editorWidth(width) - 1),
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function cursorLocation(layout, cursor) {
|
|
525
|
+
for (let index = 0; index < layout.rows.length; index += 1) {
|
|
526
|
+
const row = layout.rows[index];
|
|
527
|
+
const next = layout.rows[index + 1];
|
|
528
|
+
if (cursor < row.end) return { rowIndex: index, column: cursor - row.start };
|
|
529
|
+
if (cursor === row.end) {
|
|
530
|
+
if (next && next.start === cursor && row.text.length >= row.innerWidth) continue;
|
|
531
|
+
return { rowIndex: index, column: cursor - row.start };
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
const rowIndex = Math.max(layout.rows.length - 1, 0);
|
|
535
|
+
const row = layout.rows[rowIndex];
|
|
536
|
+
return { rowIndex, column: Math.max(row.end - row.start, 0) };
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function clearRenderedPrompt(previous) {
|
|
540
|
+
if (!previous.lineCount) return;
|
|
541
|
+
const below = previous.lineCount - 1 - previous.cursorRow;
|
|
542
|
+
if (below > 0) output.write(`\x1b[${below}B`);
|
|
543
|
+
output.write(`\r${ansi.clearLine}`);
|
|
544
|
+
for (let index = 1; index < previous.lineCount; index += 1) {
|
|
545
|
+
output.write(`\x1b[1A\r${ansi.clearLine}`);
|
|
367
546
|
}
|
|
368
|
-
|
|
369
|
-
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function renderPromptBuffer(buffer, cursor, previous = { lineCount: 0, cursorRow: 0 }) {
|
|
550
|
+
output.write(ansi.cursorHide);
|
|
551
|
+
clearRenderedPrompt(previous);
|
|
552
|
+
const layout = buildPromptLayout(buffer, cursor);
|
|
553
|
+
output.write(layout.renderedRows.join("\n"));
|
|
554
|
+
const below = layout.renderedRows.length - 1 - layout.cursorRow;
|
|
555
|
+
if (below > 0) output.write(`\x1b[${below}A`);
|
|
556
|
+
output.write(`\r\x1b[${layout.cursorColumn + 1}G`);
|
|
557
|
+
output.write(ansi.cursorShow);
|
|
558
|
+
return {
|
|
559
|
+
lineCount: layout.renderedRows.length,
|
|
560
|
+
cursorRow: layout.cursorRow,
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function moveToPromptBottom(rendered) {
|
|
565
|
+
const below = Math.max((rendered?.lineCount || 1) - 1 - (rendered?.cursorRow || 0), 0);
|
|
566
|
+
if (below > 0) output.write(`\x1b[${below}B`);
|
|
567
|
+
output.write("\r");
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function lineBounds(buffer, cursor) {
|
|
571
|
+
const safeCursor = clamp(cursor, 0, buffer.length);
|
|
572
|
+
const start = buffer.lastIndexOf("\n", safeCursor - 1) + 1;
|
|
573
|
+
const nextNewline = buffer.indexOf("\n", safeCursor);
|
|
574
|
+
const end = nextNewline === -1 ? buffer.length : nextNewline;
|
|
575
|
+
return { start, end };
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
function insertAt(buffer, cursor, text) {
|
|
579
|
+
return {
|
|
580
|
+
buffer: `${buffer.slice(0, cursor)}${text}${buffer.slice(cursor)}`,
|
|
581
|
+
cursor: cursor + text.length,
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function removeBefore(buffer, cursor) {
|
|
586
|
+
if (cursor <= 0) return { buffer, cursor };
|
|
587
|
+
return {
|
|
588
|
+
buffer: `${buffer.slice(0, cursor - 1)}${buffer.slice(cursor)}`,
|
|
589
|
+
cursor: cursor - 1,
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function removeAt(buffer, cursor) {
|
|
594
|
+
if (cursor >= buffer.length) return { buffer, cursor };
|
|
595
|
+
return {
|
|
596
|
+
buffer: `${buffer.slice(0, cursor)}${buffer.slice(cursor + 1)}`,
|
|
597
|
+
cursor,
|
|
598
|
+
};
|
|
370
599
|
}
|
|
371
600
|
|
|
372
601
|
function createAbortError(message = "Aborted with Ctrl+C") {
|
|
@@ -381,34 +610,97 @@ function readTtyPrompt() {
|
|
|
381
610
|
emitKeypressEvents(input);
|
|
382
611
|
const wasRaw = Boolean(input.isRaw);
|
|
383
612
|
let buffer = "";
|
|
384
|
-
let
|
|
613
|
+
let cursor = 0;
|
|
614
|
+
let rendered = { lineCount: 0, cursorRow: 0 };
|
|
615
|
+
let preferredColumn = null;
|
|
616
|
+
let historyIndex = promptHistory.length;
|
|
617
|
+
let draft = "";
|
|
618
|
+
let redrawHandle = null;
|
|
385
619
|
|
|
386
620
|
const cleanup = () => {
|
|
621
|
+
if (redrawHandle) {
|
|
622
|
+
clearImmediate(redrawHandle);
|
|
623
|
+
redrawHandle = null;
|
|
624
|
+
}
|
|
387
625
|
input.off("keypress", handler);
|
|
388
626
|
if (typeof input.setRawMode === "function") input.setRawMode(wasRaw);
|
|
389
627
|
input.pause();
|
|
390
628
|
output.write(ansi.cursorShow);
|
|
391
629
|
};
|
|
392
630
|
|
|
631
|
+
const renderNow = () => {
|
|
632
|
+
if (redrawHandle) {
|
|
633
|
+
clearImmediate(redrawHandle);
|
|
634
|
+
redrawHandle = null;
|
|
635
|
+
}
|
|
636
|
+
rendered = renderPromptBuffer(buffer, cursor, rendered);
|
|
637
|
+
};
|
|
638
|
+
|
|
393
639
|
const redraw = () => {
|
|
394
|
-
|
|
640
|
+
if (redrawHandle) return;
|
|
641
|
+
redrawHandle = setImmediate(() => {
|
|
642
|
+
redrawHandle = null;
|
|
643
|
+
rendered = renderPromptBuffer(buffer, cursor, rendered);
|
|
644
|
+
});
|
|
395
645
|
};
|
|
396
646
|
|
|
397
647
|
const submit = () => {
|
|
648
|
+
renderNow();
|
|
649
|
+
moveToPromptBottom(rendered);
|
|
398
650
|
cleanup();
|
|
399
651
|
output.write("\n");
|
|
652
|
+
const saved = buffer.trim();
|
|
653
|
+
if (saved && promptHistory[promptHistory.length - 1] !== buffer) promptHistory.push(buffer);
|
|
400
654
|
resolve(buffer);
|
|
401
655
|
};
|
|
402
656
|
|
|
657
|
+
const setBuffer = (nextBuffer, nextCursor = nextBuffer.length) => {
|
|
658
|
+
buffer = nextBuffer;
|
|
659
|
+
cursor = clamp(nextCursor, 0, buffer.length);
|
|
660
|
+
preferredColumn = null;
|
|
661
|
+
redraw();
|
|
662
|
+
};
|
|
663
|
+
|
|
664
|
+
const moveVertical = (direction) => {
|
|
665
|
+
const layout = buildPromptLayout(buffer, cursor);
|
|
666
|
+
const location = cursorLocation(layout, cursor);
|
|
667
|
+
const targetRowIndex = location.rowIndex + direction;
|
|
668
|
+
if (targetRowIndex < 0) {
|
|
669
|
+
if (promptHistory.length === 0) return;
|
|
670
|
+
if (historyIndex === promptHistory.length) draft = buffer;
|
|
671
|
+
historyIndex = Math.max(historyIndex - 1, 0);
|
|
672
|
+
setBuffer(promptHistory[historyIndex], promptHistory[historyIndex].length);
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
if (targetRowIndex >= layout.rows.length) {
|
|
676
|
+
if (historyIndex < promptHistory.length - 1) {
|
|
677
|
+
historyIndex += 1;
|
|
678
|
+
setBuffer(promptHistory[historyIndex], promptHistory[historyIndex].length);
|
|
679
|
+
} else if (historyIndex < promptHistory.length) {
|
|
680
|
+
historyIndex = promptHistory.length;
|
|
681
|
+
setBuffer(draft, draft.length);
|
|
682
|
+
}
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
const currentColumn = preferredColumn ?? location.column;
|
|
686
|
+
const targetRow = layout.rows[targetRowIndex];
|
|
687
|
+
cursor = targetRow.start + Math.min(currentColumn, targetRow.end - targetRow.start);
|
|
688
|
+
preferredColumn = currentColumn;
|
|
689
|
+
redraw();
|
|
690
|
+
};
|
|
691
|
+
|
|
403
692
|
const handler = (str = "", key = {}) => {
|
|
404
693
|
if (key.ctrl && key.name === "c") {
|
|
694
|
+
renderNow();
|
|
695
|
+
moveToPromptBottom(rendered);
|
|
405
696
|
cleanup();
|
|
406
697
|
output.write("\n");
|
|
407
698
|
reject(createAbortError());
|
|
408
699
|
return;
|
|
409
700
|
}
|
|
410
701
|
if ((key.ctrl && key.name === "j") || key.sequence === "\n") {
|
|
411
|
-
buffer
|
|
702
|
+
({ buffer, cursor } = insertAt(buffer, cursor, "\n"));
|
|
703
|
+
preferredColumn = null;
|
|
412
704
|
redraw();
|
|
413
705
|
return;
|
|
414
706
|
}
|
|
@@ -417,7 +709,61 @@ function readTtyPrompt() {
|
|
|
417
709
|
return;
|
|
418
710
|
}
|
|
419
711
|
if (key.name === "backspace") {
|
|
420
|
-
buffer = buffer
|
|
712
|
+
({ buffer, cursor } = removeBefore(buffer, cursor));
|
|
713
|
+
preferredColumn = null;
|
|
714
|
+
redraw();
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
if (key.name === "delete") {
|
|
718
|
+
({ buffer, cursor } = removeAt(buffer, cursor));
|
|
719
|
+
preferredColumn = null;
|
|
720
|
+
redraw();
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
if (key.name === "left") {
|
|
724
|
+
cursor = Math.max(cursor - 1, 0);
|
|
725
|
+
preferredColumn = null;
|
|
726
|
+
redraw();
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
if (key.name === "right") {
|
|
730
|
+
cursor = Math.min(cursor + 1, buffer.length);
|
|
731
|
+
preferredColumn = null;
|
|
732
|
+
redraw();
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
if (key.name === "up") {
|
|
736
|
+
moveVertical(-1);
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
if (key.name === "down") {
|
|
740
|
+
moveVertical(1);
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
if ((key.ctrl && key.name === "a") || key.name === "home") {
|
|
744
|
+
cursor = lineBounds(buffer, cursor).start;
|
|
745
|
+
preferredColumn = null;
|
|
746
|
+
redraw();
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
if ((key.ctrl && key.name === "e") || key.name === "end") {
|
|
750
|
+
cursor = lineBounds(buffer, cursor).end;
|
|
751
|
+
preferredColumn = null;
|
|
752
|
+
redraw();
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
if (key.ctrl && key.name === "u") {
|
|
756
|
+
const bounds = lineBounds(buffer, cursor);
|
|
757
|
+
buffer = `${buffer.slice(0, bounds.start)}${buffer.slice(cursor)}`;
|
|
758
|
+
cursor = bounds.start;
|
|
759
|
+
preferredColumn = null;
|
|
760
|
+
redraw();
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
if (key.ctrl && key.name === "k") {
|
|
764
|
+
const bounds = lineBounds(buffer, cursor);
|
|
765
|
+
buffer = `${buffer.slice(0, cursor)}${buffer.slice(bounds.end)}`;
|
|
766
|
+
preferredColumn = null;
|
|
421
767
|
redraw();
|
|
422
768
|
return;
|
|
423
769
|
}
|
|
@@ -425,27 +771,32 @@ function readTtyPrompt() {
|
|
|
425
771
|
const suggestions = commandSuggestions(buffer.split("\n")[0] || "");
|
|
426
772
|
if (suggestions.length === 1) {
|
|
427
773
|
buffer = suggestions[0];
|
|
774
|
+
cursor = buffer.length;
|
|
428
775
|
}
|
|
776
|
+
preferredColumn = null;
|
|
429
777
|
redraw();
|
|
430
778
|
return;
|
|
431
779
|
}
|
|
432
780
|
if (key.name === "escape") {
|
|
433
781
|
buffer = "";
|
|
782
|
+
cursor = 0;
|
|
783
|
+
preferredColumn = null;
|
|
434
784
|
redraw();
|
|
435
785
|
return;
|
|
436
786
|
}
|
|
437
787
|
if (key.ctrl || key.meta) return;
|
|
438
788
|
if (str && !key.sequence?.startsWith("\x1b")) {
|
|
439
|
-
|
|
789
|
+
const text = str.replace(/\r/g, "");
|
|
790
|
+
({ buffer, cursor } = insertAt(buffer, cursor, text));
|
|
791
|
+
preferredColumn = null;
|
|
440
792
|
redraw();
|
|
441
793
|
}
|
|
442
794
|
};
|
|
443
795
|
|
|
444
796
|
input.resume();
|
|
445
797
|
input.setRawMode(true);
|
|
446
|
-
output.write(ansi.cursorHide);
|
|
447
798
|
input.on("keypress", handler);
|
|
448
|
-
|
|
799
|
+
renderNow();
|
|
449
800
|
});
|
|
450
801
|
}
|
|
451
802
|
|
|
@@ -638,6 +989,28 @@ async function handleCommand(line, state, packageDir) {
|
|
|
638
989
|
await promptAndSaveProviderKey(value || "deepseek", state);
|
|
639
990
|
return true;
|
|
640
991
|
}
|
|
992
|
+
if (command === "instructions" || command === "memory") {
|
|
993
|
+
if (value === "init") {
|
|
994
|
+
const result = await initProject(process.cwd());
|
|
995
|
+
printAgentMessage(`AGINTI.md ready at ${result.instructionsPath}`);
|
|
996
|
+
return true;
|
|
997
|
+
}
|
|
998
|
+
const instructions = await readProjectInstructions(process.cwd(), { maxBytes: 4000 });
|
|
999
|
+
if (!instructions.exists) {
|
|
1000
|
+
printAgentMessage("No AGINTI.md found. Run `/init` or `/instructions init` to create editable project instructions.");
|
|
1001
|
+
return true;
|
|
1002
|
+
}
|
|
1003
|
+
printAgentMessage(
|
|
1004
|
+
[
|
|
1005
|
+
`AGINTI.md: ${instructions.path}${instructions.truncated ? " (preview truncated)" : ""}`,
|
|
1006
|
+
"",
|
|
1007
|
+
instructions.content.trim() || "(empty)",
|
|
1008
|
+
"",
|
|
1009
|
+
"To edit it, ask normally: update AGINTI.md to remember that tests use pytest.",
|
|
1010
|
+
].join("\n")
|
|
1011
|
+
);
|
|
1012
|
+
return true;
|
|
1013
|
+
}
|
|
641
1014
|
if (command === "auxilliary" || command === "auxiliary") {
|
|
642
1015
|
const action = value || "status";
|
|
643
1016
|
if (action === "status") {
|
|
@@ -787,7 +1160,7 @@ async function handleCommand(line, state, packageDir) {
|
|
|
787
1160
|
}
|
|
788
1161
|
if (command === "init") {
|
|
789
1162
|
const result = await initProject(process.cwd());
|
|
790
|
-
printAgentMessage(`initialized project=${result.projectRoot}`);
|
|
1163
|
+
printAgentMessage(`initialized project=${result.projectRoot}\nAGINTI.md=${result.instructionsPath}`);
|
|
791
1164
|
return true;
|
|
792
1165
|
}
|
|
793
1166
|
if (command === "web") {
|
package/src/model-client.js
CHANGED
|
@@ -67,6 +67,7 @@ function mockCommandForGoal(goal = "") {
|
|
|
67
67
|
|
|
68
68
|
function mockPathForGoal(goal = "") {
|
|
69
69
|
const text = String(goal);
|
|
70
|
+
if (/\bAGINTI\.md\b|project instructions|remember (?:that|this)|durable preference/i.test(text)) return "AGINTI.md";
|
|
70
71
|
const explicit = text.match(/(?:file|path):\s*`?([A-Za-z0-9_./-]+)`?/i)?.[1];
|
|
71
72
|
if (explicit) return explicit;
|
|
72
73
|
const createPath = text.match(
|
|
@@ -82,6 +83,13 @@ function mockPathForGoal(goal = "") {
|
|
|
82
83
|
function mockWorkspaceToolForGoal(goal = "") {
|
|
83
84
|
const text = String(goal).toLowerCase();
|
|
84
85
|
const targetPath = mockPathForGoal(goal);
|
|
86
|
+
if (targetPath === "AGINTI.md" && /update|remember|instruction|preference|aginti\.md/.test(text)) {
|
|
87
|
+
return mockToolCall("write_file", {
|
|
88
|
+
path: targetPath,
|
|
89
|
+
mode: "overwrite",
|
|
90
|
+
content: `# AGINTI.md\n\nProject instructions for AgInTiFlow agents.\n\n## Notes\n\n- ${String(goal).slice(0, 180)}\n`,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
85
93
|
if (/inspect|map|overview|architecture|large codebase|large repo|repository|repo\b|codebase/.test(text)) {
|
|
86
94
|
return mockToolCall("inspect_project", {
|
|
87
95
|
path: ".",
|
|
@@ -185,6 +193,7 @@ function mockChatResponse(content, toolCalls = []) {
|
|
|
185
193
|
export async function createPlan(client, config, state) {
|
|
186
194
|
const taskProfile = getTaskProfile(config.taskProfile);
|
|
187
195
|
const engineeringGuidance = engineeringGuidanceForTask(state.goal, config.taskProfile);
|
|
196
|
+
const projectInstructions = state.meta?.projectInstructions;
|
|
188
197
|
if (client.mock) {
|
|
189
198
|
return [
|
|
190
199
|
"1. Inspect the request and prefer the local shell when available.",
|
|
@@ -213,8 +222,11 @@ export async function createPlan(client, config, state) {
|
|
|
213
222
|
? `Shell tool is enabled in ${config.commandCwd}. In Docker, this path is mounted as /workspace with persistent /aginti-env and /aginti-cache mounts. Use relative paths or /workspace paths, not absolute host temp paths. Sandbox mode: ${config.sandboxMode}. Package install policy: ${config.packageInstallPolicy}. For npm/pip/conda/venv setup, explain the need and wait for approval unless policy is allow.`
|
|
214
223
|
: "",
|
|
215
224
|
config.allowFileTools
|
|
216
|
-
? `Workspace file tools are enabled in ${config.commandCwd}: inspect_project, list_files, read_file, search_files, write_file, apply_patch, open_workspace_file, preview_workspace. For large or unfamiliar repos, plan to call inspect_project first, then search/read exact files. apply_patch supports exact single-file replacements and Codex-style/unified multi-file patches; prefer it for edits after reading relevant context. Keep all paths workspace-relative, for example plot_fx.svg or docs/report.tex, and avoid secrets. For generated local HTML/SVG/PDF/static sites, plan to use open_workspace_file or preview_workspace rather than starting a localhost server inside Docker.`
|
|
225
|
+
? `Workspace file tools are enabled in ${config.commandCwd}: inspect_project, list_files, read_file, search_files, write_file, apply_patch, open_workspace_file, preview_workspace. For large or unfamiliar repos, plan to call inspect_project first, then search/read AGINTI.md/AGENTS.md/README/manifests and exact files. apply_patch supports exact single-file replacements and Codex-style/unified multi-file patches; prefer it for edits after reading relevant context. Keep all paths workspace-relative, for example plot_fx.svg or docs/report.tex, and avoid secrets. For generated local HTML/SVG/PDF/static sites, plan to use open_workspace_file or preview_workspace rather than starting a localhost server inside Docker.`
|
|
217
226
|
: "",
|
|
227
|
+
projectInstructions?.exists
|
|
228
|
+
? `Project instructions: AGINTI.md is loaded from ${projectInstructions.path}${projectInstructions.truncated ? " (truncated)" : ""}. Follow it and update it with file tools when the user asks to remember or change project instructions.`
|
|
229
|
+
: "Project instructions: AGINTI.md is not present unless created by /init or file tools.",
|
|
218
230
|
config.allowWrapperTools
|
|
219
231
|
? `Agent wrappers are enabled. Use the selected wrapper only: ${normalizeWrapperName(config.preferredWrapper)}. Status: ${wrapperStatusText()}.`
|
|
220
232
|
: "",
|
package/src/parallel-scouts.js
CHANGED
|
@@ -67,6 +67,9 @@ function scoutMessages(config, state, scout) {
|
|
|
67
67
|
`Goal: ${config.goal || state.goal || ""}`,
|
|
68
68
|
`Task profile: ${profile.label}. ${profile.prompt}`,
|
|
69
69
|
guidance,
|
|
70
|
+
state.meta?.projectInstructions?.exists
|
|
71
|
+
? `Project instructions: AGINTI.md loaded from ${state.meta.projectInstructions.path}${state.meta.projectInstructions.truncated ? " (truncated)" : ""}.`
|
|
72
|
+
: "Project instructions: AGINTI.md not present.",
|
|
70
73
|
`Workspace: ${config.commandCwd}`,
|
|
71
74
|
`Sandbox: ${config.sandboxMode}; package policy: ${config.packageInstallPolicy}`,
|
|
72
75
|
`Current plan:\n${state.plan || "(none yet)"}`,
|
package/src/project.js
CHANGED
|
@@ -27,6 +27,7 @@ export function projectPaths(projectRoot = process.cwd()) {
|
|
|
27
27
|
const root = resolveProjectRoot(projectRoot);
|
|
28
28
|
return {
|
|
29
29
|
root,
|
|
30
|
+
agintiInstructionsPath: path.join(root, "AGINTI.md"),
|
|
30
31
|
controlDir: path.join(root, ".aginti"),
|
|
31
32
|
envPath: path.join(root, ".aginti", ".env"),
|
|
32
33
|
rootEnvPath: path.join(root, ".env"),
|
|
@@ -40,6 +41,60 @@ export function projectPaths(projectRoot = process.cwd()) {
|
|
|
40
41
|
};
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
export function defaultAgintiInstructions() {
|
|
45
|
+
return [
|
|
46
|
+
"# AGINTI.md",
|
|
47
|
+
"",
|
|
48
|
+
"Project instructions for AgInTiFlow agents.",
|
|
49
|
+
"",
|
|
50
|
+
"Edit this file directly or ask AgInTiFlow to update it during chat. Keep durable project preferences here; keep secrets in `.aginti/.env` instead.",
|
|
51
|
+
"",
|
|
52
|
+
"## Project Goals",
|
|
53
|
+
"",
|
|
54
|
+
"- Describe what this project is for.",
|
|
55
|
+
"- Note the main user-facing workflows the agent should preserve.",
|
|
56
|
+
"",
|
|
57
|
+
"## Agent Preferences",
|
|
58
|
+
"",
|
|
59
|
+
"- Prefer small, inspectable changes over broad rewrites.",
|
|
60
|
+
"- Read relevant files before editing.",
|
|
61
|
+
"- Run focused checks when tools and dependencies are available.",
|
|
62
|
+
"- Keep generated artifacts in project folders with clear names.",
|
|
63
|
+
"",
|
|
64
|
+
"## Useful Commands",
|
|
65
|
+
"",
|
|
66
|
+
"- Add build, test, lint, preview, or compile commands here.",
|
|
67
|
+
"",
|
|
68
|
+
"## Notes",
|
|
69
|
+
"",
|
|
70
|
+
"- Add project-specific terminology, style preferences, and known constraints here.",
|
|
71
|
+
"",
|
|
72
|
+
].join("\n");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function readProjectInstructions(projectRoot = process.cwd(), { maxBytes = 24_000 } = {}) {
|
|
76
|
+
const paths = projectPaths(projectRoot);
|
|
77
|
+
try {
|
|
78
|
+
const stat = await fsp.stat(paths.agintiInstructionsPath);
|
|
79
|
+
if (!stat.isFile()) return { exists: false, path: paths.agintiInstructionsPath, content: "", truncated: false };
|
|
80
|
+
const handle = await fsp.open(paths.agintiInstructionsPath, "r");
|
|
81
|
+
try {
|
|
82
|
+
const buffer = Buffer.alloc(Math.min(stat.size, maxBytes));
|
|
83
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
|
|
84
|
+
return {
|
|
85
|
+
exists: true,
|
|
86
|
+
path: paths.agintiInstructionsPath,
|
|
87
|
+
content: buffer.subarray(0, bytesRead).toString("utf8"),
|
|
88
|
+
truncated: stat.size > maxBytes,
|
|
89
|
+
};
|
|
90
|
+
} finally {
|
|
91
|
+
await handle.close();
|
|
92
|
+
}
|
|
93
|
+
} catch {
|
|
94
|
+
return { exists: false, path: paths.agintiInstructionsPath, content: "", truncated: false };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
43
98
|
export function parseEnvText(text = "") {
|
|
44
99
|
const values = {};
|
|
45
100
|
for (const rawLine of String(text).split(/\r?\n/)) {
|
|
@@ -126,6 +181,10 @@ export async function initProject(projectRoot = process.cwd()) {
|
|
|
126
181
|
await ensureDir(paths.controlDir);
|
|
127
182
|
await ensureDir(paths.notesDir);
|
|
128
183
|
await ensureDir(paths.sessionsDir);
|
|
184
|
+
await ensureFile(
|
|
185
|
+
paths.agintiInstructionsPath,
|
|
186
|
+
defaultAgintiInstructions()
|
|
187
|
+
);
|
|
129
188
|
await ensureFile(
|
|
130
189
|
paths.controlReadmePath,
|
|
131
190
|
[
|
|
@@ -133,6 +192,7 @@ export async function initProject(projectRoot = process.cwd()) {
|
|
|
133
192
|
"",
|
|
134
193
|
"This folder stores project-local AgInTiFlow configuration.",
|
|
135
194
|
"",
|
|
195
|
+
"- `../AGINTI.md` stores editable project instructions for CLI and web agents.",
|
|
136
196
|
"- `.env` is ignored and can hold local provider keys.",
|
|
137
197
|
"- `.env.example` documents accepted variable names.",
|
|
138
198
|
"- `.sessions/` at the project root stores CLI and web run history.",
|
|
@@ -175,6 +235,7 @@ export async function initProject(projectRoot = process.cwd()) {
|
|
|
175
235
|
return {
|
|
176
236
|
ok: true,
|
|
177
237
|
projectRoot: paths.root,
|
|
238
|
+
instructionsPath: paths.agintiInstructionsPath,
|
|
178
239
|
controlDir: paths.controlDir,
|
|
179
240
|
sessionsDir: paths.sessionsDir,
|
|
180
241
|
created,
|
|
@@ -318,10 +379,11 @@ export async function npmLatestVersion(packageName = "@lazyingart/agintiflow") {
|
|
|
318
379
|
export async function doctorReport(projectRoot, packageVersion, config) {
|
|
319
380
|
const paths = projectPaths(projectRoot);
|
|
320
381
|
const keyStatus = providerKeyStatus(projectRoot);
|
|
321
|
-
const [sessions, dockerStatus, latestVersion] = await Promise.all([
|
|
382
|
+
const [sessions, dockerStatus, latestVersion, instructions] = await Promise.all([
|
|
322
383
|
listProjectSessions(projectRoot, 8),
|
|
323
384
|
getDockerSandboxStatus(config).catch((error) => ({ ok: false, error: error.message })),
|
|
324
385
|
npmLatestVersion(),
|
|
386
|
+
readProjectInstructions(projectRoot, { maxBytes: 1 }),
|
|
325
387
|
]);
|
|
326
388
|
|
|
327
389
|
return {
|
|
@@ -337,6 +399,8 @@ export async function doctorReport(projectRoot, packageVersion, config) {
|
|
|
337
399
|
},
|
|
338
400
|
project: {
|
|
339
401
|
root: paths.root,
|
|
402
|
+
instructionsPath: paths.agintiInstructionsPath,
|
|
403
|
+
instructionsPresent: instructions.exists,
|
|
340
404
|
controlDir: paths.controlDir,
|
|
341
405
|
sessionsDir: paths.sessionsDir,
|
|
342
406
|
sessionDbPath: paths.sessionDbPath,
|
package/src/task-profiles.js
CHANGED
|
@@ -3,63 +3,63 @@ export const TASK_PROFILES = {
|
|
|
3
3
|
id: "auto",
|
|
4
4
|
label: "Auto",
|
|
5
5
|
prompt:
|
|
6
|
-
"Infer the task type from the
|
|
6
|
+
"Act as the general-purpose AgInTiFlow agent. Infer the task type from the request, then choose the right mix of browser, shell, files, web search, canvas, and sandbox tools. For short tasks, use the smallest safe tool sequence that completes the work. For codebase, writing, website, LaTeX, system, debugging, migration, or multi-language tasks, automatically borrow the relevant specialized profile habits without becoming narrowly constrained.",
|
|
7
7
|
tools: ["browser", "shell", "files", "canvas", "inspect_project"],
|
|
8
8
|
},
|
|
9
9
|
code: {
|
|
10
10
|
id: "code",
|
|
11
11
|
label: "Code writing",
|
|
12
12
|
prompt:
|
|
13
|
-
"
|
|
13
|
+
"Bias toward coding-agent behavior across languages, but remain a general assistant when the task needs docs, shell, web, or design work. Inspect project instructions/manifests/conventions, edit workspace files with patches, run useful focused checks, iterate on failures, and report changed files plus residual risks.",
|
|
14
14
|
tools: ["inspect_project", "files", "shell", "sandbox"],
|
|
15
15
|
},
|
|
16
16
|
"large-codebase": {
|
|
17
17
|
id: "large-codebase",
|
|
18
18
|
label: "Large codebase engineering",
|
|
19
19
|
prompt:
|
|
20
|
-
"
|
|
20
|
+
"Bias toward senior large-repo engineering while still answering ordinary side questions. Inspect_project first unless context is already known, read AGINTI/AGENTS/README/manifests, locate entry points and tests, make a small explicit change plan, patch in coherent batches, run the narrowest relevant checks first, escalate to broader checks when stable, and summarize files changed, checks, tradeoffs, and remaining risks.",
|
|
21
21
|
tools: ["inspect_project", "search_files", "read_file", "apply_patch", "shell", "sandbox", "canvas"],
|
|
22
22
|
},
|
|
23
23
|
writing: {
|
|
24
24
|
id: "writing",
|
|
25
25
|
label: "Book/script writing",
|
|
26
26
|
prompt:
|
|
27
|
-
"Create structured drafts with outlines, sections,
|
|
27
|
+
"Bias toward long-form writing quality without refusing adjacent research, code, or formatting tasks. Create structured drafts with outlines, sections, revision notes, and saved files for durable output. Use web search when current sources matter and canvas for important drafts.",
|
|
28
28
|
tools: ["files", "canvas"],
|
|
29
29
|
},
|
|
30
30
|
design: {
|
|
31
31
|
id: "design",
|
|
32
32
|
label: "Design docs",
|
|
33
33
|
prompt:
|
|
34
|
-
"Produce concise design documents with goals, constraints, options, tradeoffs, implementation steps, and
|
|
34
|
+
"Bias toward clear product/engineering design while remaining able to implement or test when asked. Produce concise design documents with goals, constraints, options, tradeoffs, implementation steps, verification criteria, and decision records.",
|
|
35
35
|
tools: ["files", "canvas"],
|
|
36
36
|
},
|
|
37
37
|
python: {
|
|
38
38
|
id: "python",
|
|
39
39
|
label: "Python",
|
|
40
40
|
prompt:
|
|
41
|
-
"
|
|
41
|
+
"Bias toward Python best practices without ignoring non-Python project context. Inspect pyproject/requirements, create scripts/packages/tests as files, prefer project-local venv/conda/uv or Docker for setup, and run focused smoke checks when shell is enabled.",
|
|
42
42
|
tools: ["files", "shell", "sandbox"],
|
|
43
43
|
},
|
|
44
44
|
shell: {
|
|
45
45
|
id: "shell",
|
|
46
46
|
label: "Shell",
|
|
47
47
|
prompt:
|
|
48
|
-
"
|
|
48
|
+
"Bias toward terminal/system diagnosis and scripting while still using files, web, or docs when useful. Gather evidence first, write reusable scripts when appropriate, follow the configured trust/sandbox policy, and keep command outputs concise.",
|
|
49
49
|
tools: ["shell", "sandbox"],
|
|
50
50
|
},
|
|
51
51
|
node: {
|
|
52
52
|
id: "node",
|
|
53
53
|
label: "Node",
|
|
54
54
|
prompt:
|
|
55
|
-
"
|
|
55
|
+
"Bias toward Node/JavaScript/TypeScript workflows without excluding frontend, backend, docs, or deployment work. Inspect package.json/lockfiles, respect the package manager, add tests when useful, and run safe npm/node checks when available.",
|
|
56
56
|
tools: ["files", "shell", "sandbox"],
|
|
57
57
|
},
|
|
58
58
|
website: {
|
|
59
59
|
id: "website",
|
|
60
60
|
label: "Website testing",
|
|
61
61
|
prompt:
|
|
62
|
-
"
|
|
62
|
+
"Bias toward real website/app delivery while staying general enough for copy, assets, backend, and tests. Create or inspect real site files, use vivid but tidy UI when designing from scratch, preview with workspace preview tools, add local checks when useful, and use the configured sandbox/package policy for dependencies.",
|
|
63
63
|
tools: ["files", "shell", "canvas", "sandbox"],
|
|
64
64
|
},
|
|
65
65
|
image: {
|
|
@@ -73,21 +73,21 @@ export const TASK_PROFILES = {
|
|
|
73
73
|
id: "aaps",
|
|
74
74
|
label: "AAPS",
|
|
75
75
|
prompt:
|
|
76
|
-
"
|
|
76
|
+
"Bias toward AAPS workflows while still handling normal project work. Recognize .aaps folders and @lazyingart/aaps conventions, keep work project-local, document assumptions, and avoid secrets or publishing unless explicitly requested.",
|
|
77
77
|
tools: ["files", "shell", "sandbox"],
|
|
78
78
|
},
|
|
79
79
|
latex: {
|
|
80
80
|
id: "latex",
|
|
81
81
|
label: "LaTeX",
|
|
82
82
|
prompt:
|
|
83
|
-
"
|
|
83
|
+
"Bias toward LaTeX/PDF production while still using writing, plotting, code, and web research when needed. Locate or create source and figures in a subfolder, compile when a TeX toolchain is available, run enough passes for references, and send the PDF through the canvas tunnel. In Docker, use /workspace for outputs and /aginti-env for persistent tools when setup is needed.",
|
|
84
84
|
tools: ["files", "shell", "canvas", "sandbox"],
|
|
85
85
|
},
|
|
86
86
|
maintenance: {
|
|
87
87
|
id: "maintenance",
|
|
88
88
|
label: "System maintenance",
|
|
89
89
|
prompt:
|
|
90
|
-
"
|
|
90
|
+
"Bias toward practical system repair while staying useful for code/docs around the fix. Diagnose first with read-only evidence, use Docker for broad installs/toolchains when available, generate reversible project-local scripts, follow the configured trust/package policy for host-level changes, and stop with clear next actions if stronger permission is needed.",
|
|
91
91
|
tools: ["shell", "sandbox", "files", "inspect_project"],
|
|
92
92
|
},
|
|
93
93
|
};
|
package/src/workspace-tools.js
CHANGED
|
@@ -53,6 +53,7 @@ const IMPORTANT_MANIFESTS = new Set([
|
|
|
53
53
|
"compose.yml",
|
|
54
54
|
"README.md",
|
|
55
55
|
"AGENTS.md",
|
|
56
|
+
"AGINTI.md",
|
|
56
57
|
]);
|
|
57
58
|
const SOURCE_DIR_NAMES = new Set(["src", "app", "lib", "packages", "apps", "bin", "scripts", "server", "client", "public", "docs"]);
|
|
58
59
|
const TEST_DIR_NAMES = new Set(["test", "tests", "__tests__", "spec", "specs", "e2e"]);
|
|
@@ -279,7 +280,7 @@ async function readJsonFileSafe(absolutePath) {
|
|
|
279
280
|
|
|
280
281
|
function recommendedReads(summary) {
|
|
281
282
|
const reads = [];
|
|
282
|
-
for (const name of ["AGENTS.md", "README.md", "package.json", "pyproject.toml", "Cargo.toml", "go.mod"]) {
|
|
283
|
+
for (const name of ["AGINTI.md", "AGENTS.md", "README.md", "package.json", "pyproject.toml", "Cargo.toml", "go.mod"]) {
|
|
283
284
|
const match = summary.manifestFiles.find((item) => item.path === name || item.path.endsWith(`/${name}`));
|
|
284
285
|
if (match) reads.push(match.path);
|
|
285
286
|
}
|
|
@@ -533,7 +534,7 @@ async function inspectProject(config, args) {
|
|
|
533
534
|
packageScripts,
|
|
534
535
|
recommendedReads: [],
|
|
535
536
|
engineeringHints: [
|
|
536
|
-
"Read AGENTS/README/manifests before editing.",
|
|
537
|
+
"Read AGINTI/AGENTS/README/manifests before editing.",
|
|
537
538
|
"Use search_files to locate symbols and tests, then read exact files.",
|
|
538
539
|
"Use apply_patch for source edits and run the smallest relevant check first.",
|
|
539
540
|
"If a change spans modules, patch in small batches and verify after each batch.",
|