@yemi33/minions 0.1.1613 → 0.1.1614

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/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.1614 (2026-04-29)
4
+
5
+ ### Fixes
6
+ - recover ===ACTIONS=== JSON from fences and trailing prose (#1834) (#1837)
7
+
3
8
  ## 0.1.1613 (2026-04-28)
4
9
 
5
10
  ### Features
@@ -623,6 +623,11 @@ async function _ccDoSend(message, skipUserMsg, forceTabId) {
623
623
  if (evt.actions && evt.actions.length > 0) {
624
624
  _tagServerExecuted(evt.actions, evt.actionResults);
625
625
  for (var ai = 0; ai < evt.actions.length; ai++) { await ccExecuteAction(evt.actions[ai], activeTabId); }
626
+ } else if (evt.actionParseError) {
627
+ // Issue #1834: server saw ===ACTIONS=== but couldn't parse the JSON.
628
+ // Surface as an inline warning so the user knows actions were dropped
629
+ // (was previously silent — appeared as "actions failed" with no signal).
630
+ addMsg('system', '<div style="padding:6px 12px;font-size:11px;color:var(--red);background:var(--surface2);border-radius:6px;margin:4px 0">⚠️ Actions block emitted but JSON could not be parsed — no actions were executed. Resend or rephrase. (' + escHtml(String(evt.actionParseError).slice(0, 200)) + ')</div>', false, activeTabId);
626
631
  }
627
632
  } else if (evt.type === 'error') {
628
633
  terminalEventSeen = true;
package/dashboard.js CHANGED
@@ -758,16 +758,68 @@ function findCCActionsDelimiter(text) {
758
758
  return match.index + match[0].indexOf('===ACTIONS===');
759
759
  }
760
760
 
761
+ // Issue #1834: non-Claude runtimes (Copilot/GPT) routinely wrap the action JSON
762
+ // in ```json fences or append trailing prose ("Let me know if that helps!").
763
+ // JSON.parse on the raw segment fails silently → actions dropped, user sees
764
+ // inert text. This extractor pulls out the balanced JSON value (array or
765
+ // object) regardless of fences, leading whitespace, or trailing junk so the
766
+ // downstream parse can succeed. Returns null if no plausible JSON value is
767
+ // present (caller surfaces the failure via _actionParseError).
768
+ function _extractActionsJson(segment) {
769
+ if (!segment) return null;
770
+ let body = segment.trim();
771
+ // Strip ```json / ``` fences (open + close). The model sometimes only emits
772
+ // an opening fence (truncation), so handle both halves independently.
773
+ body = body.replace(/^```[a-zA-Z0-9_-]*\s*\r?\n?/, '').replace(/\r?\n?```\s*$/, '').trim();
774
+ if (!body) return null;
775
+ const first = body.indexOf('[');
776
+ const firstObj = body.indexOf('{');
777
+ let start = -1;
778
+ let openCh = '';
779
+ let closeCh = '';
780
+ if (first >= 0 && (firstObj < 0 || first <= firstObj)) {
781
+ start = first; openCh = '['; closeCh = ']';
782
+ } else if (firstObj >= 0) {
783
+ start = firstObj; openCh = '{'; closeCh = '}';
784
+ }
785
+ if (start < 0) return null;
786
+ let depth = 0;
787
+ let inString = false;
788
+ let escape = false;
789
+ for (let i = start; i < body.length; i++) {
790
+ const ch = body[i];
791
+ if (escape) { escape = false; continue; }
792
+ if (ch === '\\') { escape = true; continue; }
793
+ if (ch === '"') { inString = !inString; continue; }
794
+ if (inString) continue;
795
+ if (ch === openCh) depth++;
796
+ else if (ch === closeCh) {
797
+ depth--;
798
+ if (depth === 0) return body.slice(start, i + 1);
799
+ }
800
+ }
801
+ return null;
802
+ }
803
+
761
804
  function parseCCActions(text) {
762
805
  let actions = [];
763
806
  let displayText = text;
807
+ let parseError = null;
764
808
  const delimIdx = findCCActionsDelimiter(text);
765
809
  if (delimIdx >= 0) {
766
810
  displayText = text.slice(0, delimIdx).trim();
767
- try {
768
- const parsed = JSON.parse(text.slice(delimIdx + '===ACTIONS==='.length).trim());
769
- actions = Array.isArray(parsed) ? parsed : [parsed];
770
- } catch {}
811
+ const segment = text.slice(delimIdx + '===ACTIONS==='.length);
812
+ const jsonStr = _extractActionsJson(segment);
813
+ if (jsonStr) {
814
+ try {
815
+ const parsed = JSON.parse(jsonStr);
816
+ actions = Array.isArray(parsed) ? parsed : [parsed];
817
+ } catch (e) {
818
+ parseError = e.message || 'invalid JSON';
819
+ }
820
+ } else if (segment.trim()) {
821
+ parseError = 'no JSON value found after ===ACTIONS=== delimiter';
822
+ }
771
823
  }
772
824
  if (actions.length === 0) {
773
825
  const actionRegex = /`{3,}\s*action\s*\r?\n([\s\S]*?)`{3,}/g;
@@ -775,9 +827,24 @@ function parseCCActions(text) {
775
827
  while ((match = actionRegex.exec(displayText)) !== null) {
776
828
  try { actions.push(JSON.parse(match[1].trim())); } catch {}
777
829
  }
778
- if (actions.length > 0) displayText = displayText.replace(/`{3,}\s*action\s*\r?\n[\s\S]*?`{3,}\n?/g, '').trim();
830
+ if (actions.length > 0) {
831
+ displayText = displayText.replace(/`{3,}\s*action\s*\r?\n[\s\S]*?`{3,}\n?/g, '').trim();
832
+ parseError = null; // legacy fallback recovered actions
833
+ }
779
834
  }
780
- return { text: displayText, actions };
835
+ const result = { text: displayText, actions };
836
+ if (parseError && actions.length === 0) {
837
+ result._actionParseError = parseError;
838
+ // Visibility for the engine log — silent failure here previously masked issue #1834.
839
+ try {
840
+ const snippet = (text.slice(delimIdx + '===ACTIONS==='.length).trim() || '').slice(0, 200);
841
+ console.warn(`[CC] action JSON parse failed (${parseError}); raw segment: ${snippet}`);
842
+ if (typeof shared !== 'undefined' && shared && typeof shared.log === 'function') {
843
+ shared.log('warn', `CC action JSON parse failed: ${parseError} — segment: ${snippet}`);
844
+ }
845
+ } catch { /* logging is best-effort */ }
846
+ }
847
+ return result;
781
848
  }
782
849
 
783
850
  // ── /loop → create-watch safety net ──────────────────────────────────────────
@@ -4382,7 +4449,12 @@ What would you like to discuss or change? When you're happy, say "approve" and I
4382
4449
  if (parsed.actions.length > 0) {
4383
4450
  parsed.actionResults = await executeCCActions(parsed.actions);
4384
4451
  }
4385
- const reply = { ...parsed, sessionId: ccSession.sessionId, newSession: !wasResume };
4452
+ // Issue #1834: rename _actionParseError actionParseError (public field)
4453
+ // so the client can surface a warning when the model emitted ===ACTIONS===
4454
+ // but the JSON couldn't be recovered.
4455
+ const { _actionParseError, ...parsedReply } = parsed;
4456
+ const reply = { ...parsedReply, sessionId: ccSession.sessionId, newSession: !wasResume };
4457
+ if (_actionParseError) reply.actionParseError = _actionParseError;
4386
4458
  if (sessionReset) reply.sessionReset = true;
4387
4459
  return jsonReply(res, 200, reply);
4388
4460
  } finally {
@@ -4639,7 +4711,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
4639
4711
  }
4640
4712
 
4641
4713
  // Send final result with actions — execute server-side first
4642
- const { text: displayText, actions } = parseCCActions(result.text);
4714
+ const { text: displayText, actions, _actionParseError } = parseCCActions(result.text);
4643
4715
  // Safety net: detect /loop invocation and convert to create-watch
4644
4716
  const _loopWatch = _detectLoopInvocation(displayText, actions, toolUses);
4645
4717
  if (_loopWatch) {
@@ -4652,6 +4724,9 @@ What would you like to discuss or change? When you're happy, say "approve" and I
4652
4724
  actionResults = await executeCCActions(actions);
4653
4725
  }
4654
4726
  const donePayload = { type: 'done', text: displayText, actions, actionResults, sessionId: responseSessionId, newSession: !wasResume };
4727
+ // Issue #1834: surface action JSON parse failures so the UI can warn
4728
+ // instead of silently dropping. Client renders this as a small notice.
4729
+ if (_actionParseError) donePayload.actionParseError = _actionParseError;
4655
4730
  if (sessionReset) donePayload.sessionReset = true;
4656
4731
  liveState.donePayload = donePayload;
4657
4732
  if (liveState.writer) liveState.writer(donePayload);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1613",
3
+ "version": "0.1.1614",
4
4
  "description": "Multi-agent AI dev team that runs from ~/.minions/ — five autonomous agents share a single engine, dashboard, and knowledge base",
5
5
  "bin": {
6
6
  "minions": "bin/minions.js"
@@ -52,6 +52,8 @@ When in doubt, delegate. You are the dispatcher, not the worker. Agents have iso
52
52
  ## Actions
53
53
  Append actions at the END of your response. Write your response first, then `===ACTIONS===` on its own line, then a JSON array. No text after the JSON. Omit entirely if no actions needed.
54
54
 
55
+ **CRITICAL — emit RAW JSON only.** Do NOT wrap the JSON array in ```json fences, ``` fences, or any other markdown. Do NOT add commentary or "Let me know if that helps" lines after the JSON. The JSON array must start with `[` on the line immediately after `===ACTIONS===` and end with `]` as the very last character of the response. Anything else (fences, prose, trailing commas) breaks server-side action parsing and your actions will be silently dropped.
56
+
55
57
  Example:
56
58
  I'll dispatch dallas to fix that bug.
57
59