@lazyingart/agintiflow 0.20.192 → 0.20.193

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.20.192",
3
+ "version": "0.20.193",
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",
@@ -10,6 +10,18 @@ This note records the working publication route for AgInTiFlow so future release
10
10
 
11
11
  Latest verified release:
12
12
 
13
+ - Version: `0.20.192`
14
+ - Commit: `fe571c0`
15
+ - GitHub Actions run: `https://github.com/lazyingart/AgInTiFlow/actions/runs/27003317331`
16
+ - Workflow result: success
17
+ - npm registry check: `npm view @lazyingart/agintiflow version dist-tags.latest --registry=https://registry.npmjs.org` returned `0.20.192`.
18
+ - Temporary install verification: `npm install --prefix <tmp> -g @lazyingart/agintiflow@0.20.192` then `<tmp>/bin/aginti --version` returned `0.20.192`.
19
+ - Installed verification: `npm install -g @lazyingart/agintiflow@0.20.192` then `aginti --version` returned `0.20.192`.
20
+ - Webapp verification: `aginti webapp restart --port 3210` then `curl -fsS http://127.0.0.1:3210/health` returned `version":"0.20.192"` from the global npm package path.
21
+ - Feature verification: `npm run smoke:auth`, `npm run smoke:auxiliary-tools`, `npm run smoke:web-api`, `npm run check`, full `npm test`, and `npm pack --dry-run` passed before release; post-version-bump checks covered `npm run check`, `npm run smoke:auth`, `npm run smoke:auxiliary-tools`, and `npm pack --dry-run`.
22
+
23
+ Previous verified release:
24
+
13
25
  - Version: `0.20.190`
14
26
  - Commit: `f192f8e`
15
27
  - GitHub Actions run: `https://github.com/lazyingart/AgInTiFlow/actions/runs/26736284533`
@@ -162,6 +162,33 @@ try {
162
162
  interruptedDeepSeekState.messages.at(-1)?.content === "Continue with this new request: /review",
163
163
  "interrupted repair dropped the new user request"
164
164
  );
165
+ const workspaceToolConfig = {
166
+ commandCwd: workspace,
167
+ allowFileTools: true,
168
+ workspaceWritePolicy: "allow",
169
+ sandboxMode: "host",
170
+ };
171
+ const cdataSvgResult = await executeWorkspaceTool(
172
+ "write_file",
173
+ {
174
+ path: "figures/cdata-wrapped.svg",
175
+ content: "<![CDATA[<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"10\" height=\"10\"></svg>]]>",
176
+ },
177
+ workspaceToolConfig
178
+ );
179
+ assert(cdataSvgResult.ok === false, "CDATA-wrapped SVG write should be marked not ok");
180
+ assert(cdataSvgResult.artifactValidation?.kind === "svg", "SVG validation result missing");
181
+ assert(/CDATA/i.test(cdataSvgResult.artifactValidation.errors.join(" ")), "CDATA SVG validation did not explain the wrapper problem");
182
+ const validSvgResult = await executeWorkspaceTool(
183
+ "write_file",
184
+ {
185
+ path: "figures/valid.svg",
186
+ content: "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"10\" height=\"10\"><rect width=\"10\" height=\"10\" /></svg>",
187
+ },
188
+ workspaceToolConfig
189
+ );
190
+ assert(validSvgResult.ok === true, "valid standalone SVG write should pass");
191
+ assert(validSvgResult.artifactValidation?.ok === true, "valid SVG validation did not pass");
165
192
  const blockedBatchResult = {
166
193
  ok: false,
167
194
  blocked: true,
@@ -1237,6 +1264,8 @@ try {
1237
1264
  "resume_session_write",
1238
1265
  "resume_runtime_time_context",
1239
1266
  "virtual_workspace_path",
1267
+ "svg_cdata_validation_failure",
1268
+ "svg_standalone_validation_pass",
1240
1269
  "apply_patch",
1241
1270
  "multi_file_patch",
1242
1271
  "unified_patch",
@@ -1535,7 +1535,7 @@ export async function requestNextStep(client, config, messages) {
1535
1535
  function: {
1536
1536
  name: "write_file",
1537
1537
  description:
1538
- "Create or overwrite a small UTF-8 workspace file. Use mode=create with a descriptive non-conflicting filename for newly generated standalone content. Use mode=overwrite only when the user explicitly asks to replace/update that file, or after reading the file and the task is clearly to modify it. Secret paths, .git, node_modules writes, and outside-workspace paths are blocked. The runtime records before/after hashes and a compact diff.",
1538
+ "Create or overwrite a small UTF-8 workspace file. Use mode=create with a descriptive non-conflicting filename for newly generated standalone content. Use mode=overwrite only when the user explicitly asks to replace/update that file, or after reading the file and the task is clearly to modify it. Secret paths, .git, node_modules writes, and outside-workspace paths are blocked. The runtime records before/after hashes and a compact diff. For .svg files, the runtime reports deterministic XML/SVG validation; if that validation is not ok, repair the file before claiming it is valid.",
1539
1539
  parameters: {
1540
1540
  type: "object",
1541
1541
  properties: {
@@ -99,6 +99,95 @@ function compactPreview(value, limit = 180) {
99
99
  return text.length <= limit ? text : `${text.slice(0, limit)}...`;
100
100
  }
101
101
 
102
+ function firstXmlElementName(source = "") {
103
+ let text = String(source || "").replace(/^\uFEFF/, "").trimStart();
104
+ let changed = true;
105
+ while (changed) {
106
+ changed = false;
107
+ const xmlDeclaration = text.match(/^<\?xml\b[^>]*\?>\s*/i);
108
+ if (xmlDeclaration) {
109
+ text = text.slice(xmlDeclaration[0].length).trimStart();
110
+ changed = true;
111
+ continue;
112
+ }
113
+ const comment = text.match(/^<!--[\s\S]*?-->\s*/);
114
+ if (comment) {
115
+ text = text.slice(comment[0].length).trimStart();
116
+ changed = true;
117
+ continue;
118
+ }
119
+ const doctype = text.match(/^<!DOCTYPE\b[^>]*>\s*/i);
120
+ if (doctype) {
121
+ text = text.slice(doctype[0].length).trimStart();
122
+ changed = true;
123
+ }
124
+ }
125
+ return text.match(/^<([A-Za-z_][\w:.-]*)\b/)?.[1] || "";
126
+ }
127
+
128
+ function validateXmlTagStack(source = "") {
129
+ const stack = [];
130
+ const errors = [];
131
+ const tagPattern = /<[^>]+>/g;
132
+ for (const match of String(source || "").matchAll(tagPattern)) {
133
+ const tag = match[0];
134
+ if (/^<\?/.test(tag) || /^<!--/.test(tag) || /^<!DOCTYPE\b/i.test(tag) || /^<!\[CDATA\[/i.test(tag)) continue;
135
+ const end = tag.match(/^<\/\s*([A-Za-z_][\w:.-]*)\s*>$/);
136
+ if (end) {
137
+ const expected = stack.pop();
138
+ if (expected !== end[1]) {
139
+ errors.push(`Mismatched closing tag </${end[1]}>${expected ? `, expected </${expected}>` : " with no open tag"}.`);
140
+ }
141
+ continue;
142
+ }
143
+ const start = tag.match(/^<\s*([A-Za-z_][\w:.-]*)\b/);
144
+ if (!start) {
145
+ errors.push(`Malformed XML tag near: ${compactPreview(tag, 80)}`);
146
+ continue;
147
+ }
148
+ if (/\/\s*>$/.test(tag)) continue;
149
+ stack.push(start[1]);
150
+ }
151
+ while (stack.length) errors.push(`Unclosed XML tag <${stack.pop()}>.`);
152
+ return errors;
153
+ }
154
+
155
+ function validateSvgArtifact(relativePath, content) {
156
+ if (path.extname(String(relativePath || "")).toLowerCase() !== ".svg") return null;
157
+ const source = String(content ?? "");
158
+ const trimmed = source.replace(/^\uFEFF/, "").trim();
159
+ const errors = [];
160
+ const warnings = [];
161
+ if (!trimmed) errors.push("SVG file is empty.");
162
+ if (/^<!\[CDATA\[[\s\S]*\]\]>\s*$/i.test(trimmed)) {
163
+ errors.push("SVG is wrapped in a top-level CDATA section; a standalone .svg must start with <svg or an XML declaration.");
164
+ }
165
+ if (/^<!\[CDATA\[/i.test(trimmed) && !/^<!\[CDATA\[[\s\S]*\]\]>\s*$/i.test(trimmed)) {
166
+ errors.push("SVG starts with CDATA instead of an XML/SVG root element.");
167
+ }
168
+ const root = firstXmlElementName(trimmed);
169
+ if (!root) {
170
+ errors.push("No XML root element found.");
171
+ } else if (root !== "svg") {
172
+ errors.push(`Root element is <${root}>; expected <svg>.`);
173
+ }
174
+ const cdataOpen = (trimmed.match(/<!\[CDATA\[/gi) || []).length;
175
+ const cdataClose = (trimmed.match(/\]\]>/g) || []).length;
176
+ if (cdataOpen !== cdataClose) errors.push("CDATA open/close markers are unbalanced.");
177
+ errors.push(...validateXmlTagStack(trimmed));
178
+ if (/<text\b/i.test(trimmed) && !/(viewBox|width|height)=/i.test(trimmed)) {
179
+ warnings.push("SVG contains text but no obvious sizing/viewBox metadata; render-preview before claiming visual fit.");
180
+ }
181
+ return {
182
+ kind: "svg",
183
+ ok: errors.length === 0,
184
+ parser: "builtin-svg-xml-guard",
185
+ root: root || "",
186
+ errors,
187
+ warnings,
188
+ };
189
+ }
190
+
102
191
  function countOccurrences(text, search) {
103
192
  if (!search) return 0;
104
193
  let count = 0;
@@ -732,6 +821,7 @@ async function writeChange(target, nextContent, action, details = {}) {
732
821
  const afterBuffer = await fs.readFile(target.absolutePath);
733
822
  const afterText = afterBuffer.toString("utf8");
734
823
  const afterHash = hashBuffer(afterBuffer);
824
+ const artifactValidation = validateSvgArtifact(target.relativePath, afterText);
735
825
 
736
826
  return {
737
827
  ok: true,
@@ -744,6 +834,7 @@ async function writeChange(target, nextContent, action, details = {}) {
744
834
  created: !existed,
745
835
  diff: compactDiff(target.relativePath, beforeText, afterText),
746
836
  contentSha256: hashText(content),
837
+ ...(artifactValidation ? { artifactValidation } : {}),
747
838
  ...details,
748
839
  };
749
840
  }
@@ -784,10 +875,11 @@ async function writeFile(config, args) {
784
875
  mode,
785
876
  });
786
877
  return {
787
- ok: true,
878
+ ok: change.artifactValidation ? change.artifactValidation.ok : true,
788
879
  toolName: "write_file",
789
880
  path: target.relativePath,
790
881
  change,
882
+ ...(change.artifactValidation ? { artifactValidation: change.artifactValidation } : {}),
791
883
  };
792
884
  }
793
885
 
@@ -1060,11 +1152,12 @@ async function applyPatchDocument(config, args) {
1060
1152
  }
1061
1153
 
1062
1154
  return {
1063
- ok: true,
1155
+ ok: !changes.some((change) => change.artifactValidation && !change.artifactValidation.ok),
1064
1156
  toolName: "apply_patch",
1065
1157
  path: changes.length === 1 ? changes[0].path : "",
1066
1158
  changes,
1067
1159
  change: changes[0],
1160
+ artifactValidations: changes.map((change) => change.artifactValidation).filter(Boolean),
1068
1161
  summary: `${changes.length} file change(s) applied`,
1069
1162
  };
1070
1163
  }