@krmxd/onegpt 0.2.2-beta → 0.2.3-beta

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": "@krmxd/onegpt",
3
- "version": "0.2.2-beta",
3
+ "version": "0.2.3-beta",
4
4
  "description": "OGPT - AI coding assistant for the terminal with a built-in local engine",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/agent.js CHANGED
@@ -10,6 +10,41 @@ const path = require("path");
10
10
  const { getConfig } = require("./config");
11
11
  const { getSharedProvider } = require("./ollama");
12
12
  const { ToolRegistry } = require("./tools");
13
+ const { spawnSync } = require("child_process");
14
+
15
+ // --- auto syntax check for AI-written code -------------------------------
16
+ const CHECKABLE_EXT = new Set([".py", ".js", ".mjs", ".cjs"]);
17
+
18
+ function _pick(binaries, args, timeoutMs) {
19
+ for (const bin of binaries) {
20
+ try {
21
+ const r = spawnSync(bin, args, { timeout: timeoutMs });
22
+ if (r.status === 0) return null;
23
+ if (r.error && r.error.code === "ENOENT") continue;
24
+ const err = (r.stderr ? r.stderr.toString() : "")
25
+ || (r.stdout ? r.stdout.toString() : "") || `exit ${r.status}`;
26
+ return err.split("\n").filter((l) => l.trim()).slice(0, 4).join("\n").slice(0, 500);
27
+ } catch { /* try next */ }
28
+ }
29
+ return null; // no interpreter available - skip silently
30
+ }
31
+
32
+ function syntaxCheckError(filePath) {
33
+ const ext = path.extname(filePath).toLowerCase();
34
+ if (!CHECKABLE_EXT.has(ext)) return null;
35
+ if (!fs.existsSync(filePath)) return null;
36
+ if (ext === ".py") {
37
+ return _pick(["python3", "python"], ["-m", "py_compile", filePath], 15000);
38
+ }
39
+ return _pick([process.execPath || "node", "node"], ["--check", filePath], 15000);
40
+ }
41
+
42
+ function autoCheckMsg(p, err) {
43
+ // Loop-break redirect style: tiny models need the exact shape again.
44
+ return "[auto-check] The file you just wrote FAILED its syntax check:\n" +
45
+ `${p}\n${err}\n` +
46
+ "Rewrite the ENTIRE file correctly NOW with write_file (bare JSON tool call), fixing every reported error. Do not explain.";
47
+ }
13
48
 
14
49
  const TOOL_RE = /\x00TOOL:([^:]*):([^:]*):(.*?)\x00/g;
15
50
  const USAGE_RE = /\x00USAGE:(.*?)\x00/;
@@ -570,6 +605,7 @@ class Agent {
570
605
  let full = "";
571
606
  let rounds = 0;
572
607
  let nudges = 0;
608
+ this._autoChecks = 0;
573
609
  let lastSig = null;
574
610
  let loopBreaks = 0;
575
611
  const doneBits = [];
@@ -633,6 +669,15 @@ class Agent {
633
669
  doneBits.push(result.output.split("\n")[0].slice(0, 90));
634
670
  const ev = codeEvent(tc, pre);
635
671
  if (ev) full += (full.trim() ? "\n\n" : "") + codeMarkdown(ev);
672
+ // Auto syntax check: broken AI-written code gets one bounce-back.
673
+ if (ev && (tc.name === "write_file" || tc.name === "edit_file")
674
+ && this._autoChecks < 2) {
675
+ const err = syntaxCheckError(ev.path);
676
+ if (err) {
677
+ this._autoChecks++;
678
+ this.history.push({ role: "user", content: autoCheckMsg(ev.path, err), ts: Date.now() / 1000 });
679
+ }
680
+ }
636
681
  }
637
682
  }
638
683
  } else {
@@ -682,6 +727,7 @@ class Agent {
682
727
  this.history.push({ role: "user", content: userInput, ts: Date.now() / 1000 });
683
728
  let rounds = 0;
684
729
  let nudges = 0;
730
+ this._autoChecks = 0;
685
731
  const tStart = Date.now();
686
732
  let ttft = 0;
687
733
  let nTokens = 0;
@@ -790,6 +836,16 @@ class Agent {
790
836
  }
791
837
  const ev = codeEvent(tc, pre);
792
838
  if (ev) yield "\n" + codeMarkdown(ev) + "\n";
839
+ // Auto syntax check: broken AI-written code gets one bounce-back.
840
+ if (ev && (tc.name === "write_file" || tc.name === "edit_file")
841
+ && this._autoChecks < 2) {
842
+ const err = syntaxCheckError(ev.path);
843
+ if (err) {
844
+ this._autoChecks++;
845
+ yield `\n[auto-check] ${path.basename(ev.path)} has a syntax error - fixing...\n`;
846
+ this.history.push({ role: "user", content: autoCheckMsg(ev.path, err), ts: Date.now() / 1000 });
847
+ }
848
+ }
793
849
  }
794
850
  }
795
851
  } else {
@@ -909,4 +965,5 @@ class Agent {
909
965
  }
910
966
  }
911
967
 
912
- module.exports = { Agent, extractJsonTools, repairToolJson, wantsAction, repliedWithoutActing, stalledHalfway };
968
+ module.exports = { Agent, extractJsonTools, repairToolJson, wantsAction,
969
+ repliedWithoutActing, stalledHalfway, syntaxCheckError, autoCheckMsg };
package/src/render.js CHANGED
@@ -275,12 +275,10 @@ class SmoothPrinter {
275
275
 
276
276
  _degrade(skipFlush) {
277
277
  this.degraded = true;
278
- if (!skipFlush && this.buf) {
279
- // Hand back the un-styled tail so nothing is lost visually.
280
- this.out.write("\n" + this.buf.slice(-4000) + "\n");
281
- } else {
282
- this.out.write("\n");
283
- }
278
+ // Do NOT reprint the buffer here - part of it is already on screen
279
+ // and repainting would duplicate it. Just mark the seam and let the
280
+ // remaining chunks stream raw.
281
+ this.out.write("\n");
284
282
  this.buf = "";
285
283
  }
286
284
 
@@ -288,7 +286,9 @@ class SmoothPrinter {
288
286
  if (this.closed) return;
289
287
  this.closed = true;
290
288
  if (this.timer) { clearTimeout(this.timer); this.timer = null; }
291
- if (!this.tty || this.degraded) return;
289
+ if (!this.tty) return;
290
+ // Always un-hide the cursor, degraded or not.
291
+ if (this.degraded) { this.out.write(`${ESC}[?25h`); return; }
292
292
  this._erase();
293
293
  const width = this.widthOf();
294
294
  this.out.write(renderMarkdown(this.buf, width) + "\n");