@lazyingart/agintiflow 0.20.144 → 0.20.150
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 +1 -1
- package/docs/autonomous-artifact-pipelines.md +36 -0
- package/docs/self-healing-pipelines.md +23 -0
- package/docs/skills-and-tools.md +10 -1
- package/package.json +1 -1
- package/scripts/smoke-capabilities.js +9 -0
- package/scripts/smoke-coding-tools.js +27 -0
- package/scripts/smoke-skills.js +13 -0
- package/skills/autonomous-artifact-pipeline/SKILL.md +69 -0
- package/skills/bilingual-interlinear-book/SKILL.md +5 -4
- package/skills/self-healing-pipeline/SKILL.md +73 -0
- package/skills/source-ingestion/SKILL.md +63 -0
- package/skills/structured-json/SKILL.md +46 -0
- package/src/agent-runner.js +48 -0
- package/src/guardrails.js +60 -0
- package/src/json-specialist.js +422 -0
- package/src/model-client.js +87 -0
- package/src/step-budget-controller.js +2 -0
- package/src/task-profiles.js +18 -0
package/README.md
CHANGED
|
@@ -240,7 +240,7 @@ The website keeps the visual walkthrough in a carousel so this README can stay f
|
|
|
240
240
|
| SCS mode | Optional Student-Committee-Supervisor quality gate for complicated or risky tasks. |
|
|
241
241
|
| AAPS adapter | Optional `@lazyingart/aaps` integration for `.aaps` workflow init, validate, parse, compile, dry-run, and run commands. |
|
|
242
242
|
| Image generation | Optional GRS AI and Venice image tools with saved manifests and canvas artifact previews. |
|
|
243
|
-
| Skill library | Built-in Markdown skills for code, websites, Android/iOS, Python, Rust, Java, LaTeX, writing, reviews, GitHub, AAPS, and more. |
|
|
243
|
+
| Skill library | Built-in Markdown skills for code, websites, Android/iOS, Python, Rust, Java, LaTeX, writing, reviews, source ingestion/OCR, structured JSON, autonomous artifact pipelines, GitHub, AAPS, and more. |
|
|
244
244
|
| Skill Mesh | Optional strict skill recording/sharing for reviewed reusable skill packs. If unused, AgInTiFlow runs normally without background sharing. |
|
|
245
245
|
| Multilingual UI | CLI and docs language support for English, Japanese, Simplified/Traditional Chinese, Korean, French, Spanish, Arabic, Vietnamese, German, and Russian. |
|
|
246
246
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Autonomous Artifact Pipelines
|
|
2
|
+
|
|
3
|
+
AgInTiFlow can run a project from raw inputs to final artifacts when the work is expressed as a local, resumable pipeline. This pattern is for jobs such as source conversion, book generation, report building, dataset annotation, media preparation, or any workflow where partial outputs are valuable and failure recovery matters.
|
|
4
|
+
|
|
5
|
+
## Contract
|
|
6
|
+
|
|
7
|
+
Each pipeline should create these project-local files or equivalents:
|
|
8
|
+
|
|
9
|
+
- Source manifest: raw files, hashes, roles, languages, extraction method, and caveats.
|
|
10
|
+
- Derived inputs: Markdown, text, tables, images, or structured bundles created from the raw files.
|
|
11
|
+
- Task manifest: stable chunk IDs, source locations, dependencies, prompt/schema version, and output paths.
|
|
12
|
+
- Schema and validator: the exact artifact shape plus semantic checks that define a promotable output.
|
|
13
|
+
- Runners: writer, reviewer, repairer, monitor, merge, compile/export, and status commands.
|
|
14
|
+
- Completion report: counts, first missing ID, failed/quarantined items, latest previews, final artifact paths, and resume commands.
|
|
15
|
+
|
|
16
|
+
The target repository owns its schemas, prompts, chunk policy, and rendering code. AgInTiFlow owns the behavior: inspect, create missing scripts, run observable sessions, preserve valid work, validate, repair, compile, and report evidence.
|
|
17
|
+
|
|
18
|
+
## Roles
|
|
19
|
+
|
|
20
|
+
The writer creates candidate artifacts. It should never be the only quality gate.
|
|
21
|
+
|
|
22
|
+
The validator promotes candidates only after schema and project-specific checks pass.
|
|
23
|
+
|
|
24
|
+
The reviewer inspects valid-looking artifacts for missing source units, source drift, repeated filler, malformed annotations, suspicious all-one-style output, and other known quality failures. It writes candidate fixes or failed-only repair requests.
|
|
25
|
+
|
|
26
|
+
The repairer runs independently of the writer. It can wake from status files, handle failed or quarantined chunks, retry with exact validator errors, reduce chunk size, or escalate to a stronger model when the project allows it.
|
|
27
|
+
|
|
28
|
+
The monitor is gentle. It waits through healthy progress and provider limits, restarts only on hard evidence of stall or crash, and records each decision.
|
|
29
|
+
|
|
30
|
+
## Concurrency
|
|
31
|
+
|
|
32
|
+
Parallelism is optional. When used, each worker needs deterministic shard ownership, separate logs, atomic writes, and no direct compile responsibility. Merge, promotion, compilation, publishing, and commits should be serialized unless the project already has a safe coordinator.
|
|
33
|
+
|
|
34
|
+
## Completion
|
|
35
|
+
|
|
36
|
+
A run is complete only when the final artifact was built from the current manifest and the status report shows full coverage or intentional quarantine. A successful tmux pane, a page count, or a single preview file is not enough.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Self-Healing Pipelines
|
|
2
|
+
|
|
3
|
+
AgInTiFlow treats long-running writers, reviewers, repairers, monitors, queues, ETL jobs, and batch generators as resumable pipelines rather than one-shot commands.
|
|
4
|
+
|
|
5
|
+
Use the `pipeline` task profile or rely on automatic skill selection when a prompt mentions a stalled writer, monitor, reviewer, queue worker, failed chunks, retry passes, stale claims, or tmux supervision.
|
|
6
|
+
|
|
7
|
+
## Operating Model
|
|
8
|
+
|
|
9
|
+
The agent should first read status files, manifests, logs, and tmux panes. It should compare progress across observations before calling a job stalled unless a hard error is visible. Healthy provider waits, rate limits, and long compile steps should usually be left alone.
|
|
10
|
+
|
|
11
|
+
When repair is justified, the agent should patch project-owned scripts or prompts in small reversible changes. Preferred repairs include failed-only retry modes, bounded retry passes, atomic writes, stale claim cleanup, idempotent compile commands, clear status JSON, heartbeat files, and durable logs.
|
|
12
|
+
|
|
13
|
+
Concurrency is a tool, not a product stance. AgInTiFlow should choose sequential, parallel, async, or review-gated operation from the user's request and the local project evidence. It should not force a sharded design into projects that do not need it.
|
|
14
|
+
|
|
15
|
+
Review and repair are separate from writing. A reviewer should detect missing source units, repeated filler, malformed structured data, source drift, and known quality failures, then produce candidate repairs or failed-only requests. A repairer should be able to run independently of the writer, wake from status files, run bounded passes, and exit without blocking healthy progress.
|
|
16
|
+
|
|
17
|
+
## Boundaries
|
|
18
|
+
|
|
19
|
+
AgInTiFlow should not embed project-specific schemas in its core. A book writer, data pipeline, or build system owns its own validators and artifact layout. AgInTiFlow provides the reusable behavior: diagnose, preserve valid work, patch the local workflow, verify, build checkpoint artifacts, restart only affected sessions, and report exact resume commands.
|
|
20
|
+
|
|
21
|
+
## Verification
|
|
22
|
+
|
|
23
|
+
After a repair, the agent should run syntax checks for changed scripts, perform a dry-run or bounded batch when safe, inspect counters and first-missing IDs, and keep unrelated tmux sessions running. If the same symptom repeats, the agent should improve the project workflow or a reusable AgInTiFlow skill instead of repeatedly sending manual nudges.
|
package/docs/skills-and-tools.md
CHANGED
|
@@ -12,7 +12,7 @@ AgInTiFlow separates **skills** from **tools** so the agent can stay general whi
|
|
|
12
12
|
|
|
13
13
|
## Built-In Skills
|
|
14
14
|
|
|
15
|
-
The package ships built-in skills for code engineering, website/app building, LaTeX manuscripts, books, Microsoft Word documents, image generation, GitHub maintenance, system maintenance, tmux session control, Android, R/Stan, Python, C/C++, shell scripting, AAPS, novel writing, and supervision/student-agent training.
|
|
15
|
+
The package ships built-in skills for code engineering, website/app building, LaTeX manuscripts, books, Microsoft Word documents, image generation, GitHub maintenance, system maintenance, source ingestion/OCR, structured JSON, autonomous artifact pipelines, tmux session control, Android, R/Stan, Python, C/C++, shell scripting, AAPS, novel writing, and supervision/student-agent training.
|
|
16
16
|
|
|
17
17
|
List them from a project:
|
|
18
18
|
|
|
@@ -71,3 +71,12 @@ For substantial writing tasks, prefer:
|
|
|
71
71
|
- The main agent for all non-writing work around that draft: file names, workspace edits, citations, Markdown/LaTeX/Final Draft formatting, PDF compilation, canvas publishing, and verification.
|
|
72
72
|
|
|
73
73
|
The writer receives only writing context: brief, canon, style guide, prior draft, target, audience, constraints, length, and downstream format intent. It should not receive shell/file/browser policy or agent-runtime details.
|
|
74
|
+
|
|
75
|
+
For schema-bound structured data, prefer:
|
|
76
|
+
|
|
77
|
+
- `json_specialist` for one isolated extraction, annotation, conversion, or validation request.
|
|
78
|
+
- `json_specialist_batch` for independent chunks that can be requested in parallel without shared writes.
|
|
79
|
+
|
|
80
|
+
The JSON specialist receives only the task, focused instructions, minimal context, input, and JSON Schema. It tries provider-native structured output (`json_schema` or JSON object mode) when available, then falls back to prompt-and-validate parsing.
|
|
81
|
+
|
|
82
|
+
For raw-input-to-final-output work, prefer the autonomous artifact pipeline pattern. The target project should own its source manifest, chunk manifest, schemas, validators, runner scripts, reviewer/repairer logic, and compiler/exporter. AgInTiFlow should create or patch those project-local pieces, run them in observable sessions, and verify checkpoint artifacts before declaring completion.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.150",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AgInTiFlow is a project-aware agent workspace for hybrid wet-dry R&D, hardware-aware intelligence, software automation, and industrial workflows.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -129,6 +129,10 @@ try {
|
|
|
129
129
|
capabilities.tools?.taskProfiles?.some((profile) => profile.id === "supervision"),
|
|
130
130
|
"capabilities did not report supervision task profile"
|
|
131
131
|
);
|
|
132
|
+
assert(
|
|
133
|
+
capabilities.tools?.taskProfiles?.some((profile) => profile.id === "pipeline"),
|
|
134
|
+
"capabilities did not report pipeline task profile"
|
|
135
|
+
);
|
|
132
136
|
for (const profileId of ["docs", "data", "qa", "database", "devops", "security", "slides", "education", "java", "ios", "go", "rust", "dotnet", "php", "ruby"]) {
|
|
133
137
|
assert(
|
|
134
138
|
capabilities.tools?.taskProfiles?.some((profile) => profile.id === profileId),
|
|
@@ -138,6 +142,7 @@ try {
|
|
|
138
142
|
const qaProfile = listTaskProfiles().find((profile) => profile.id === "qa");
|
|
139
143
|
assert(qaProfile, "QA profile is missing");
|
|
140
144
|
assert(defaultMaxStepsForProfile("qa") >= 40, "QA profile step budget is too low for verification and cleanup");
|
|
145
|
+
assert(defaultMaxStepsForProfile("pipeline") >= 44, "pipeline profile step budget is too low for repair/verify/resume loops");
|
|
141
146
|
assert(!/misleading failing test/i.test(qaProfile.prompt), "QA profile still encourages misleading test fixtures");
|
|
142
147
|
assert(/do not stage fake bugs/i.test(qaProfile.prompt), "QA profile does not discourage fake staged failures");
|
|
143
148
|
assert(
|
|
@@ -168,6 +173,10 @@ try {
|
|
|
168
173
|
capabilities.tools?.skills?.some((skill) => skill.id === "supervision-student"),
|
|
169
174
|
"capabilities did not report built-in supervision skill"
|
|
170
175
|
);
|
|
176
|
+
assert(
|
|
177
|
+
capabilities.tools?.skills?.some((skill) => skill.id === "self-healing-pipeline"),
|
|
178
|
+
"capabilities did not report built-in self-healing pipeline skill"
|
|
179
|
+
);
|
|
171
180
|
for (const skillId of ["data-analysis", "docs-knowledge", "qa-testing", "database", "devops-deployment", "security-review", "presentation-slides", "writing-editing", "java-jvm", "ios-swift", "go", "rust", "dotnet-csharp", "php", "ruby"]) {
|
|
172
181
|
assert(
|
|
173
182
|
capabilities.tools?.skills?.some((skill) => skill.id === skillId),
|
|
@@ -25,6 +25,7 @@ import { createPlan } from "../src/model-client.js";
|
|
|
25
25
|
import { selectModelRoute } from "../src/model-routing.js";
|
|
26
26
|
import { listParallelScouts, runParallelScouts, shouldRunParallelScouts } from "../src/parallel-scouts.js";
|
|
27
27
|
import { buildFailedCommandAdvice, buildPermissionAdvice } from "../src/permission-advice.js";
|
|
28
|
+
import { runJsonSpecialist } from "../src/json-specialist.js";
|
|
28
29
|
import { SessionStore } from "../src/session-store.js";
|
|
29
30
|
import { getTaskProfile } from "../src/task-profiles.js";
|
|
30
31
|
import { searchWeb } from "../src/web-search.js";
|
|
@@ -613,6 +614,30 @@ try {
|
|
|
613
614
|
writingRun.events.some((event) => event.type === "tool.completed" && event.data?.toolName === "writing_specialist"),
|
|
614
615
|
"mock agent did not route writing work through writing_specialist"
|
|
615
616
|
);
|
|
617
|
+
const jsonResult = await runJsonSpecialist(
|
|
618
|
+
{
|
|
619
|
+
task: "Return a strict JSON status object.",
|
|
620
|
+
schema: {
|
|
621
|
+
type: "object",
|
|
622
|
+
properties: {
|
|
623
|
+
summary: { type: "string" },
|
|
624
|
+
complete: { type: "boolean" },
|
|
625
|
+
},
|
|
626
|
+
required: ["summary", "complete"],
|
|
627
|
+
additionalProperties: false,
|
|
628
|
+
},
|
|
629
|
+
inputText: "structured JSON smoke",
|
|
630
|
+
provider: "mock",
|
|
631
|
+
},
|
|
632
|
+
{ provider: "mock", model: "mock-agent" },
|
|
633
|
+
new SessionStore(runtimeDir, "json-specialist-smoke", { projectRoot: workspace, commandCwd: workspace })
|
|
634
|
+
);
|
|
635
|
+
assert(jsonResult.ok && jsonResult.result?.complete === true, "json_specialist mock result did not satisfy schema");
|
|
636
|
+
const jsonRun = await runMock("Extract a valid JSON object with schema from this text.", "coding-json-specialist");
|
|
637
|
+
assert(
|
|
638
|
+
jsonRun.events.some((event) => event.type === "tool.completed" && event.data?.toolName === "json_specialist"),
|
|
639
|
+
"mock agent did not route structured JSON work through json_specialist"
|
|
640
|
+
);
|
|
616
641
|
|
|
617
642
|
await fs.mkdir(path.join(workspace, "src"), { recursive: true });
|
|
618
643
|
await fs.mkdir(path.join(workspace, "test"), { recursive: true });
|
|
@@ -1104,6 +1129,8 @@ try {
|
|
|
1104
1129
|
"deepseek_pro_writing_route",
|
|
1105
1130
|
"writing_specialist_mock",
|
|
1106
1131
|
"writing_specialist_mock_routing",
|
|
1132
|
+
"json_specialist_mock",
|
|
1133
|
+
"json_specialist_mock_routing",
|
|
1107
1134
|
"runtime_time_context",
|
|
1108
1135
|
"large_profile_pro_route",
|
|
1109
1136
|
"auto_system_pro_route",
|
package/scripts/smoke-skills.js
CHANGED
|
@@ -21,6 +21,7 @@ const ids = new Set(skills.map((skill) => skill.id));
|
|
|
21
21
|
assert(skills.length >= 27, "expected built-in skills to load");
|
|
22
22
|
for (const required of [
|
|
23
23
|
"aaps",
|
|
24
|
+
"autonomous-artifact-pipeline",
|
|
24
25
|
"code",
|
|
25
26
|
"code-review",
|
|
26
27
|
"data-analysis",
|
|
@@ -41,6 +42,9 @@ for (const required of [
|
|
|
41
42
|
"rust",
|
|
42
43
|
"php",
|
|
43
44
|
"security-review",
|
|
45
|
+
"self-healing-pipeline",
|
|
46
|
+
"source-ingestion",
|
|
47
|
+
"structured-json",
|
|
44
48
|
"system-maintenance",
|
|
45
49
|
"supervision-student",
|
|
46
50
|
"tmux-session",
|
|
@@ -57,6 +61,13 @@ assert(selectedIds("edit a Microsoft Word docx and preserve the original").inclu
|
|
|
57
61
|
assert(selectedIds("generate a logo image with grsai nanobanana").includes("image-generation"), "image prompt did not select image-generation");
|
|
58
62
|
assert(selectedIds("git status commit push with gh").includes("github-maintenance"), "git prompt did not select github-maintenance");
|
|
59
63
|
assert(selectedIds("monitor a long running tmux session").includes("tmux-session"), "tmux prompt did not select tmux-session");
|
|
64
|
+
assert(selectedIds("repair a stuck writer monitor pipeline and retry failed chunks").includes("self-healing-pipeline"), "stuck pipeline prompt did not select self-healing-pipeline");
|
|
65
|
+
assert(
|
|
66
|
+
selectedIds("from raw pdf epub sources create markdown chunks json and compile final pdf in tmux with monitor and auto repair").includes(
|
|
67
|
+
"autonomous-artifact-pipeline"
|
|
68
|
+
),
|
|
69
|
+
"raw-source artifact pipeline prompt did not select autonomous-artifact-pipeline"
|
|
70
|
+
);
|
|
60
71
|
assert(selectedIds("create an .aaps example for @lazyingart/aaps").includes("aaps"), "AAPS prompt did not select aaps");
|
|
61
72
|
assert(selectedIds("debug a C++ CMake build").includes("c-cpp"), "C++ prompt did not select c-cpp");
|
|
62
73
|
assert(selectedIds("set up Stan and CmdStanR reproducibly").includes("r-stan"), "Stan prompt did not select r-stan");
|
|
@@ -68,6 +79,8 @@ assert(selectedIds("debug a C# dotnet web API").includes("dotnet-csharp"), ".NET
|
|
|
68
79
|
assert(selectedIds("fix a PHP Laravel composer project").includes("php"), "PHP prompt did not select php");
|
|
69
80
|
assert(selectedIds("repair a Ruby Rails app with RSpec").includes("ruby"), "Ruby prompt did not select ruby");
|
|
70
81
|
assert(selectedIds("clean a CSV dataset and make plots").includes("data-analysis"), "data prompt did not select data-analysis");
|
|
82
|
+
assert(selectedIds("convert scanned PDF EPUB and image sources to markdown with OCR").includes("source-ingestion"), "source ingestion prompt did not select source-ingestion");
|
|
83
|
+
assert(selectedIds("use json schema to fetch valid structured json for each chunk in parallel").includes("structured-json"), "structured JSON prompt did not select structured-json");
|
|
71
84
|
assert(selectedIds("write README API docs and a tutorial").includes("docs-knowledge"), "docs prompt did not select docs-knowledge");
|
|
72
85
|
assert(selectedIds("fix failing tests and add regression coverage").includes("qa-testing"), "QA prompt did not select qa-testing");
|
|
73
86
|
const qaSkill = skills.find((skill) => skill.id === "qa-testing");
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: autonomous-artifact-pipeline
|
|
3
|
+
label: Autonomous Artifact Pipeline
|
|
4
|
+
description: Turn raw inputs into validated final artifacts through a resumable, observable pipeline with ingestion, chunking, writers, reviewers, repairers, monitors, and checkpoint builds.
|
|
5
|
+
triggers:
|
|
6
|
+
- raw source to final output
|
|
7
|
+
- autonomous pipeline
|
|
8
|
+
- artifact pipeline
|
|
9
|
+
- final artifact
|
|
10
|
+
- prepare chunks
|
|
11
|
+
- meta tasks
|
|
12
|
+
- writer reviewer repairer
|
|
13
|
+
- tmux monitor
|
|
14
|
+
- resumable batch
|
|
15
|
+
- compile output
|
|
16
|
+
- auto repair
|
|
17
|
+
tools:
|
|
18
|
+
- inspect_project
|
|
19
|
+
- read_file
|
|
20
|
+
- write_file
|
|
21
|
+
- apply_patch
|
|
22
|
+
- run_command
|
|
23
|
+
- tmux_start_session
|
|
24
|
+
- tmux_capture_pane
|
|
25
|
+
- tmux_send_keys
|
|
26
|
+
- send_to_canvas
|
|
27
|
+
---
|
|
28
|
+
# Autonomous Artifact Pipeline
|
|
29
|
+
|
|
30
|
+
Use this skill when the user wants AgInTiFlow to start from raw materials and keep working until durable final artifacts exist, such as books, reports, datasets, PDFs, apps, slides, media bundles, or generated JSON corpora.
|
|
31
|
+
|
|
32
|
+
## Pipeline Contract
|
|
33
|
+
|
|
34
|
+
Before launching long work, create or identify a project-local contract:
|
|
35
|
+
|
|
36
|
+
1. Source manifest: raw paths, hashes, source roles, language/type, extraction method, and caveats.
|
|
37
|
+
2. Derived inputs: Markdown, text, images, tables, or structured bundles produced from the raw sources.
|
|
38
|
+
3. Task manifest: stable chunk IDs, source location, dependency order, prompt/schema version, and output paths.
|
|
39
|
+
4. Artifact schema: JSON Schema or other validator-owned shape for each generated unit.
|
|
40
|
+
5. Runners: resumable writer, reviewer, repairer, monitor, merge, compile/export, and status commands.
|
|
41
|
+
6. Completion evidence: counters, first missing item, failed IDs, current previews, final artifact paths, and resume commands.
|
|
42
|
+
|
|
43
|
+
Project-specific schemas, prompts, layouts, and compilers belong in the target repository. AgInTiFlow provides the orchestration pattern and should generate or patch local scripts when they are missing.
|
|
44
|
+
|
|
45
|
+
## Execution Pattern
|
|
46
|
+
|
|
47
|
+
- Inspect the repository and instructions first. Preserve raw inputs and do not overwrite reviewed outputs.
|
|
48
|
+
- Convert raw files into durable intermediate inputs before asking a model to generate downstream artifacts.
|
|
49
|
+
- Split work into deterministic chunks that survive reruns. If chunk policy changes, map old outputs by stable source IDs instead of restarting from zero.
|
|
50
|
+
- Use isolated structured-data calls for repetitive JSON units. Keep prompts focused on the chunk, schema, source references, and validation errors.
|
|
51
|
+
- Run writers in tmux or another observable background process. Each worker must have disjoint claims, atomic output writes, and shard-local logs.
|
|
52
|
+
- Keep review and repair asynchronous but safe. Reviewers may produce candidate fixes while writers continue; only validators or merge scripts promote candidates.
|
|
53
|
+
- Compile or export checkpoint previews after successful merge batches and always at final completion.
|
|
54
|
+
- Commit reusable scripts, manifests, validators, templates, and stable checkpoints when the project expects git tracking.
|
|
55
|
+
|
|
56
|
+
## Autorepair Behavior
|
|
57
|
+
|
|
58
|
+
A robust pipeline has an independent repair path that is not blocked by the main writer:
|
|
59
|
+
|
|
60
|
+
- Heartbeats record active worker, current chunk, last success, last failure, and provider wait state.
|
|
61
|
+
- Provider/rate-limit failures wait with backoff and retry at long intervals.
|
|
62
|
+
- Schema/parse failures are repaired with the exact validator error and the smallest useful input.
|
|
63
|
+
- Semantic/source-drift failures are retried with smaller chunks or stronger source references.
|
|
64
|
+
- Repeated failures are quarantined with reasons, then handled by a bounded failed-only repair pass.
|
|
65
|
+
- Monitor intervention is gentle: observe healthy progress, restart only on hard error, stale claim, repeated no-progress window, or missing child process.
|
|
66
|
+
|
|
67
|
+
## Done Criteria
|
|
68
|
+
|
|
69
|
+
Do not call the task complete until the final artifact was built from the current manifest and the status report shows complete or intentionally quarantined coverage. A partial PDF, stale page count, or successful worker log is not enough.
|
|
@@ -27,13 +27,14 @@ Use this skill when the task asks for a paired-language book, ruby/furigana/piny
|
|
|
27
27
|
|
|
28
28
|
1. Inspect repository instructions, existing scripts, book plans, and ignored paths before editing.
|
|
29
29
|
2. Keep original PDFs/EPUBs in source folders and do not commit large source media unless the repository explicitly tracks them.
|
|
30
|
-
3. Convert source books to durable Markdown first. Keep raw and cleaned Markdown separate when OCR or EPUB extraction is noisy.
|
|
30
|
+
3. Convert source books to durable Markdown first. Treat PDF, EPUB, image, JSON/wiki, and scanned sources as ingestion problems; create a source manifest with hashes, method, role, language, and caveats before generation. Keep raw and cleaned Markdown separate when OCR or EPUB extraction is noisy.
|
|
31
31
|
4. Split cleaned Markdown into stable paragraph- or chapter-scoped chunks with `manifest.json` and `chunks.jsonl`. Use source paragraph IDs that survive reruns. If a paragraph is too large for reliable provider output, split it into ordered subchunks at sentence or clause boundaries while preserving the original source order and recording `split_from_chunk_id`, `split_part`, and `split_part_count`.
|
|
32
32
|
5. When retuning chunk size or repairing split logic, merge any existing `split_from_chunk_id` groups back to the original paragraph text first, then split again. Do not repeatedly split already-split parts, and do not discard valid reviewed chunks unless validation proves they no longer match the manifest.
|
|
33
33
|
6. Write resumable per-chunk JSON artifacts. Never overwrite a valid reviewed chunk unless a validator or prompt version requires regeneration; move stale chunks out of the compile path.
|
|
34
|
-
7. Generate or repair annotations with a provider worker loop, not a monolithic prompt. Each chunk should validate independently before promotion. If JSON is malformed or validation fails, retry the chunk with the exact validator errors before marking it failed.
|
|
35
|
-
8.
|
|
36
|
-
9.
|
|
34
|
+
7. Generate or repair annotations with a provider worker loop, not a monolithic prompt. Each chunk should validate independently before promotion. If JSON is malformed or validation fails, retry the chunk with the exact validator errors before marking it failed. Use the structured JSON workflow for repetitive chunk output.
|
|
35
|
+
8. Keep writer, reviewer, repairer, monitor, merge, and compile roles explicit. Writers produce candidates; validators promote; reviewers check semantic quality and request fixes; repairers handle failed-only or quarantined chunks; monitors observe and resume gently.
|
|
36
|
+
9. Compile preview PDFs periodically and at the end. For paired-language books, compile both directions when renderers exist, plus color and blackwhite variants when color is supported.
|
|
37
|
+
10. Run long jobs in observable tmux sessions with status files, logs, retry/backoff for provider limits, and clear resume commands.
|
|
37
38
|
|
|
38
39
|
## JSON Quality Gates
|
|
39
40
|
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: self-healing-pipeline
|
|
3
|
+
label: Self-Healing Pipeline
|
|
4
|
+
description: Diagnose stalled long-running writer, reviewer, monitor, queue, ETL, build, and generation pipelines; patch project-owned scripts; verify; and resume without discarding valid work.
|
|
5
|
+
triggers:
|
|
6
|
+
- self healing
|
|
7
|
+
- autorepair
|
|
8
|
+
- auto repair
|
|
9
|
+
- stuck pipeline
|
|
10
|
+
- stalled pipeline
|
|
11
|
+
- writer monitor
|
|
12
|
+
- reviewer monitor
|
|
13
|
+
- queue worker
|
|
14
|
+
- long running pipeline
|
|
15
|
+
- retry failed
|
|
16
|
+
- resume failed
|
|
17
|
+
tools:
|
|
18
|
+
- inspect_project
|
|
19
|
+
- read_file
|
|
20
|
+
- search_files
|
|
21
|
+
- apply_patch
|
|
22
|
+
- run_command
|
|
23
|
+
- tmux_list_sessions
|
|
24
|
+
- tmux_capture_pane
|
|
25
|
+
- tmux_send_keys
|
|
26
|
+
- tmux_start_session
|
|
27
|
+
---
|
|
28
|
+
# Self-Healing Pipeline
|
|
29
|
+
|
|
30
|
+
Use this skill when a project has a durable worker, writer, reviewer, monitor, queue, batch generator, ETL, build loop, or tmux job that must keep moving after malformed output, provider limits, crashes, bad chunks, stale locks, or compile failures.
|
|
31
|
+
|
|
32
|
+
## Diagnose Before Repair
|
|
33
|
+
|
|
34
|
+
1. Read project instructions, runner scripts, status files, manifests, logs, and current tmux panes.
|
|
35
|
+
2. Classify the symptom as one of: healthy wait, provider/rate-limit wait, validation failure, deterministic data/schema failure, script crash, stale lock/claim, compile/render failure, missing dependency, or monitor failure.
|
|
36
|
+
3. Compare progress counters across two observations before declaring a stall unless the logs show a hard error.
|
|
37
|
+
4. Preserve valid outputs. Never delete reviewed artifacts, manifests, checkpoints, or source inputs unless validation proves they are stale and the project has a quarantine path.
|
|
38
|
+
|
|
39
|
+
## Repair Pattern
|
|
40
|
+
|
|
41
|
+
- Patch project-owned scripts, prompts, validators, or monitors only after the logs identify a repeatable failure.
|
|
42
|
+
- Prefer small resumability upgrades: `--failed-only`, bounded retry passes, stale-claim cleanup, atomic writes, checkpoint status, idempotent compile commands, and clear resume commands.
|
|
43
|
+
- Keep writer/reviewer/monitor responsibilities separate. The writer should produce and validate; the reviewer should repair quality; the monitor should observe, compile, restart, or queue the next bounded run.
|
|
44
|
+
- Make monitors gentle: wait on healthy progress, restart only after explicit stop/stall/error evidence, and write a durable decision log.
|
|
45
|
+
- If parallel workers exist, require disjoint output paths or claim files, atomic promotion, and merge-stage validation before compiling.
|
|
46
|
+
- A reviewer is not just a promoter. It should inspect valid-looking outputs for source drift, missing units, repeated filler, malformed annotations, and known quality failures, then write candidate fixes or failed-only repair requests.
|
|
47
|
+
- A companion repairer should be independent from the main writer process. It can stay dormant, wake from status files or monitor decisions, run bounded repair passes, and exit without blocking healthy writer progress.
|
|
48
|
+
- If the same failure repeats after a local nudge, improve the runner, validator, prompt, or status model rather than relying on manual chat intervention.
|
|
49
|
+
|
|
50
|
+
## Parallel And Async Options
|
|
51
|
+
|
|
52
|
+
Parallel and async designs are optional implementation patterns, not a default preference. Use them when the user asks for concurrency, the existing project already has a parallel pipeline, or the evidence shows a sequential bottleneck that can be partitioned safely.
|
|
53
|
+
|
|
54
|
+
- Sequential processing is often the safest default for small jobs, fragile prompts, scarce quota, or unclear ownership boundaries.
|
|
55
|
+
- If using sharded writer/fetcher workers for independent JSON/data chunks, give each worker a deterministic shard, separate log file, and no compile responsibility.
|
|
56
|
+
- An async reviewer/promoter loop can validate and promote completed candidate files while writers continue, but it must use atomic writes, locks, or merge directories so it cannot race with active writers.
|
|
57
|
+
- Keep compilation, publishing, and git commits out of parallel workers unless the project already has a safe, serialized mechanism for those steps.
|
|
58
|
+
- If a worker stalls on one bad chunk, it should mark the chunk failed and continue its shard. Failed-only repair passes should be bounded and observable.
|
|
59
|
+
- When increasing concurrency, check provider quota/rate-limit behavior. If rate limits appear, reduce worker count or add backoff rather than letting every worker retry aggressively.
|
|
60
|
+
|
|
61
|
+
## Verification And Resume
|
|
62
|
+
|
|
63
|
+
After a repair:
|
|
64
|
+
|
|
65
|
+
1. Run syntax checks for changed scripts.
|
|
66
|
+
2. Run a dry-run or small bounded batch when safe.
|
|
67
|
+
3. Verify status counters, first missing item, failed IDs, and output timestamps.
|
|
68
|
+
4. Restart only the affected tmux session, not unrelated jobs.
|
|
69
|
+
5. Compile or export a checkpoint artifact when the pipeline has a renderer or build command.
|
|
70
|
+
6. Record the exact resume command, current status, logs inspected, remaining failed items, and any quarantined artifacts.
|
|
71
|
+
7. Commit reusable script/profile/prompt fixes when the project expects git tracking.
|
|
72
|
+
|
|
73
|
+
The goal is not to hide failures. The goal is to keep the pipeline observable, resumable, and able to recover from known classes of failure without overwriting good work.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: source-ingestion
|
|
3
|
+
label: Source Ingestion And OCR
|
|
4
|
+
description: Inspect mixed input files and convert PDFs, EPUBs, images, scans, web/JSON sources, archives, or unknown documents into useful text, Markdown, manifests, or reviewable artifacts.
|
|
5
|
+
triggers:
|
|
6
|
+
- source
|
|
7
|
+
- sources
|
|
8
|
+
- input file
|
|
9
|
+
- convert to markdown
|
|
10
|
+
- extract text
|
|
11
|
+
- pdf
|
|
12
|
+
- ocr
|
|
13
|
+
- scanned
|
|
14
|
+
- epub
|
|
15
|
+
- image text
|
|
16
|
+
- recognition
|
|
17
|
+
- markdown
|
|
18
|
+
tools:
|
|
19
|
+
- inspect_project
|
|
20
|
+
- read_file
|
|
21
|
+
- write_file
|
|
22
|
+
- run_command
|
|
23
|
+
- web_search
|
|
24
|
+
- send_to_canvas
|
|
25
|
+
---
|
|
26
|
+
# Source Ingestion And OCR
|
|
27
|
+
|
|
28
|
+
Use this skill when the user gives arbitrary source files or asks to read, recognize, OCR, convert, or prepare inputs for another workflow. The task is to make the files usable without assuming they are already text.
|
|
29
|
+
|
|
30
|
+
## Operating Loop
|
|
31
|
+
|
|
32
|
+
1. Inventory inputs first. List paths, extensions, sizes, likely language, and whether each file is original media or derived output. Keep original files untouched.
|
|
33
|
+
2. Probe before converting:
|
|
34
|
+
- `file`, `stat`, `pdfinfo`, `pdftotext`, `exiftool` when available.
|
|
35
|
+
- EPUB: inspect the archive or use an existing EPUB-to-Markdown script/tool.
|
|
36
|
+
- PDF: try text-layer extraction first; if empty or garbage, mark as image-only and choose OCR.
|
|
37
|
+
- Images/scans: inspect dimensions/orientation and run OCR only after deciding language and page segmentation.
|
|
38
|
+
- JSON/HTML/wiki manifests: read the manifest, follow local `html`, `pdf`, `iiif`, or source fields, and prefer structured extraction over OCR.
|
|
39
|
+
- Archives/directories: expand or enumerate into a durable work folder only when needed.
|
|
40
|
+
3. Choose the simplest reliable route. Prefer existing project scripts and installed tools before adding dependencies. Install only when policy allows and record the command.
|
|
41
|
+
4. Write durable outputs near the project workflow, usually `books/<id>/sources/markdown/`, `ocr/`, `artifacts/`, or a user-specified path. Use descriptive names; do not overwrite reviewed outputs unless asked.
|
|
42
|
+
5. Produce a manifest for nontrivial ingestion. Include source path, sha256, extraction method, status (`complete`, `requires_ocr`, `failed`, `pending`), language, page/chapter counts, output path, and caveats.
|
|
43
|
+
6. Validate the result externally: line/character counts, heading counts, boilerplate/debris checks, sample excerpts, and a no-text-layer check for PDFs marked `requires_ocr`.
|
|
44
|
+
|
|
45
|
+
## OCR And Recognition Strategy
|
|
46
|
+
|
|
47
|
+
- Treat OCR as a pipeline, not a guess: render pages, crop/deskew if needed, choose language (`chi_sim`, `chi_tra`, `jpn`, `jpn_vert`, `eng`, or combinations), test a small page range, then scale up.
|
|
48
|
+
- For large scans, create resumable page-level outputs and a manifest before full OCR. Do not run a fragile all-pages command without logs and resume paths.
|
|
49
|
+
- For vertical Japanese or classical Chinese scans, expect tool tuning. If OCR quality is poor, save page images and report that manual/model-assisted correction is required rather than fabricating text.
|
|
50
|
+
- Preserve page references in OCR Markdown (`## Page N`) until the text is reviewed; only collapse into chapters after quality checks.
|
|
51
|
+
|
|
52
|
+
## Expected Outputs
|
|
53
|
+
|
|
54
|
+
When the downstream task expects Markdown, JSON, or a source bundle, create exactly that shape plus a report:
|
|
55
|
+
|
|
56
|
+
- Markdown: clean body text with stable headings and paragraphs.
|
|
57
|
+
- JSON: schema-valid and validated, not prose pretending to be JSON.
|
|
58
|
+
- Source bundle: roles, source hashes, extraction status, and activation rules.
|
|
59
|
+
- Report: what was converted, what was not, tools used, validation commands, and residual risks.
|
|
60
|
+
|
|
61
|
+
## Safety Rules
|
|
62
|
+
|
|
63
|
+
Do not claim a scanned PDF or image was converted if the output is empty, repeated headers, mojibake, or OCR garbage. Mark it `requires_ocr` or `failed` with evidence. Do not silently delete real content while removing boilerplate. Do not commit original large source media unless the repository already tracks that category and the user asked for it.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: structured-json
|
|
3
|
+
label: Structured JSON Generation
|
|
4
|
+
description: Use isolated schema-bound JSON generation for extraction, annotation, conversion, classification, repair, or chunked data production without mixing in agent runtime context.
|
|
5
|
+
triggers:
|
|
6
|
+
- structured json
|
|
7
|
+
- json schema
|
|
8
|
+
- schema-bound
|
|
9
|
+
- json specialist
|
|
10
|
+
- json fetcher
|
|
11
|
+
- parallel json
|
|
12
|
+
- repair json
|
|
13
|
+
- valid json
|
|
14
|
+
tools:
|
|
15
|
+
- json_specialist
|
|
16
|
+
- json_specialist_batch
|
|
17
|
+
- read_file
|
|
18
|
+
- write_file
|
|
19
|
+
- run_command
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
# Structured JSON
|
|
23
|
+
|
|
24
|
+
Use this skill when the user needs reliable JSON that follows an explicit schema, especially for repetitive chunk processing.
|
|
25
|
+
|
|
26
|
+
## Workflow
|
|
27
|
+
|
|
28
|
+
1. Define the smallest useful JSON Schema for the next data artifact.
|
|
29
|
+
2. Pass only the task, focused instructions, minimal domain context, input, and schema to `json_specialist`.
|
|
30
|
+
3. Use `json_specialist_batch` only when items are independent and can be processed in parallel without shared writes.
|
|
31
|
+
4. Keep formatting, file writes, validation scripts, compilation, and project-specific orchestration in the main agent.
|
|
32
|
+
5. After the tool returns, validate any project-specific invariants with local scripts before treating the data as complete.
|
|
33
|
+
|
|
34
|
+
## Provider Strategy
|
|
35
|
+
|
|
36
|
+
- Prefer provider-native structured output when available, such as JSON Schema or JSON object mode.
|
|
37
|
+
- Keep a fallback parser/repair path for providers that do not support strict schema responses.
|
|
38
|
+
- On validation failure, retry with the exact schema errors and only the smallest relevant source text.
|
|
39
|
+
- For batch work, write candidate JSON per chunk first; promote it only after schema and semantic validators pass.
|
|
40
|
+
- Keep schema versions in the artifact metadata so old reviewed outputs can be reused or selectively regenerated when prompts change.
|
|
41
|
+
|
|
42
|
+
## Boundaries
|
|
43
|
+
|
|
44
|
+
- Do not pass shell, browser, file policy, package-install, or agent-planning context into the JSON specialist.
|
|
45
|
+
- Do not make schemas book-, app-, or project-specific inside AgInTiFlow core. Project schemas belong in the target repository.
|
|
46
|
+
- Do not let parallel JSON workers share one mutable output file. Use shard-local outputs, atomic renames, and a serialized merge/promote step.
|
package/src/agent-runner.js
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
import { refreshCodebaseMap } from "./codebase-map.js";
|
|
27
27
|
import { readImage, researchWrapper, webResearch } from "./perception-tools.js";
|
|
28
28
|
import { searchWeb } from "./web-search.js";
|
|
29
|
+
import { runJsonSpecialist, runJsonSpecialistBatch } from "./json-specialist.js";
|
|
29
30
|
import { runWritingSpecialist } from "./writing-specialist.js";
|
|
30
31
|
import { runParallelScouts, shouldRunParallelScouts } from "./parallel-scouts.js";
|
|
31
32
|
import { readProjectInstructions } from "./project.js";
|
|
@@ -1214,6 +1215,26 @@ function sanitizeToolArgs(toolName, args) {
|
|
|
1214
1215
|
typeof args.constraints === "string" ? `[${Buffer.byteLength(args.constraints, "utf8")} bytes sha256=${hashForLog(args.constraints)}]` : safeArgs.constraints,
|
|
1215
1216
|
};
|
|
1216
1217
|
}
|
|
1218
|
+
if (toolName === "json_specialist") {
|
|
1219
|
+
return {
|
|
1220
|
+
...safeArgs,
|
|
1221
|
+
task: typeof args.task === "string" ? `[${Buffer.byteLength(args.task, "utf8")} bytes sha256=${hashForLog(args.task)}]` : safeArgs.task,
|
|
1222
|
+
instructions:
|
|
1223
|
+
typeof args.instructions === "string" ? `[${Buffer.byteLength(args.instructions, "utf8")} bytes sha256=${hashForLog(args.instructions)}]` : safeArgs.instructions,
|
|
1224
|
+
context: typeof args.context === "string" ? `[${Buffer.byteLength(args.context, "utf8")} bytes sha256=${hashForLog(args.context)}]` : safeArgs.context,
|
|
1225
|
+
inputText:
|
|
1226
|
+
typeof args.inputText === "string" ? `[${Buffer.byteLength(args.inputText, "utf8")} bytes sha256=${hashForLog(args.inputText)}]` : safeArgs.inputText,
|
|
1227
|
+
schemaJson:
|
|
1228
|
+
typeof args.schemaJson === "string" ? `[${Buffer.byteLength(args.schemaJson, "utf8")} bytes sha256=${hashForLog(args.schemaJson)}]` : safeArgs.schemaJson,
|
|
1229
|
+
};
|
|
1230
|
+
}
|
|
1231
|
+
if (toolName === "json_specialist_batch") {
|
|
1232
|
+
return {
|
|
1233
|
+
...safeArgs,
|
|
1234
|
+
defaults: args.defaults ? "[json specialist defaults redacted]" : safeArgs.defaults,
|
|
1235
|
+
items: Array.isArray(args.items) ? `[${args.items.length} json specialist items]` : safeArgs.items,
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1217
1238
|
if (toolName === "apply_patch") {
|
|
1218
1239
|
return {
|
|
1219
1240
|
...safeArgs,
|
|
@@ -1309,6 +1330,19 @@ export function sanitizeToolResult(result) {
|
|
|
1309
1330
|
safeResult.draftTruncated = true;
|
|
1310
1331
|
delete safeResult.draft;
|
|
1311
1332
|
}
|
|
1333
|
+
if (safeResult.toolName === "json_specialist" && safeResult.result !== undefined) {
|
|
1334
|
+
const encoded = JSON.stringify(safeResult.result);
|
|
1335
|
+
safeResult.resultBytes = Buffer.byteLength(encoded, "utf8");
|
|
1336
|
+
if (safeResult.resultBytes > TOOL_RESULT_INLINE_CONTENT_BYTES) {
|
|
1337
|
+
safeResult.resultPreview = encoded.slice(0, TOOL_RESULT_CONTENT_PREVIEW_CHARS);
|
|
1338
|
+
safeResult.resultTruncated = true;
|
|
1339
|
+
delete safeResult.result;
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
if (safeResult.toolName === "json_specialist_batch" && Array.isArray(safeResult.results)) {
|
|
1343
|
+
safeResult.resultCount = safeResult.results.length;
|
|
1344
|
+
safeResult.results = safeResult.results.map((item) => sanitizeToolResult(item));
|
|
1345
|
+
}
|
|
1312
1346
|
return safeResult;
|
|
1313
1347
|
}
|
|
1314
1348
|
|
|
@@ -1584,6 +1618,20 @@ async function executeTool(browserState, toolCall, snapshot, config, store, obse
|
|
|
1584
1618
|
observers.event(result.ok ? "tool.completed" : "tool.failed", eventResult);
|
|
1585
1619
|
return result;
|
|
1586
1620
|
}
|
|
1621
|
+
case "json_specialist": {
|
|
1622
|
+
const result = await runJsonSpecialist(args, config, store);
|
|
1623
|
+
const eventResult = sanitizeToolResult(result);
|
|
1624
|
+
await store.appendEvent(result.ok ? "tool.completed" : "tool.failed", eventResult);
|
|
1625
|
+
observers.event(result.ok ? "tool.completed" : "tool.failed", eventResult);
|
|
1626
|
+
return result;
|
|
1627
|
+
}
|
|
1628
|
+
case "json_specialist_batch": {
|
|
1629
|
+
const result = await runJsonSpecialistBatch(args.items || [], { ...args, items: undefined }, config, store);
|
|
1630
|
+
const eventResult = sanitizeToolResult(result);
|
|
1631
|
+
await store.appendEvent(result.ok ? "tool.completed" : "tool.failed", eventResult);
|
|
1632
|
+
observers.event(result.ok ? "tool.completed" : "tool.failed", eventResult);
|
|
1633
|
+
return result;
|
|
1634
|
+
}
|
|
1587
1635
|
case "writing_specialist": {
|
|
1588
1636
|
const result = await runWritingSpecialist(args, config, store);
|
|
1589
1637
|
const eventResult = sanitizeToolResult(result);
|
package/src/guardrails.js
CHANGED
|
@@ -304,6 +304,66 @@ export function checkToolUse({ toolName, args, snapshot, config }) {
|
|
|
304
304
|
return { allowed: true };
|
|
305
305
|
}
|
|
306
306
|
|
|
307
|
+
if (toolName === "json_specialist") {
|
|
308
|
+
const task = String(args.task || args.prompt || "").trim();
|
|
309
|
+
if (!task) return { allowed: false, reason: "JSON specialist requires task.", category: "json-specialist" };
|
|
310
|
+
if (!args.schema && !String(args.schemaJson || "").trim()) {
|
|
311
|
+
return { allowed: false, reason: "JSON specialist requires schema or schemaJson.", category: "json-specialist" };
|
|
312
|
+
}
|
|
313
|
+
const provider = String(args.provider || process.env.AGINTI_JSON_PROVIDER || "").trim();
|
|
314
|
+
if (provider && !["deepseek", "openai", "qwen", "venice", "mock"].includes(provider)) {
|
|
315
|
+
return { allowed: false, reason: `Unknown JSON specialist provider: ${provider}`, category: "json-specialist" };
|
|
316
|
+
}
|
|
317
|
+
const payloadBytes = Buffer.byteLength(
|
|
318
|
+
[
|
|
319
|
+
task,
|
|
320
|
+
args.instructions,
|
|
321
|
+
args.requirements,
|
|
322
|
+
args.context,
|
|
323
|
+
args.inputText,
|
|
324
|
+
args.source,
|
|
325
|
+
args.content,
|
|
326
|
+
args.schemaJson,
|
|
327
|
+
args.inputJson ? JSON.stringify(args.inputJson) : "",
|
|
328
|
+
args.schema ? JSON.stringify(args.schema) : "",
|
|
329
|
+
]
|
|
330
|
+
.filter(Boolean)
|
|
331
|
+
.join("\n"),
|
|
332
|
+
"utf8"
|
|
333
|
+
);
|
|
334
|
+
if (payloadBytes > 220_000) {
|
|
335
|
+
return {
|
|
336
|
+
allowed: false,
|
|
337
|
+
reason: "JSON specialist payload is too large. Split the source into smaller independent items or save inputs to files and pass a focused excerpt.",
|
|
338
|
+
category: "json-specialist",
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
return { allowed: true };
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (toolName === "json_specialist_batch") {
|
|
345
|
+
const items = Array.isArray(args.items) ? args.items : [];
|
|
346
|
+
if (items.length === 0) return { allowed: false, reason: "JSON specialist batch requires items.", category: "json-specialist" };
|
|
347
|
+
if (items.length > 32) return { allowed: false, reason: "JSON specialist batch is limited to 32 items per tool call.", category: "json-specialist" };
|
|
348
|
+
const concurrency = Number(args.concurrency || 4);
|
|
349
|
+
if (Number.isFinite(concurrency) && concurrency > 16) {
|
|
350
|
+
return { allowed: false, reason: "JSON specialist batch concurrency is limited to 16.", category: "json-specialist" };
|
|
351
|
+
}
|
|
352
|
+
const provider = String(args.provider || args.defaults?.provider || process.env.AGINTI_JSON_PROVIDER || "").trim();
|
|
353
|
+
if (provider && !["deepseek", "openai", "qwen", "venice", "mock"].includes(provider)) {
|
|
354
|
+
return { allowed: false, reason: `Unknown JSON specialist provider: ${provider}`, category: "json-specialist" };
|
|
355
|
+
}
|
|
356
|
+
const payloadBytes = Buffer.byteLength(JSON.stringify({ defaults: args.defaults || {}, items }), "utf8");
|
|
357
|
+
if (payloadBytes > 360_000) {
|
|
358
|
+
return {
|
|
359
|
+
allowed: false,
|
|
360
|
+
reason: "JSON specialist batch payload is too large. Use fewer items or smaller chunk text per call.",
|
|
361
|
+
category: "json-specialist",
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
return { allowed: true };
|
|
365
|
+
}
|
|
366
|
+
|
|
307
367
|
if (toolName === "generate_image") {
|
|
308
368
|
if (!config.allowAuxiliaryTools) {
|
|
309
369
|
return { allowed: false, reason: "Auxiliary tools are disabled for this run.", category: "auxiliary-tools" };
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { createChatCompletion, createClient } from "./model-client.js";
|
|
3
|
+
import { getProviderDefaults } from "./model-routing.js";
|
|
4
|
+
import { redactSensitiveText, redactValue } from "./redaction.js";
|
|
5
|
+
|
|
6
|
+
const MAX_INLINE_PREVIEW = 1600;
|
|
7
|
+
const JSON_PROVIDERS = new Set(["openai", "deepseek", "qwen", "venice", "mock"]);
|
|
8
|
+
|
|
9
|
+
function compact(value = "", limit = MAX_INLINE_PREVIEW) {
|
|
10
|
+
const text = redactSensitiveText(String(value || "").trim());
|
|
11
|
+
if (text.length <= limit) return text;
|
|
12
|
+
return `${text.slice(0, Math.max(0, limit - 24))} ... [truncated]`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function parseJsonValue(content = "") {
|
|
16
|
+
const text = String(content || "").trim();
|
|
17
|
+
if (!text) return { ok: false, error: "empty JSON response" };
|
|
18
|
+
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
19
|
+
const candidates = [fenced?.[1], text].filter(Boolean);
|
|
20
|
+
for (const candidate of candidates) {
|
|
21
|
+
try {
|
|
22
|
+
return { ok: true, value: JSON.parse(candidate.trim()) };
|
|
23
|
+
} catch {
|
|
24
|
+
// Try a balanced excerpt below.
|
|
25
|
+
}
|
|
26
|
+
const starts = ["{", "["]
|
|
27
|
+
.map((char) => ({ char, index: candidate.indexOf(char) }))
|
|
28
|
+
.filter((item) => item.index >= 0)
|
|
29
|
+
.sort((a, b) => a.index - b.index);
|
|
30
|
+
for (const start of starts) {
|
|
31
|
+
const close = start.char === "{" ? "}" : "]";
|
|
32
|
+
const end = candidate.lastIndexOf(close);
|
|
33
|
+
if (end <= start.index) continue;
|
|
34
|
+
try {
|
|
35
|
+
return { ok: true, value: JSON.parse(candidate.slice(start.index, end + 1)) };
|
|
36
|
+
} catch {
|
|
37
|
+
// Keep looking.
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return { ok: false, error: "response was not parseable JSON" };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function normalizeSchema(raw) {
|
|
45
|
+
let schema = raw;
|
|
46
|
+
let name = "";
|
|
47
|
+
let strict = true;
|
|
48
|
+
if (typeof schema === "string" && schema.trim()) {
|
|
49
|
+
schema = JSON.parse(schema);
|
|
50
|
+
}
|
|
51
|
+
if (schema?.schema && typeof schema.schema === "object" && !schema.type) {
|
|
52
|
+
name = String(schema.name || "").trim();
|
|
53
|
+
strict = schema.strict !== false;
|
|
54
|
+
schema = schema.schema;
|
|
55
|
+
}
|
|
56
|
+
if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
|
|
57
|
+
throw new Error("JSON specialist requires a JSON Schema object.");
|
|
58
|
+
}
|
|
59
|
+
return { schema, name, strict };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function normalizeInputValue(args = {}) {
|
|
63
|
+
if (args.inputJson !== undefined) return redactValue(args.inputJson);
|
|
64
|
+
if (args.input !== undefined) return redactValue(args.input);
|
|
65
|
+
const text = args.inputText ?? args.source ?? args.content ?? "";
|
|
66
|
+
return redactSensitiveText(String(text || ""));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function normalizeResponseFormat(value = "") {
|
|
70
|
+
const normalized = String(value || "auto").trim().toLowerCase();
|
|
71
|
+
if (["auto", "json_schema", "json_object", "prompt"].includes(normalized)) return normalized;
|
|
72
|
+
return "auto";
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function normalizeJsonRequest(args = {}) {
|
|
76
|
+
const normalizedSchema = normalizeSchema(args.schemaJson || args.schema);
|
|
77
|
+
const task = redactSensitiveText(String(args.task || args.prompt || "").trim());
|
|
78
|
+
const instructions = redactSensitiveText(String(args.instructions || args.requirements || "").trim());
|
|
79
|
+
const context = redactSensitiveText(String(args.context || "").trim());
|
|
80
|
+
const temperature = Number.isFinite(Number(args.temperature)) ? Math.min(Math.max(Number(args.temperature), 0), 1.2) : 0;
|
|
81
|
+
const maxTokens = Number.isFinite(Number(args.maxTokens)) ? Math.max(256, Math.floor(Number(args.maxTokens))) : 4096;
|
|
82
|
+
const provider = String(args.provider || process.env.AGINTI_JSON_PROVIDER || "").trim();
|
|
83
|
+
const model = String(args.model || process.env.AGINTI_JSON_MODEL || "").trim();
|
|
84
|
+
const schemaName = String(args.schemaName || normalizedSchema.name || "aginti_structured_output")
|
|
85
|
+
.trim()
|
|
86
|
+
.replace(/[^A-Za-z0-9_-]/g, "_")
|
|
87
|
+
.slice(0, 64) || "aginti_structured_output";
|
|
88
|
+
return {
|
|
89
|
+
task,
|
|
90
|
+
instructions,
|
|
91
|
+
context,
|
|
92
|
+
input: normalizeInputValue(args),
|
|
93
|
+
schema: normalizedSchema.schema,
|
|
94
|
+
schemaName,
|
|
95
|
+
strict: args.strict === undefined ? normalizedSchema.strict : args.strict !== false,
|
|
96
|
+
responseFormat: normalizeResponseFormat(args.responseFormat),
|
|
97
|
+
fallbackOnInvalid: args.fallbackOnInvalid !== false,
|
|
98
|
+
temperature,
|
|
99
|
+
maxTokens,
|
|
100
|
+
provider,
|
|
101
|
+
model,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function jsonSystemPrompt() {
|
|
106
|
+
return [
|
|
107
|
+
"You are the isolated AgInTiFlow JSON Specialist.",
|
|
108
|
+
"You transform only the supplied task, input, and schema into structured JSON.",
|
|
109
|
+
"You do not know or discuss AgInTiFlow internals, shell tools, browser tools, file policies, planning, package installs, or execution constraints.",
|
|
110
|
+
"Never include commentary, markdown fences, prose explanations, or partial objects.",
|
|
111
|
+
"Return one JSON value that satisfies the provided schema.",
|
|
112
|
+
].join(" ");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function jsonUserPrompt(request, attempt) {
|
|
116
|
+
return JSON.stringify(
|
|
117
|
+
{
|
|
118
|
+
boundary:
|
|
119
|
+
"This is the complete context visible to the JSON specialist. Ignore absent agent/runtime details. Produce only schema-valid JSON.",
|
|
120
|
+
task: request.task,
|
|
121
|
+
instructions: request.instructions,
|
|
122
|
+
context: request.context,
|
|
123
|
+
input: request.input,
|
|
124
|
+
json_schema: request.schema,
|
|
125
|
+
output_contract: {
|
|
126
|
+
mode: attempt,
|
|
127
|
+
strict: request.strict,
|
|
128
|
+
requirement: "Return exactly one JSON value matching json_schema. Do not wrap it in markdown.",
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
null,
|
|
132
|
+
2
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function allowedTypes(schema) {
|
|
137
|
+
if (!schema || schema.type === undefined) return [];
|
|
138
|
+
return Array.isArray(schema.type) ? schema.type : [schema.type];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function typeMatches(value, type) {
|
|
142
|
+
if (type === "array") return Array.isArray(value);
|
|
143
|
+
if (type === "object") return value && typeof value === "object" && !Array.isArray(value);
|
|
144
|
+
if (type === "integer") return Number.isInteger(value);
|
|
145
|
+
if (type === "number") return typeof value === "number" && Number.isFinite(value);
|
|
146
|
+
if (type === "string") return typeof value === "string";
|
|
147
|
+
if (type === "boolean") return typeof value === "boolean";
|
|
148
|
+
if (type === "null") return value === null;
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function validateSchema(value, schema, path = "$") {
|
|
153
|
+
if (!schema || typeof schema !== "object") return [];
|
|
154
|
+
if (Array.isArray(schema.anyOf) && schema.anyOf.length > 0) {
|
|
155
|
+
const variants = schema.anyOf.map((variant) => validateSchema(value, variant, path));
|
|
156
|
+
return variants.some((errs) => errs.length === 0) ? [] : [`${path}: did not match anyOf`];
|
|
157
|
+
}
|
|
158
|
+
if (Array.isArray(schema.oneOf) && schema.oneOf.length > 0) {
|
|
159
|
+
const matches = schema.oneOf.filter((variant) => validateSchema(value, variant, path).length === 0).length;
|
|
160
|
+
return matches === 1 ? [] : [`${path}: did not match exactly one oneOf variant`];
|
|
161
|
+
}
|
|
162
|
+
const errors = [];
|
|
163
|
+
if (schema.const !== undefined && JSON.stringify(value) !== JSON.stringify(schema.const)) {
|
|
164
|
+
errors.push(`${path}: expected const ${JSON.stringify(schema.const)}`);
|
|
165
|
+
}
|
|
166
|
+
if (Array.isArray(schema.enum) && !schema.enum.some((item) => JSON.stringify(item) === JSON.stringify(value))) {
|
|
167
|
+
errors.push(`${path}: value is not in enum`);
|
|
168
|
+
}
|
|
169
|
+
const types = allowedTypes(schema);
|
|
170
|
+
if (types.length > 0 && !types.some((type) => typeMatches(value, type))) {
|
|
171
|
+
errors.push(`${path}: expected type ${types.join("|")}`);
|
|
172
|
+
return errors;
|
|
173
|
+
}
|
|
174
|
+
if (Array.isArray(value)) {
|
|
175
|
+
if (Number.isFinite(schema.minItems) && value.length < schema.minItems) errors.push(`${path}: fewer than minItems`);
|
|
176
|
+
if (Number.isFinite(schema.maxItems) && value.length > schema.maxItems) errors.push(`${path}: more than maxItems`);
|
|
177
|
+
if (schema.items) {
|
|
178
|
+
value.forEach((item, index) => errors.push(...validateSchema(item, schema.items, `${path}[${index}]`)));
|
|
179
|
+
}
|
|
180
|
+
} else if (value && typeof value === "object") {
|
|
181
|
+
const properties = schema.properties && typeof schema.properties === "object" ? schema.properties : {};
|
|
182
|
+
for (const key of schema.required || []) {
|
|
183
|
+
if (!(key in value)) errors.push(`${path}.${key}: required property missing`);
|
|
184
|
+
}
|
|
185
|
+
for (const [key, child] of Object.entries(properties)) {
|
|
186
|
+
if (key in value) errors.push(...validateSchema(value[key], child, `${path}.${key}`));
|
|
187
|
+
}
|
|
188
|
+
if (schema.additionalProperties === false) {
|
|
189
|
+
for (const key of Object.keys(value)) {
|
|
190
|
+
if (!(key in properties)) errors.push(`${path}.${key}: additional property not allowed`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return errors;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function mockValueForSchema(schema) {
|
|
198
|
+
if (!schema || typeof schema !== "object") return {};
|
|
199
|
+
if (schema.const !== undefined) return schema.const;
|
|
200
|
+
if (Array.isArray(schema.enum) && schema.enum.length > 0) return schema.enum[0];
|
|
201
|
+
const type = allowedTypes(schema)[0] || (schema.properties ? "object" : "string");
|
|
202
|
+
if (type === "string") return "mock";
|
|
203
|
+
if (type === "integer") return 1;
|
|
204
|
+
if (type === "number") return 1;
|
|
205
|
+
if (type === "boolean") return true;
|
|
206
|
+
if (type === "null") return null;
|
|
207
|
+
if (type === "array") {
|
|
208
|
+
const count = Number.isFinite(schema.minItems) ? Math.max(1, schema.minItems) : 1;
|
|
209
|
+
return Array.from({ length: count }, () => mockValueForSchema(schema.items || { type: "string" }));
|
|
210
|
+
}
|
|
211
|
+
const result = {};
|
|
212
|
+
const properties = schema.properties && typeof schema.properties === "object" ? schema.properties : {};
|
|
213
|
+
const keys = new Set([...(schema.required || []), ...Object.keys(properties).slice(0, 6)]);
|
|
214
|
+
for (const key of keys) result[key] = mockValueForSchema(properties[key] || { type: "string" });
|
|
215
|
+
return result;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function attemptsForRequest(request, provider) {
|
|
219
|
+
if (request.responseFormat === "prompt") return ["prompt"];
|
|
220
|
+
if (request.responseFormat === "json_schema") return ["json_schema", "prompt"];
|
|
221
|
+
if (request.responseFormat === "json_object") return ["json_object", "prompt"];
|
|
222
|
+
if (["openai", "deepseek", "qwen"].includes(provider)) return ["json_schema", "json_object", "prompt"];
|
|
223
|
+
return ["json_object", "prompt"];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function responseFormatForAttempt(request, attempt) {
|
|
227
|
+
if (attempt === "json_schema") {
|
|
228
|
+
return {
|
|
229
|
+
type: "json_schema",
|
|
230
|
+
json_schema: {
|
|
231
|
+
name: request.schemaName,
|
|
232
|
+
strict: request.strict,
|
|
233
|
+
schema: request.schema,
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
if (attempt === "json_object") return { type: "json_object" };
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function errorLooksLikeUnsupportedResponseFormat(error) {
|
|
242
|
+
const message = [error?.message, error?.error?.message, error?.response?.data?.error?.message]
|
|
243
|
+
.filter(Boolean)
|
|
244
|
+
.join(" ");
|
|
245
|
+
return /response_format|json_schema|json_object|unsupported|invalid request|400/i.test(message);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function parsedRawPreview(rawContent = "") {
|
|
249
|
+
return rawContent ? compact(rawContent, 1200) : "";
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export async function runJsonSpecialist(args = {}, config = {}, store = null) {
|
|
253
|
+
let request;
|
|
254
|
+
try {
|
|
255
|
+
request = normalizeJsonRequest(args);
|
|
256
|
+
} catch (error) {
|
|
257
|
+
return {
|
|
258
|
+
ok: false,
|
|
259
|
+
toolName: "json_specialist",
|
|
260
|
+
reason: redactSensitiveText(error instanceof Error ? error.message : String(error)),
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
if (!request.task) {
|
|
264
|
+
return {
|
|
265
|
+
ok: false,
|
|
266
|
+
toolName: "json_specialist",
|
|
267
|
+
reason: "task is required.",
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const startedAt = new Date().toISOString();
|
|
272
|
+
const requestFingerprint = crypto.createHash("sha256").update(JSON.stringify(redactValue(request))).digest("hex");
|
|
273
|
+
let model = request.model || config.model || "";
|
|
274
|
+
let provider = request.provider || config.provider || "";
|
|
275
|
+
let rawContent = "";
|
|
276
|
+
const attemptNotes = [];
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
let result;
|
|
280
|
+
let validationErrors = [];
|
|
281
|
+
let usedResponseFormat = "mock";
|
|
282
|
+
if (config.provider === "mock" || provider === "mock") {
|
|
283
|
+
provider = "mock";
|
|
284
|
+
model = request.model || config.model || "mock-agent";
|
|
285
|
+
result = mockValueForSchema(request.schema);
|
|
286
|
+
validationErrors = validateSchema(result, request.schema);
|
|
287
|
+
} else {
|
|
288
|
+
if (provider && !JSON_PROVIDERS.has(provider)) {
|
|
289
|
+
throw new Error(`Unknown JSON specialist provider: ${provider}`);
|
|
290
|
+
}
|
|
291
|
+
const providerDefaults = request.provider ? getProviderDefaults(request.provider) : {};
|
|
292
|
+
const jsonConfig = {
|
|
293
|
+
...config,
|
|
294
|
+
...providerDefaults,
|
|
295
|
+
provider: provider || config.provider,
|
|
296
|
+
model: model || providerDefaults.model || config.model,
|
|
297
|
+
};
|
|
298
|
+
model = jsonConfig.model;
|
|
299
|
+
provider = jsonConfig.provider;
|
|
300
|
+
const client = createClient(jsonConfig);
|
|
301
|
+
const attempts = attemptsForRequest(request, provider);
|
|
302
|
+
for (const attempt of attempts) {
|
|
303
|
+
const responseFormat = responseFormatForAttempt(request, attempt);
|
|
304
|
+
const payload = {
|
|
305
|
+
model: jsonConfig.model,
|
|
306
|
+
temperature: request.temperature,
|
|
307
|
+
max_tokens: request.maxTokens,
|
|
308
|
+
messages: [
|
|
309
|
+
{ role: "system", content: jsonSystemPrompt() },
|
|
310
|
+
{ role: "user", content: jsonUserPrompt(request, attempt) },
|
|
311
|
+
],
|
|
312
|
+
...(responseFormat ? { response_format: responseFormat } : {}),
|
|
313
|
+
};
|
|
314
|
+
try {
|
|
315
|
+
const response = await createChatCompletion(client, payload, jsonConfig, `json specialist ${attempt} request`);
|
|
316
|
+
rawContent = response.choices[0]?.message?.content || "";
|
|
317
|
+
const parsed = parseJsonValue(rawContent);
|
|
318
|
+
if (!parsed.ok) {
|
|
319
|
+
attemptNotes.push(`${attempt}: ${parsed.error}`);
|
|
320
|
+
if (request.fallbackOnInvalid && attempt !== attempts.at(-1)) continue;
|
|
321
|
+
throw new Error(parsed.error);
|
|
322
|
+
}
|
|
323
|
+
const errors = validateSchema(parsed.value, request.schema);
|
|
324
|
+
if (errors.length > 0) {
|
|
325
|
+
attemptNotes.push(`${attempt}: ${errors.slice(0, 4).join("; ")}`);
|
|
326
|
+
if (request.fallbackOnInvalid && attempt !== attempts.at(-1)) continue;
|
|
327
|
+
}
|
|
328
|
+
result = parsed.value;
|
|
329
|
+
validationErrors = errors;
|
|
330
|
+
usedResponseFormat = attempt;
|
|
331
|
+
break;
|
|
332
|
+
} catch (error) {
|
|
333
|
+
const message = redactSensitiveText(error instanceof Error ? error.message : String(error));
|
|
334
|
+
attemptNotes.push(`${attempt}: ${message}`);
|
|
335
|
+
if (attempt !== attempts.at(-1) && (request.fallbackOnInvalid || errorLooksLikeUnsupportedResponseFormat(error))) continue;
|
|
336
|
+
throw error;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const artifact = {
|
|
342
|
+
version: 1,
|
|
343
|
+
generatedAt: new Date().toISOString(),
|
|
344
|
+
startedAt,
|
|
345
|
+
provider,
|
|
346
|
+
model,
|
|
347
|
+
responseFormat: usedResponseFormat,
|
|
348
|
+
requestFingerprint,
|
|
349
|
+
request: redactValue(request),
|
|
350
|
+
result: redactValue(result),
|
|
351
|
+
validationErrors,
|
|
352
|
+
attemptNotes,
|
|
353
|
+
rawPreview: parsedRawPreview(rawContent),
|
|
354
|
+
};
|
|
355
|
+
const artifactPath = store
|
|
356
|
+
? await store.saveJsonArtifact(`json-specialist-${Date.now()}.json`, artifact).catch(() => "")
|
|
357
|
+
: "";
|
|
358
|
+
return {
|
|
359
|
+
ok: validationErrors.length === 0,
|
|
360
|
+
toolName: "json_specialist",
|
|
361
|
+
provider,
|
|
362
|
+
model,
|
|
363
|
+
responseFormat: usedResponseFormat,
|
|
364
|
+
args: {
|
|
365
|
+
task: request.task,
|
|
366
|
+
schemaName: request.schemaName,
|
|
367
|
+
responseFormat: request.responseFormat,
|
|
368
|
+
provider: request.provider,
|
|
369
|
+
requestFingerprint,
|
|
370
|
+
},
|
|
371
|
+
artifactPath,
|
|
372
|
+
result,
|
|
373
|
+
validationErrors,
|
|
374
|
+
attemptNotes,
|
|
375
|
+
rawPreview: parsedRawPreview(rawContent),
|
|
376
|
+
};
|
|
377
|
+
} catch (error) {
|
|
378
|
+
return {
|
|
379
|
+
ok: false,
|
|
380
|
+
toolName: "json_specialist",
|
|
381
|
+
provider,
|
|
382
|
+
model,
|
|
383
|
+
args: {
|
|
384
|
+
task: request.task,
|
|
385
|
+
schemaName: request.schemaName,
|
|
386
|
+
responseFormat: request.responseFormat,
|
|
387
|
+
provider: request.provider,
|
|
388
|
+
requestFingerprint,
|
|
389
|
+
},
|
|
390
|
+
error: redactSensitiveText(error instanceof Error ? error.message : String(error)),
|
|
391
|
+
attemptNotes,
|
|
392
|
+
rawPreview: parsedRawPreview(rawContent),
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export async function runJsonSpecialistBatch(tasks = [], options = {}, config = {}, store = null) {
|
|
398
|
+
const items = Array.isArray(tasks) ? tasks : [];
|
|
399
|
+
const concurrency = Math.min(Math.max(Number(options.concurrency) || 4, 1), 32);
|
|
400
|
+
const results = new Array(items.length);
|
|
401
|
+
let nextIndex = 0;
|
|
402
|
+
|
|
403
|
+
async function worker() {
|
|
404
|
+
while (nextIndex < items.length) {
|
|
405
|
+
const index = nextIndex;
|
|
406
|
+
nextIndex += 1;
|
|
407
|
+
const task = items[index] || {};
|
|
408
|
+
results[index] = await runJsonSpecialist({ ...options.defaults, ...task }, config, store);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => worker()));
|
|
413
|
+
return {
|
|
414
|
+
ok: results.every((item) => item?.ok),
|
|
415
|
+
toolName: "json_specialist_batch",
|
|
416
|
+
count: results.length,
|
|
417
|
+
succeeded: results.filter((item) => item?.ok).length,
|
|
418
|
+
failed: results.filter((item) => !item?.ok).length,
|
|
419
|
+
concurrency,
|
|
420
|
+
results,
|
|
421
|
+
};
|
|
422
|
+
}
|
package/src/model-client.js
CHANGED
|
@@ -567,6 +567,27 @@ function mockWritingSpecialistToolForGoal(goal = "", taskProfile = "") {
|
|
|
567
567
|
});
|
|
568
568
|
}
|
|
569
569
|
|
|
570
|
+
function mockJsonSpecialistToolForGoal(goal = "") {
|
|
571
|
+
const text = String(goal || "");
|
|
572
|
+
if (!/\b(json|schema|structured output|extract structured|valid object|valid array)\b/i.test(text)) return null;
|
|
573
|
+
return mockToolCall("json_specialist", {
|
|
574
|
+
task: text.replace(/\s+/g, " ").slice(0, 800) || "Return structured JSON.",
|
|
575
|
+
schemaName: "mock_structured_output",
|
|
576
|
+
schema: {
|
|
577
|
+
type: "object",
|
|
578
|
+
properties: {
|
|
579
|
+
summary: { type: "string" },
|
|
580
|
+
complete: { type: "boolean" },
|
|
581
|
+
},
|
|
582
|
+
required: ["summary", "complete"],
|
|
583
|
+
additionalProperties: false,
|
|
584
|
+
},
|
|
585
|
+
inputText: text.slice(0, 1200),
|
|
586
|
+
responseFormat: "prompt",
|
|
587
|
+
provider: "mock",
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
|
|
570
591
|
function mockPreviewToolForGoal(goal = "") {
|
|
571
592
|
const text = String(goal).toLowerCase();
|
|
572
593
|
if (!/(open|preview|view|browser|website|web\s*site)/.test(text)) return null;
|
|
@@ -676,6 +697,7 @@ export async function createPlan(client, config, state) {
|
|
|
676
697
|
? "web_search is available for lightweight snippets. web_research is available for auditable sourced research with persisted artifacts; use mode=snippets by default and mode=openai only when hosted OpenAI web research is needed and configured. Prefer these tools over opening a search engine in the browser."
|
|
677
698
|
: "web_search is disabled for this run.",
|
|
678
699
|
"For substantial writing work such as novels, chapters, books, scripts, essays, LaTeX manuscripts, or research-paper prose, plan to call writing_specialist with only the writing brief/canon/style/draft context. The main agent should handle files, citations, checks, and Markdown/LaTeX/Final Draft formatting after the isolated writing draft returns.",
|
|
700
|
+
"For repetitive schema-bound extraction, annotation, conversion, or validation tasks, use json_specialist with only the task, input, schema, and focused instructions. It calls the model directly for strict JSON, tries provider-native structured output when supported, and keeps agent/runtime/tool context out of the specialist prompt.",
|
|
679
701
|
config.allowFileTools
|
|
680
702
|
? "read_image is available for workspace-local or allowed remote screenshots/images using OpenAI vision when OPENAI_API_KEY is configured. It returns typed visual observations and persists a perception artifact; if credentials are missing, report the blocker instead of guessing from the filename."
|
|
681
703
|
: "",
|
|
@@ -729,6 +751,66 @@ export async function requestNextStep(client, config, messages) {
|
|
|
729
751
|
},
|
|
730
752
|
},
|
|
731
753
|
},
|
|
754
|
+
{
|
|
755
|
+
type: "function",
|
|
756
|
+
function: {
|
|
757
|
+
name: "json_specialist",
|
|
758
|
+
description:
|
|
759
|
+
"Call an isolated schema-only LLM context for strict JSON extraction, annotation, classification, conversion, or validation. Pass only the task, input, schema, and focused instructions; do not pass shell/browser/file policy, agent runtime, or broad planning context. The tool tries provider-native structured JSON where supported and falls back to prompt-and-validate parsing.",
|
|
760
|
+
parameters: {
|
|
761
|
+
type: "object",
|
|
762
|
+
properties: {
|
|
763
|
+
task: { type: "string", description: "Focused JSON task. Required." },
|
|
764
|
+
instructions: { type: "string", description: "Additional schema-specific rules or quality constraints." },
|
|
765
|
+
context: { type: "string", description: "Minimal domain context needed for the JSON transformation." },
|
|
766
|
+
inputText: { type: "string", description: "Source text or serialized input to transform." },
|
|
767
|
+
inputJson: { type: "object", description: "Source object to transform.", additionalProperties: true },
|
|
768
|
+
schema: { type: "object", description: "JSON Schema object for the expected output.", additionalProperties: true },
|
|
769
|
+
schemaJson: { type: "string", description: "JSON-stringified schema alternative when schema is easier to pass as text." },
|
|
770
|
+
schemaName: { type: "string", description: "Short schema name for provider-native structured output." },
|
|
771
|
+
responseFormat: {
|
|
772
|
+
type: "string",
|
|
773
|
+
enum: ["auto", "json_schema", "json_object", "prompt"],
|
|
774
|
+
description: "Native structured-output preference. auto tries native JSON schema/object then prompt fallback.",
|
|
775
|
+
},
|
|
776
|
+
fallbackOnInvalid: { type: "boolean", description: "Try a weaker fallback if native structured output is unsupported or invalid. Defaults true." },
|
|
777
|
+
strict: { type: "boolean", description: "Use strict provider schema mode where supported. Defaults true." },
|
|
778
|
+
temperature: { type: "number", description: "Defaults to 0 for deterministic structured data." },
|
|
779
|
+
maxTokens: { type: "integer", description: "Maximum completion tokens for the JSON specialist call." },
|
|
780
|
+
provider: {
|
|
781
|
+
type: "string",
|
|
782
|
+
enum: ["deepseek", "openai", "qwen", "venice", "mock"],
|
|
783
|
+
description: "Optional provider override. Defaults to AGINTI_JSON_PROVIDER or current provider.",
|
|
784
|
+
},
|
|
785
|
+
model: { type: "string", description: "Optional model override. Defaults to AGINTI_JSON_MODEL or current model." },
|
|
786
|
+
},
|
|
787
|
+
required: ["task"],
|
|
788
|
+
additionalProperties: false,
|
|
789
|
+
},
|
|
790
|
+
},
|
|
791
|
+
},
|
|
792
|
+
{
|
|
793
|
+
type: "function",
|
|
794
|
+
function: {
|
|
795
|
+
name: "json_specialist_batch",
|
|
796
|
+
description:
|
|
797
|
+
"Run many isolated json_specialist requests concurrently for chunked structured-data work. Each item is one independent schema-bound task. Use only for independent items with no shared writes or ordering dependency.",
|
|
798
|
+
parameters: {
|
|
799
|
+
type: "object",
|
|
800
|
+
properties: {
|
|
801
|
+
concurrency: { type: "integer", description: "Parallel request count, clamped by runtime guardrails." },
|
|
802
|
+
defaults: { type: "object", description: "Default json_specialist arguments applied to every item.", additionalProperties: true },
|
|
803
|
+
items: {
|
|
804
|
+
type: "array",
|
|
805
|
+
description: "Independent json_specialist argument objects.",
|
|
806
|
+
items: { type: "object", additionalProperties: true },
|
|
807
|
+
},
|
|
808
|
+
},
|
|
809
|
+
required: ["items"],
|
|
810
|
+
additionalProperties: false,
|
|
811
|
+
},
|
|
812
|
+
},
|
|
813
|
+
},
|
|
732
814
|
{
|
|
733
815
|
type: "function",
|
|
734
816
|
function: {
|
|
@@ -1393,6 +1475,11 @@ export async function requestNextStep(client, config, messages) {
|
|
|
1393
1475
|
}
|
|
1394
1476
|
}
|
|
1395
1477
|
|
|
1478
|
+
const jsonTool = mockJsonSpecialistToolForGoal(config.goal);
|
|
1479
|
+
if (jsonTool) {
|
|
1480
|
+
return mockChatResponse("Mock mode will exercise the isolated JSON specialist.", [jsonTool]);
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1396
1483
|
const writingTool = mockWritingSpecialistToolForGoal(config.goal, config.taskProfile);
|
|
1397
1484
|
if (writingTool) {
|
|
1398
1485
|
return mockChatResponse("Mock mode will exercise the isolated writing specialist before file or format work.", [writingTool]);
|
package/src/task-profiles.js
CHANGED
|
@@ -132,6 +132,13 @@ export const TASK_PROFILES = {
|
|
|
132
132
|
"Bias toward supervising another agent or long-running task instead of doing the target work directly. Define acceptance criteria, give the student agent normal user-level prompts, monitor progress through tmux/session logs/artifacts, independently verify claims, record evidence, and convert repeated failures into reusable AgInTiFlow skills, tools, policies, tests, or profile improvements.",
|
|
133
133
|
tools: ["shell", "files", "canvas", "inspect_project", "tmux"],
|
|
134
134
|
},
|
|
135
|
+
pipeline: {
|
|
136
|
+
id: "pipeline",
|
|
137
|
+
label: "Self-healing pipeline",
|
|
138
|
+
prompt:
|
|
139
|
+
"Bias toward keeping long-running writer, reviewer, repairer, monitor, queue, ETL, batch generation, and build pipelines observable, resumable, and moving from raw inputs to final artifacts. Diagnose from status files, logs, manifests, tmux panes, heartbeats, and timestamps before intervening. Distinguish healthy waiting from stalls. Preserve valid artifacts, quarantine stale or invalid outputs, and patch project-owned scripts/prompts/validators in small reversible steps only when evidence supports it. For new pipelines, create a local contract with source manifest, stable chunks, schemas, validators, runners, checkpoint builds, and completion evidence. Choose sequential, parallel, async, or review-gated designs according to the user's request and the project's existing architecture. Verify with focused checks, checkpoint artifacts, then restart only the affected session and record the exact resume command.",
|
|
140
|
+
tools: ["inspect_project", "search_files", "read_file", "apply_patch", "shell", "tmux", "sandbox"],
|
|
141
|
+
},
|
|
135
142
|
app: {
|
|
136
143
|
id: "app",
|
|
137
144
|
label: "App builder",
|
|
@@ -391,6 +398,16 @@ const PROFILE_ALIASES = {
|
|
|
391
398
|
curriculum: "supervision",
|
|
392
399
|
selfsupervision: "supervision",
|
|
393
400
|
"self-supervision": "supervision",
|
|
401
|
+
pipeline: "pipeline",
|
|
402
|
+
pipelines: "pipeline",
|
|
403
|
+
autorepair: "pipeline",
|
|
404
|
+
"auto-repair": "pipeline",
|
|
405
|
+
"self-healing": "pipeline",
|
|
406
|
+
stuck: "pipeline",
|
|
407
|
+
stalled: "pipeline",
|
|
408
|
+
monitor: "pipeline",
|
|
409
|
+
queue: "pipeline",
|
|
410
|
+
reviewer: "pipeline",
|
|
394
411
|
cpp: "c-cpp",
|
|
395
412
|
"c++": "c-cpp",
|
|
396
413
|
clang: "c-cpp",
|
|
@@ -432,6 +449,7 @@ export function defaultMaxStepsForProfile(value = "auto") {
|
|
|
432
449
|
if (profile === "android") return 60;
|
|
433
450
|
if (profile === "latex") return 30;
|
|
434
451
|
if (profile === "supervision") return 40;
|
|
452
|
+
if (profile === "pipeline") return 44;
|
|
435
453
|
if (profile === "aaps") return 36;
|
|
436
454
|
if (["devops", "security"].includes(profile)) return 36;
|
|
437
455
|
if (
|