@cccarv82/freya 1.0.18 → 1.0.20

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.
Files changed (2) hide show
  1. package/cli/web.js +86 -13
  2. package/package.json +1 -1
package/cli/web.js CHANGED
@@ -182,6 +182,52 @@ function postTeamsWebhook(url, text) {
182
182
  return postJson(url, { text });
183
183
  }
184
184
 
185
+ function escapeJsonControlChars(jsonText) {
186
+ // Replace unescaped control chars inside JSON string literals with safe escapes.
187
+ // Handles Copilot outputs where newlines/tabs leak into string values.
188
+ const out = [];
189
+ let inString = false;
190
+ let esc = false;
191
+
192
+ for (let i = 0; i < jsonText.length; i++) {
193
+ const ch = jsonText[i];
194
+ const code = ch.charCodeAt(0);
195
+
196
+ if (esc) {
197
+ out.push(ch);
198
+ esc = false;
199
+ continue;
200
+ }
201
+
202
+ if (ch === '\\') {
203
+ out.push(ch);
204
+ esc = true;
205
+ continue;
206
+ }
207
+
208
+ if (ch === '"') {
209
+ out.push(ch);
210
+ inString = !inString;
211
+ continue;
212
+ }
213
+
214
+ if (inString) {
215
+ if (code === 10) { out.push('\\n'); continue; }
216
+ if (code === 13) { out.push('\\r'); continue; }
217
+ if (code === 9) { out.push('\\t'); continue; }
218
+ if (code >= 0 && code < 32) {
219
+ const hex = code.toString(16).padStart(2, '0');
220
+ out.push('\\u00' + hex);
221
+ continue;
222
+ }
223
+ }
224
+
225
+ out.push(ch);
226
+ }
227
+
228
+ return out.join('');
229
+ }
230
+
185
231
  async function publishRobust(webhookUrl, text, opts = {}) {
186
232
  const u = new URL(webhookUrl);
187
233
  const isDiscord = u.hostname.includes('discord.com') || u.hostname.includes('discordapp.com');
@@ -353,12 +399,11 @@ function buildHtml(safeDefault) {
353
399
  <div class="sideGroup">
354
400
  <div class="sideTitle">Workspace</div>
355
401
  <button class="btn sideBtn" onclick="pickDir()">Select workspace…</button>
356
- <button class="btn primary sideBtn" onclick="doInit()">Init workspace</button>
357
- <button class="btn sideBtn" onclick="doUpdate()">Update (preserve data/logs)</button>
358
- <button class="btn sideBtn" onclick="doHealth()">Health</button>
359
- <button class="btn sideBtn" onclick="doMigrate()">Migrate</button>
402
+ <button class="btn sideBtn" onclick="doUpdate()">Sync workspace</button>
403
+ <button class="btn sideBtn" onclick="doMigrate()">Migrate data</button>
360
404
  <div style="height:10px"></div>
361
- <div class="help">Dica: se você tem uma workspace antiga, use Update. Por padrão, data/logs não são sobrescritos.</div>
405
+ <div class="help"><b>Sync workspace</b>: atualiza scripts/templates/agents na pasta <code>freya</code> sem sobrescrever <code>data/</code> e <code>logs/</code>.</div>
406
+ <div class="help"><b>Migrate data</b>: ajusta formatos/schemaVersion quando uma versão nova exige.</div>
362
407
  </div>
363
408
 
364
409
  <div class="sideGroup">
@@ -793,7 +838,7 @@ async function cmdWeb({ port, dir, open, dev }) {
793
838
  ]
794
839
  };
795
840
 
796
- const prompt = `Você é o planner do sistema F.R.E.Y.A.\n\nContexto: vamos receber um input bruto do usuário e propor ações estruturadas.\nRegras: siga os arquivos de regras abaixo.\nSaída: retorne APENAS JSON válido no formato: ${JSON.stringify(schema)}\n\nREGRAS:${rulesText}\n\nINPUT DO USUÁRIO:\n${text}\n`;
841
+ const prompt = `Você é o planner do sistema F.R.E.Y.A.\n\nContexto: vamos receber um input bruto do usuário e propor ações estruturadas.\nRegras: siga os arquivos de regras abaixo.\nSaída: retorne APENAS JSON válido no formato: ${JSON.stringify(schema)}\n\nRestrições:\n- NÃO use code fences (\`\`\`)\n- NÃO inclua texto extra antes/depois do JSON\n- NÃO use quebras de linha dentro de strings (transforme em uma frase única)\n\nREGRAS:${rulesText}\n\nINPUT DO USUÁRIO:\n${text}\n`;
797
842
 
798
843
  // Prefer COPILOT_CMD if provided, otherwise try 'copilot'
799
844
  const cmd = process.env.COPILOT_CMD || 'copilot';
@@ -836,7 +881,15 @@ async function cmdWeb({ port, dir, open, dev }) {
836
881
  try {
837
882
  plan = JSON.parse(jsonText);
838
883
  } catch (e) {
839
- return safeJson(res, 400, { error: 'Plan is not valid JSON', details: e.message || String(e) });
884
+ try {
885
+ plan = JSON.parse(escapeJsonControlChars(jsonText));
886
+ } catch (e2) {
887
+ return safeJson(res, 400, {
888
+ error: 'Plan is not valid JSON',
889
+ details: (e2 && e2.message) ? e2.message : (e && e.message ? e.message : String(e)),
890
+ hint: 'O planner gerou caracteres de controle dentro de strings (ex.: quebra de linha). Reexecute o planner ou escape quebras de linha como \\n.'
891
+ });
892
+ }
840
893
  }
841
894
 
842
895
  const actions = Array.isArray(plan.actions) ? plan.actions : [];
@@ -914,15 +967,35 @@ async function cmdWeb({ port, dir, open, dev }) {
914
967
  }
915
968
 
916
969
  const jsonText = extractJson(planRaw) || planRaw;
970
+
971
+ function errorSnippet(text, pos) {
972
+ const p = Number.isFinite(pos) ? pos : 0;
973
+ const start = Math.max(0, p - 60);
974
+ const end = Math.min(text.length, p + 60);
975
+ const slice = text.slice(start, end);
976
+ const codes = Array.from(slice).map((ch) => ch.charCodeAt(0));
977
+ return { start, end, slice, codes };
978
+ }
979
+
917
980
  let plan;
918
981
  try {
919
982
  plan = JSON.parse(jsonText);
920
983
  } catch (e) {
921
- return safeJson(res, 400, {
922
- error: 'Plan is not valid JSON',
923
- details: (e && e.message) ? e.message : String(e),
924
- hint: 'O planner precisa retornar APENAS JSON. Se vier com texto extra, revise o prompt ou use apenas o bloco JSON.'
925
- });
984
+ // Attempt repair for common control-character issues
985
+ try {
986
+ plan = JSON.parse(escapeJsonControlChars(jsonText));
987
+ } catch (e2) {
988
+ return safeJson(res, 400, {
989
+ error: 'Plan is not valid JSON',
990
+ details: (e2 && e2.message) ? e2.message : ((e && e.message) ? e.message : String(e)),
991
+ hint: 'O planner gerou caracteres de controle dentro de strings (ex.: quebra de linha literal). Reexecute o planner; ou escape quebras de linha como \\n.',
992
+ snippet: (() => {
993
+ const m2 = /position (\d+)/.exec(((e2 && e2.message) ? e2.message : ((e && e.message) ? e.message : '')));
994
+ const pos = m2 ? Number(m2[1]) : NaN;
995
+ return errorSnippet(jsonText, pos);
996
+ })()
997
+ });
998
+ }
926
999
  }
927
1000
 
928
1001
  const actions = Array.isArray(plan.actions) ? plan.actions : [];
@@ -942,7 +1015,7 @@ async function cmdWeb({ port, dir, open, dev }) {
942
1015
  if (typeof blockerLog.schemaVersion !== 'number') blockerLog.schemaVersion = 1;
943
1016
 
944
1017
  function normalizeTextForKey(t) {
945
- return String(t || '').toLowerCase().replace(/s+/g, ' ').trim();
1018
+ return String(t || '').toLowerCase().replace(/\s+/g, ' ').trim();
946
1019
  }
947
1020
 
948
1021
  function sha1(text) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cccarv82/freya",
3
- "version": "1.0.18",
3
+ "version": "1.0.20",
4
4
  "description": "Personal AI Assistant with local-first persistence",
5
5
  "scripts": {
6
6
  "health": "node scripts/validate-data.js",