@tjamescouch/gro 1.3.10 → 1.3.12
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/dist/main.js +11 -39
- package/dist/package.json +1 -1
- package/dist/stream-markers.js +25 -0
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -22,7 +22,8 @@ import { groError, asError, isGroError, errorLogFields } from "./errors.js";
|
|
|
22
22
|
import { bashToolDefinition, executeBash } from "./tools/bash.js";
|
|
23
23
|
import { agentpatchToolDefinition, executeAgentpatch } from "./tools/agentpatch.js";
|
|
24
24
|
import { groVersionToolDefinition, executeGroVersion, getGroVersion } from "./tools/version.js";
|
|
25
|
-
|
|
25
|
+
// Stream marker imports — parser disabled for now, markers pass through as visible text.
|
|
26
|
+
// import { createMarkerParser, extractMarkers } from "./stream-markers.js";
|
|
26
27
|
const VERSION = getGroVersion();
|
|
27
28
|
// ---------------------------------------------------------------------------
|
|
28
29
|
// Graceful shutdown state — module-level so signal handlers can save sessions.
|
|
@@ -520,29 +521,15 @@ async function executeTurn(driver, memory, mcp, cfg, sessionId) {
|
|
|
520
521
|
let brokeCleanly = false;
|
|
521
522
|
let idleNudges = 0;
|
|
522
523
|
for (let round = 0; round < cfg.maxToolRounds; round++) {
|
|
523
|
-
//
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
if (marker.name === "model-change") {
|
|
528
|
-
const newModel = resolveModelAlias(marker.arg);
|
|
529
|
-
Logger.info(`Stream marker: model-change '${marker.arg}' → ${newModel}`);
|
|
530
|
-
activeModel = newModel;
|
|
531
|
-
cfg.model = newModel; // persist across turns
|
|
532
|
-
memory.setModel(newModel); // persist in session metadata on save
|
|
533
|
-
}
|
|
534
|
-
else {
|
|
535
|
-
Logger.debug(`Stream marker: ${marker.name}('${marker.arg}')`);
|
|
536
|
-
}
|
|
537
|
-
},
|
|
538
|
-
});
|
|
524
|
+
// Stream markers: currently pass-through (visible in output).
|
|
525
|
+
// Model-change and other marker actions are disabled until the
|
|
526
|
+
// infrastructure is ready for hot-swapping.
|
|
527
|
+
const onToken = rawOnToken;
|
|
539
528
|
const output = await driver.chat(memory.messages(), {
|
|
540
529
|
model: activeModel,
|
|
541
530
|
tools: tools.length > 0 ? tools : undefined,
|
|
542
|
-
onToken
|
|
531
|
+
onToken,
|
|
543
532
|
});
|
|
544
|
-
// Flush any remaining buffered tokens from the marker parser
|
|
545
|
-
markerParser.flush();
|
|
546
533
|
// Track token usage for niki budget enforcement
|
|
547
534
|
if (output.usage) {
|
|
548
535
|
turnTokensIn += output.usage.inputTokens;
|
|
@@ -550,13 +537,10 @@ async function executeTurn(driver, memory, mcp, cfg, sessionId) {
|
|
|
550
537
|
// Log cumulative usage to stderr — niki parses these patterns for budget enforcement
|
|
551
538
|
process.stderr.write(`"input_tokens": ${turnTokensIn}, "output_tokens": ${turnTokensOut}\n`);
|
|
552
539
|
}
|
|
553
|
-
// Accumulate
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
// Store clean text in memory — markers are runtime directives, not conversation content.
|
|
558
|
-
// The original output.text is preserved in case we need it for debugging.
|
|
559
|
-
const assistantMsg = { role: "assistant", from: "Assistant", content: cleanText || "" };
|
|
540
|
+
// Accumulate output text
|
|
541
|
+
if (output.text)
|
|
542
|
+
finalText += output.text;
|
|
543
|
+
const assistantMsg = { role: "assistant", from: "Assistant", content: output.text || "" };
|
|
560
544
|
if (output.toolCalls.length > 0) {
|
|
561
545
|
assistantMsg.tool_calls = output.toolCalls;
|
|
562
546
|
}
|
|
@@ -686,12 +670,6 @@ async function singleShot(cfg, driver, mcp, sessionId, positionalArgs) {
|
|
|
686
670
|
// Resume existing session if requested
|
|
687
671
|
if (cfg.continueSession || cfg.resumeSession) {
|
|
688
672
|
await memory.load(sessionId);
|
|
689
|
-
const sess = loadSession(sessionId);
|
|
690
|
-
if (sess?.meta.model && sess.meta.model !== cfg.model) {
|
|
691
|
-
Logger.info(`Restoring model from session: ${cfg.model} → ${sess.meta.model}`);
|
|
692
|
-
cfg.model = sess.meta.model;
|
|
693
|
-
memory.setModel(sess.meta.model);
|
|
694
|
-
}
|
|
695
673
|
}
|
|
696
674
|
await memory.add({ role: "user", from: "User", content: prompt });
|
|
697
675
|
let text;
|
|
@@ -741,12 +719,6 @@ async function interactive(cfg, driver, mcp, sessionId) {
|
|
|
741
719
|
if (sess) {
|
|
742
720
|
const msgCount = sess.messages.filter((m) => m.role !== "system").length;
|
|
743
721
|
Logger.info(C.gray(`Resumed session ${sessionId} (${msgCount} messages)`));
|
|
744
|
-
// Restore model from session metadata (e.g. after a stream marker model-change)
|
|
745
|
-
if (sess.meta.model && sess.meta.model !== cfg.model) {
|
|
746
|
-
Logger.info(`Restoring model from session: ${cfg.model} → ${sess.meta.model}`);
|
|
747
|
-
cfg.model = sess.meta.model;
|
|
748
|
-
memory.setModel(sess.meta.model);
|
|
749
|
-
}
|
|
750
722
|
}
|
|
751
723
|
}
|
|
752
724
|
const rl = readline.createInterface({
|
package/dist/package.json
CHANGED
package/dist/stream-markers.js
CHANGED
|
@@ -27,6 +27,31 @@ import { Logger } from "./logger.js";
|
|
|
27
27
|
const MARKER_RE = /@@([a-zA-Z][a-zA-Z0-9_-]*)\((?:'([^']*)'|"([^"]*)"|([^)]*?))\)@@/g;
|
|
28
28
|
/** Partial marker detection — we might be mid-stream in a marker */
|
|
29
29
|
const PARTIAL_MARKER_RE = /@@[a-zA-Z][a-zA-Z0-9_-]*(?:\([^)]*)?$/;
|
|
30
|
+
/**
|
|
31
|
+
* Scan a string for markers, fire the handler for each, and return cleaned text.
|
|
32
|
+
* Unlike the streaming parser, this operates on a complete string (e.g. tool call arguments).
|
|
33
|
+
*/
|
|
34
|
+
export function extractMarkers(text, onMarker) {
|
|
35
|
+
let cleaned = "";
|
|
36
|
+
let lastIndex = 0;
|
|
37
|
+
const regex = new RegExp(MARKER_RE.source, "g");
|
|
38
|
+
let match;
|
|
39
|
+
while ((match = regex.exec(text)) !== null) {
|
|
40
|
+
cleaned += text.slice(lastIndex, match.index);
|
|
41
|
+
const marker = {
|
|
42
|
+
name: match[1],
|
|
43
|
+
arg: match[2] ?? match[3] ?? match[4] ?? "",
|
|
44
|
+
raw: match[0],
|
|
45
|
+
};
|
|
46
|
+
try {
|
|
47
|
+
onMarker(marker);
|
|
48
|
+
}
|
|
49
|
+
catch { /* handled by caller */ }
|
|
50
|
+
lastIndex = match.index + match[0].length;
|
|
51
|
+
}
|
|
52
|
+
cleaned += text.slice(lastIndex);
|
|
53
|
+
return cleaned;
|
|
54
|
+
}
|
|
30
55
|
export function createMarkerParser(opts) {
|
|
31
56
|
const { onMarker, onToken } = opts;
|
|
32
57
|
let buffer = "";
|