omnius 1.0.364 → 1.0.366

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/index.js CHANGED
@@ -17802,15 +17802,23 @@ function relationAllowed(edge, options2) {
17802
17802
  return false;
17803
17803
  return true;
17804
17804
  }
17805
+ function edgeAllowed(edge, options2, allowedSourceIds) {
17806
+ if (!relationAllowed(edge, options2))
17807
+ return false;
17808
+ if (!allowedSourceIds)
17809
+ return true;
17810
+ return !!edge.sourceEpisodeId && allowedSourceIds.has(edge.sourceEpisodeId);
17811
+ }
17805
17812
  function selectInnerGraphCandidates(graph, candidates, options2 = {}) {
17806
17813
  const minDegree = Math.max(1, Math.floor(options2.minDegree ?? 2));
17807
17814
  const candidateEpisodeIds = [...new Set(candidates.map((candidate) => candidate.episode.id).filter(Boolean))];
17815
+ const allowedSourceIds = options2.allowedSourceEpisodeIds?.length ? new Set(options2.allowedSourceEpisodeIds) : void 0;
17808
17816
  const candidateScore = new Map(candidates.map((candidate) => [candidate.episode.id, candidate.score ?? 0]));
17809
17817
  const candidateTimestamp = new Map(candidates.map((candidate) => [candidate.episode.id, candidate.episode.timestamp]));
17810
17818
  const sourceEdges = graph.currentEdgesForSourceEpisodes(candidateEpisodeIds, Math.max(1e3, candidateEpisodeIds.length * 20));
17811
17819
  const byNode = /* @__PURE__ */ new Map();
17812
17820
  for (const edge of sourceEdges) {
17813
- if (!relationAllowed(edge, options2))
17821
+ if (!edgeAllowed(edge, options2, allowedSourceIds))
17814
17822
  continue;
17815
17823
  const episodeId = edge.sourceEpisodeId;
17816
17824
  if (!episodeId || !candidateEpisodeIds.includes(episodeId))
@@ -17828,7 +17836,7 @@ function selectInnerGraphCandidates(graph, candidates, options2 = {}) {
17828
17836
  const now2 = Date.now();
17829
17837
  const results = [];
17830
17838
  for (const entry of byNode.values()) {
17831
- const allEdges = graph.currentEdges(entry.node.id).filter((edge) => relationAllowed(edge, options2));
17839
+ const allEdges = graph.currentEdges(entry.node.id).filter((edge) => edgeAllowed(edge, options2, allowedSourceIds));
17832
17840
  const degree = allEdges.length;
17833
17841
  if (degree < minDegree)
17834
17842
  continue;
@@ -17864,6 +17872,7 @@ function walkGraphFromSeed(graph, seed, options2 = {}) {
17864
17872
  const maxVisitedNodes = Math.max(1, Math.floor(options2.maxVisitedNodes ?? 64));
17865
17873
  const maxTraversedEdges = Math.max(1, Math.floor(options2.maxTraversedEdges ?? 160));
17866
17874
  const maxSourceEpisodes = Math.max(1, Math.floor(options2.maxSourceEpisodes ?? 80));
17875
+ const allowedSourceIds = options2.allowedSourceEpisodeIds?.length ? new Set(options2.allowedSourceEpisodeIds) : void 0;
17867
17876
  const visited = /* @__PURE__ */ new Map();
17868
17877
  const depthByNodeId = {};
17869
17878
  const traversed = /* @__PURE__ */ new Map();
@@ -17882,7 +17891,7 @@ function walkGraphFromSeed(graph, seed, options2 = {}) {
17882
17891
  const item = queue.shift();
17883
17892
  if (item.depth >= maxDepth)
17884
17893
  continue;
17885
- const neighbors2 = graph.neighbors(item.node.id).filter(({ edge }) => relationAllowed(edge, options2)).sort((a2, b) => b.edge.confidence - a2.edge.confidence || b.node.mentionCount - a2.node.mentionCount);
17894
+ const neighbors2 = graph.neighbors(item.node.id).filter(({ edge }) => edgeAllowed(edge, options2, allowedSourceIds)).sort((a2, b) => b.edge.confidence - a2.edge.confidence || b.node.mentionCount - a2.node.mentionCount);
17886
17895
  for (const { node, edge } of neighbors2) {
17887
17896
  if (traversed.size >= maxTraversedEdges)
17888
17897
  break;
@@ -558565,6 +558574,73 @@ function buildCriticPacketFromLedger(ledger) {
558565
558574
  }
558566
558575
  return lines.join("\n");
558567
558576
  }
558577
+ function appendUnresolved(items, text2, source) {
558578
+ const clean5 = cleanText(text2, 500);
558579
+ if (!clean5)
558580
+ return items;
558581
+ if (items.some((item) => item.text === clean5 && item.source === source)) {
558582
+ return items;
558583
+ }
558584
+ return [
558585
+ ...items,
558586
+ {
558587
+ id: nextId2("unresolved", items.length),
558588
+ text: clean5,
558589
+ source,
558590
+ recordedAtIso: nowIso2()
558591
+ }
558592
+ ];
558593
+ }
558594
+ function isMutationEvidence(entry) {
558595
+ if (entry.kind === "file_change")
558596
+ return true;
558597
+ return entry.success === true && /^(file_write|file_edit|file_patch|batch_edit)$/.test(entry.toolName ?? "");
558598
+ }
558599
+ function isSuccessfulVerificationEvidence(entry) {
558600
+ if (entry.success !== true)
558601
+ return false;
558602
+ const text2 = `${entry.toolName ?? ""} ${entry.summary}`.toLowerCase();
558603
+ return /\b(test|tests|tested|verify|verified|verification|build|built|compile|compiled|tsc|vitest|jest|pytest|go test|cargo test|pass|passed|success)\b/.test(text2);
558604
+ }
558605
+ function isFailedVerificationEvidence(entry) {
558606
+ if (entry.success !== false)
558607
+ return false;
558608
+ const text2 = `${entry.toolName ?? ""} ${entry.summary}`.toLowerCase();
558609
+ return /\b(test|tests|verify|verification|build|compile|tsc|vitest|jest|pytest|go test|cargo test)\b/.test(text2);
558610
+ }
558611
+ function isStaleEditEvidence(entry) {
558612
+ if (entry.success !== false)
558613
+ return false;
558614
+ const text2 = entry.summary.toLowerCase();
558615
+ return /stale edit loop blocked|old[_ -]?string|old text|expected[_ -]?hash|context mismatch|no occurrences|could not find/.test(text2);
558616
+ }
558617
+ function finalizeCompletionLedgerTruth(ledger) {
558618
+ let unresolved = [...ledger.unresolved];
558619
+ let lastMutation = -1;
558620
+ let lastVerification = -1;
558621
+ ledger.evidence.forEach((entry, index) => {
558622
+ if (isMutationEvidence(entry))
558623
+ lastMutation = index;
558624
+ if (isSuccessfulVerificationEvidence(entry))
558625
+ lastVerification = index;
558626
+ if (isFailedVerificationEvidence(entry)) {
558627
+ unresolved = appendUnresolved(unresolved, `Verification failed or did not prove success: ${entry.summary}`, entry.id);
558628
+ }
558629
+ if (isStaleEditEvidence(entry)) {
558630
+ unresolved = appendUnresolved(unresolved, `Stale edit failure remains unresolved: ${entry.summary}`, entry.id);
558631
+ }
558632
+ });
558633
+ if (lastVerification >= 0 && lastMutation > lastVerification) {
558634
+ unresolved = appendUnresolved(unresolved, "File changes occurred after the last successful verification; final verification is stale.", "verification_freshness");
558635
+ }
558636
+ const status = ledger.status === "blocked" || ledger.status === "request_changes" ? ledger.status : unresolved.length > 0 ? "incomplete_verification" : ledger.status;
558637
+ return {
558638
+ ...ledger,
558639
+ unresolved,
558640
+ status,
558641
+ updatedAtIso: nowIso2()
558642
+ };
558643
+ }
558568
558644
  function saveCompletionLedger(filePath, ledger) {
558569
558645
  mkdirSync49(dirname28(filePath), { recursive: true });
558570
558646
  writeFileSync40(filePath, `${JSON.stringify(ledger, null, 2)}
@@ -561348,7 +561424,7 @@ function sanitizeMessagesForCheckpoint(messages2) {
561348
561424
  deduped.push(m2);
561349
561425
  continue;
561350
561426
  }
561351
- if (i2 === lastFrameIdx) {
561427
+ if (content.startsWith(CONTEXT_FRAME_MARKER)) {
561352
561428
  deduped.push(m2);
561353
561429
  continue;
561354
561430
  }
@@ -561363,6 +561439,10 @@ function sanitizeMessagesForCheckpoint(messages2) {
561363
561439
  for (const m2 of deduped) {
561364
561440
  if (m2.role === "system") {
561365
561441
  const content = typeof m2.content === "string" ? m2.content : "";
561442
+ if (content.startsWith(CONTEXT_FRAME_MARKER)) {
561443
+ capped.push(m2);
561444
+ continue;
561445
+ }
561366
561446
  if (sysChars + content.length > MAX_CHECKPOINT_SYSTEM_CHARS) {
561367
561447
  continue;
561368
561448
  }
@@ -563837,11 +563917,59 @@ ${text2}` : SUMMARY_PREFIX;
563837
563917
  }
563838
563918
  });
563839
563919
 
563920
+ // packages/orchestrator/dist/artifactQuality.js
563921
+ function isInternalArtifactPrompt(text2) {
563922
+ const raw = String(text2 ?? "");
563923
+ if (!raw.trim())
563924
+ return false;
563925
+ return INTERNAL_PROMPT_PATTERNS.some((pattern) => pattern.test(raw));
563926
+ }
563927
+ function assessTaskArtifactQuality(input) {
563928
+ if (input.artifactMode === "internal") {
563929
+ return { eligible: false, reason: "internal artifact mode" };
563930
+ }
563931
+ const goal = String(input.goal ?? "");
563932
+ const summary = String(input.summary ?? "");
563933
+ if (isInternalArtifactPrompt(goal) || isInternalArtifactPrompt(summary)) {
563934
+ return { eligible: false, reason: "internal helper prompt" };
563935
+ }
563936
+ const toolsUsed = input.toolsUsed ?? [];
563937
+ const filesTouched = input.filesTouched ?? [];
563938
+ const turns = input.turns ?? 0;
563939
+ if (turns <= 0 && toolsUsed.length === 0) {
563940
+ return { eligible: false, reason: "zero-turn no-tool artifact" };
563941
+ }
563942
+ if (!summary.trim() && toolsUsed.length === 0 && filesTouched.length === 0) {
563943
+ return { eligible: false, reason: "empty no-evidence artifact" };
563944
+ }
563945
+ return { eligible: true };
563946
+ }
563947
+ var INTERNAL_PROMPT_PATTERNS;
563948
+ var init_artifactQuality = __esm({
563949
+ "packages/orchestrator/dist/artifactQuality.js"() {
563950
+ "use strict";
563951
+ INTERNAL_PROMPT_PATTERNS = [
563952
+ /\byou are an emotion center\b/i,
563953
+ /\bemotion center\b.*\brespond with one emoji\b/i,
563954
+ /\byou are the agent's private reflection process\b/i,
563955
+ /\bprefrontal cortex gating evaluator\b/i,
563956
+ /\bdentate gyrus pattern separation evaluator\b/i,
563957
+ /\bsnr evaluator\b/i,
563958
+ /\byou are the prefrontal cortex\b/i,
563959
+ /\byou are the hippocampus\b/i,
563960
+ /\byou are the amygdala\b/i,
563961
+ /\byou are the inner critic\b/i,
563962
+ /\byou are the basal ganglia\b/i
563963
+ ];
563964
+ }
563965
+ });
563966
+
563840
563967
  // packages/orchestrator/dist/reflectionBuffer.js
563841
563968
  var MAX_REFLECTIONS, MAX_TOTAL, TaskReflectionBuffer;
563842
563969
  var init_reflectionBuffer = __esm({
563843
563970
  "packages/orchestrator/dist/reflectionBuffer.js"() {
563844
563971
  "use strict";
563972
+ init_artifactQuality();
563845
563973
  MAX_REFLECTIONS = 5;
563846
563974
  MAX_TOTAL = 50;
563847
563975
  TaskReflectionBuffer = class {
@@ -563892,6 +564020,18 @@ var init_reflectionBuffer = __esm({
563892
564020
  */
563893
564021
  addReflection(params) {
563894
564022
  const { taskGoal, sessionId, turnsSpent, failedApproaches, toolCallLog, lastError, failedPaths } = params;
564023
+ const goalCoreForQuality = this.extractUserGoalCore(taskGoal) || taskGoal;
564024
+ const quality = assessTaskArtifactQuality({
564025
+ goal: goalCoreForQuality,
564026
+ summary: lastError,
564027
+ toolsUsed: toolCallLog.map((entry) => entry.tool),
564028
+ filesTouched: failedPaths || [],
564029
+ turns: turnsSpent
564030
+ });
564031
+ if (!quality.eligible) {
564032
+ const reason = quality.reason || "low-quality artifact";
564033
+ throw new Error("Reflection rejected: " + reason);
564034
+ }
563895
564035
  const taskFingerprint = this.computeFingerprint(taskGoal);
563896
564036
  const errorType = this.classifyError(toolCallLog, failedApproaches, lastError, turnsSpent);
563897
564037
  const failedTools = [...new Set(toolCallLog.filter((t2) => !t2.success).map((t2) => t2.tool))].slice(0, 5);
@@ -563940,23 +564080,26 @@ var init_reflectionBuffer = __esm({
563940
564080
  const fingerprint = this.computeFingerprint(taskGoal);
563941
564081
  const goalLower = goalCore.toLowerCase();
563942
564082
  const goalWords = new Set(goalLower.split(/\s+/).filter((w) => w.length > 3));
563943
- const scored = this.state.reflections.map((r2) => {
563944
- let score = 0;
564083
+ const scored = this.state.reflections.filter((r2) => !isInternalArtifactPrompt(r2.taskGoal) && !isInternalArtifactPrompt(r2.whatFailed) && !isInternalArtifactPrompt(r2.whatToDoDifferently)).map((r2) => {
564084
+ let baseScore = 0;
563945
564085
  if (r2.taskFingerprint === fingerprint)
563946
- score += 5;
564086
+ baseScore += 5;
563947
564087
  const rWords = new Set(r2.taskGoal.toLowerCase().split(/\s+/).filter((w) => w.length > 3));
563948
564088
  let overlap = 0;
563949
564089
  for (const w of goalWords)
563950
564090
  if (rWords.has(w))
563951
564091
  overlap++;
563952
- score += overlap;
564092
+ baseScore += overlap;
564093
+ if (baseScore <= 0)
564094
+ return { reflection: r2, score: 0, relevant: false };
564095
+ let score = baseScore;
563953
564096
  const hoursAgo = (Date.now() - r2.timestamp) / 36e5;
563954
564097
  score += Math.max(0, 2 - hoursAgo * 0.1);
563955
564098
  score += r2.confidence * 2;
563956
- return { reflection: r2, score };
564099
+ return { reflection: r2, score, relevant: true };
563957
564100
  });
563958
564101
  scored.sort((a2, b) => b.score - a2.score);
563959
- const results = scored.slice(0, maxResults).filter((s2) => s2.score > 1).map((s2) => s2.reflection);
564102
+ const results = scored.filter((s2) => s2.relevant && s2.score > 1).slice(0, maxResults).map((s2) => s2.reflection);
563960
564103
  this.state.totalConsumed += results.length;
563961
564104
  this.persist();
563962
564105
  return results;
@@ -564169,23 +564312,50 @@ function buildTaskHandoff(params) {
564169
564312
  const argHint = tc.argsKey ? ` ${tc.argsKey.slice(0, 80)}` : "";
564170
564313
  return `${tc.name}${argHint}`;
564171
564314
  });
564315
+ const artifactMode = params.artifactMode ?? "user-task";
564316
+ const filesTouched = [...new Set(params.filesModified)].slice(0, MAX_FILES);
564317
+ const toolsUsed = [...new Set(params.toolsUsed)].slice(0, MAX_TOOLS);
564318
+ const quality = assessTaskArtifactQuality({
564319
+ artifactMode,
564320
+ goal: params.goal,
564321
+ summary: params.summary,
564322
+ filesTouched,
564323
+ toolsUsed,
564324
+ turns: params.turns
564325
+ });
564172
564326
  return {
564173
564327
  v: 2,
564174
564328
  sessionId: params.sessionId,
564175
564329
  priorGoal: params.goal.slice(0, 500),
564176
564330
  priorOutcome: params.outcome,
564177
564331
  priorSummary: (params.summary || "").slice(0, MAX_SUMMARY_CHARS),
564178
- filesTouched: [...new Set(params.filesModified)].slice(0, MAX_FILES),
564179
- toolsUsed: [...new Set(params.toolsUsed)].slice(0, MAX_TOOLS),
564332
+ filesTouched,
564333
+ toolsUsed,
564180
564334
  lastActions,
564181
564335
  turns: params.turns,
564182
564336
  endedAt: Date.now(),
564337
+ artifactMode,
564338
+ quality,
564183
564339
  ...params.transcriptPath ? { transcriptPath: params.transcriptPath } : {}
564184
564340
  };
564185
564341
  }
564342
+ function shouldPersistTaskHandoff(handoff) {
564343
+ if (handoff.quality && handoff.quality.eligible === false)
564344
+ return false;
564345
+ return assessTaskArtifactQuality({
564346
+ artifactMode: handoff.artifactMode,
564347
+ goal: handoff.priorGoal,
564348
+ summary: handoff.priorSummary,
564349
+ toolsUsed: handoff.toolsUsed,
564350
+ filesTouched: handoff.filesTouched,
564351
+ turns: handoff.turns
564352
+ }).eligible;
564353
+ }
564186
564354
  function writeTaskHandoff(omniusDir, handoff) {
564187
564355
  if (!handoffEnabled())
564188
564356
  return;
564357
+ if (!shouldPersistTaskHandoff(handoff))
564358
+ return;
564189
564359
  try {
564190
564360
  const file = handoffPath(omniusDir);
564191
564361
  fs4.mkdirSync(path5.dirname(file), { recursive: true });
@@ -564206,6 +564376,8 @@ function readTaskHandoff(omniusDir, opts) {
564206
564376
  return null;
564207
564377
  }
564208
564378
  const handoff = parsed;
564379
+ if (!shouldPersistTaskHandoff(handoff))
564380
+ return null;
564209
564381
  const sameSession = handoff.sessionId === opts.currentSessionId;
564210
564382
  if (!sameSession) {
564211
564383
  const age = Date.now() - handoff.endedAt;
@@ -564274,6 +564446,7 @@ var DEFAULT_TTL_MS2, MAX_SUMMARY_CHARS, MAX_FILES, MAX_TOOLS, MAX_LAST_ACTIONS;
564274
564446
  var init_taskHandoff = __esm({
564275
564447
  "packages/orchestrator/dist/taskHandoff.js"() {
564276
564448
  "use strict";
564449
+ init_artifactQuality();
564277
564450
  DEFAULT_TTL_MS2 = 24 * 60 * 60 * 1e3;
564278
564451
  MAX_SUMMARY_CHARS = 8e3;
564279
564452
  MAX_FILES = 30;
@@ -564304,6 +564477,8 @@ function logPath2(omniusDir, sessionLogId) {
564304
564477
  function appendBoundaryToLog(omniusDir, handoff) {
564305
564478
  if (!logEnabled())
564306
564479
  return;
564480
+ if (!shouldPersistTaskHandoff(handoff))
564481
+ return;
564307
564482
  const id = tuiSessionLogId();
564308
564483
  if (!id)
564309
564484
  return;
@@ -564332,7 +564507,9 @@ function loadBoundaryRecords(omniusDir) {
564332
564507
  try {
564333
564508
  const parsed = JSON.parse(lines[i2]);
564334
564509
  if (parsed && parsed.v === 1 && parsed.handoff && typeof parsed.recordedAt === "number") {
564335
- records.push(parsed);
564510
+ const record = parsed;
564511
+ if (shouldPersistTaskHandoff(record.handoff))
564512
+ records.push(record);
564336
564513
  }
564337
564514
  } catch {
564338
564515
  }
@@ -565883,7 +566060,7 @@ function heuristicSummarize(inputs) {
565883
566060
  }
565884
566061
  }
565885
566062
  return {
565886
- functionalSummary: `Completed: ${inputs.todoContent.slice(0, 120)} (heuristic summary — use memex_retrieve(expand=true) for full context)`,
566063
+ functionalSummary: `Work recorded: ${inputs.todoContent.slice(0, 120)} (heuristic summary — use memex_retrieve(expand=true) for full context)`,
565887
566064
  keyFiles: [...files].slice(0, 30),
565888
566065
  criticalSymbols: {
565889
566066
  defined: [...new Set(symbolsDefined)].slice(0, 50),
@@ -565901,6 +566078,31 @@ function heuristicSummarize(inputs) {
565901
566078
  ].join("\n")
565902
566079
  };
565903
566080
  }
566081
+ function hasExplicitVerification(text2) {
566082
+ const raw = String(text2 || "").trim();
566083
+ if (!raw)
566084
+ return false;
566085
+ if (/^\(?no verification detected\)?$/i.test(raw))
566086
+ return false;
566087
+ if (/\b(no verification|not verified|not run|missing verification|none detected)\b/i.test(raw))
566088
+ return false;
566089
+ return /\b(pass|passed|success|succeeded|verified|tests?|build|compile|tsc|vitest|jest|pytest|go test|cargo test)\b/i.test(raw);
566090
+ }
566091
+ function deriveChunkStatus(verdict, toolCalls) {
566092
+ if ((verdict.unresolved || []).length > 0)
566093
+ return "blocked";
566094
+ if (toolCalls.some((tc) => !tc.success))
566095
+ return "partial";
566096
+ if (!hasExplicitVerification(verdict.verification || ""))
566097
+ return "unverified";
566098
+ return "completed";
566099
+ }
566100
+ function statusAwareFunctionalSummary(summary, status, fallback) {
566101
+ const raw = (summary || fallback).trim();
566102
+ if (status === "completed")
566103
+ return raw;
566104
+ return raw.replace(/^completed\s*:?/i, "Work recorded:");
566105
+ }
565904
566106
  async function runTodoChunker(opts) {
565905
566107
  const startMs = Date.now();
565906
566108
  const memexId = `todo-ctx-${sanitizeId(opts.inputs.todoId).slice(0, 20)}`;
@@ -565919,11 +566121,12 @@ async function runTodoChunker(opts) {
565919
566121
  usedFallback = true;
565920
566122
  }
565921
566123
  const estimatedTokens = Math.round(opts.inputs.transcript.length / 4);
566124
+ const status = deriveChunkStatus(verdict, opts.inputs.toolCalls);
565922
566125
  const chunk = {
565923
566126
  todoId: opts.inputs.todoId,
565924
566127
  todoContent: opts.inputs.todoContent,
565925
- status: "completed",
565926
- functionalSummary: verdict.functionalSummary || `Completed: ${opts.inputs.todoContent.slice(0, 120)}`,
566128
+ status,
566129
+ functionalSummary: statusAwareFunctionalSummary(verdict.functionalSummary, status, `Work recorded: ${opts.inputs.todoContent.slice(0, 120)}`),
565927
566130
  detailSummary: verdict.detailSummary || "",
565928
566131
  keyFiles: verdict.keyFiles || [],
565929
566132
  criticalSymbols: verdict.criticalSymbols || { defined: [], modified: [] },
@@ -570259,6 +570462,7 @@ var init_agenticRunner = __esm({
570259
570462
  */
570260
570463
  _completionCaveat = null;
570261
570464
  _completionLedger = null;
570465
+ _staleEditFamilies = /* @__PURE__ */ new Map();
570262
570466
  // ── WO-AM-01/04/10: Associative memory stores ──
570263
570467
  // Episode store: every tool call → persistent episode with importance + decay
570264
570468
  // Temporal KG: entities + relations with temporal validity (valid_from/valid_until)
@@ -570607,6 +570811,7 @@ ${parts.join("\n")}
570607
570811
  memoryPrefix: options2?.memoryPrefix ?? "",
570608
570812
  memoryPrefixHash: options2?.memoryPrefixHash ?? "",
570609
570813
  stateDir: options2?.stateDir ?? "",
570814
+ artifactMode: options2?.artifactMode ?? "user-task",
570610
570815
  disablePersistentMemory: options2?.disablePersistentMemory ?? false,
570611
570816
  disableCodebaseMap: options2?.disableCodebaseMap ?? false,
570612
570817
  sessionId: options2?.sessionId ?? "",
@@ -570658,7 +570863,12 @@ ${parts.join("\n")}
570658
570863
  const configured = (this.options.stateDir || "").trim();
570659
570864
  return configured ? _pathResolve(configured) : _pathJoin(process.cwd(), ".omnius");
570660
570865
  }
570866
+ writesUserTaskArtifacts() {
570867
+ return this.options.artifactMode === "user-task" && !this.options.subAgent;
570868
+ }
570661
570869
  _persistCompletionContract(contract) {
570870
+ if (!this.writesUserTaskArtifacts())
570871
+ return;
570662
570872
  try {
570663
570873
  const dir = _pathJoin(this.omniusStateDir(), "completion-contracts");
570664
570874
  _fsMkdirSync(dir, { recursive: true });
@@ -570671,6 +570881,8 @@ ${parts.join("\n")}
570671
570881
  }
570672
570882
  }
570673
570883
  _saveCompletionLedgerSafe() {
570884
+ if (!this.writesUserTaskArtifacts())
570885
+ return;
570674
570886
  if (!this._completionLedger)
570675
570887
  return;
570676
570888
  try {
@@ -570682,8 +570894,9 @@ ${parts.join("\n")}
570682
570894
  }
570683
570895
  _initializeCompletionContract(task, context2, actualUserGoal) {
570684
570896
  const goalText = actualUserGoal || task;
570685
- this._completionContractSeedText = [goalText, context2 ?? ""].map((text2) => String(text2 || "").trim()).filter(Boolean).join("\n\n");
570686
- this._completionContract = inferCompletionContractFromTexts([this._completionContractSeedText], task);
570897
+ const seedTexts = actualUserGoal ? [goalText] : [goalText, context2 ?? ""];
570898
+ this._completionContractSeedText = seedTexts.map((text2) => String(text2 || "").trim()).filter(Boolean).join("\n\n");
570899
+ this._completionContract = inferCompletionContractFromTexts([this._completionContractSeedText], goalText);
570687
570900
  this._persistCompletionContract(this._completionContract);
570688
570901
  return this._completionContract;
570689
570902
  }
@@ -575442,6 +575655,7 @@ Respond with your assessment, then take action.`;
575442
575655
  this._completionIncompleteVerification = null;
575443
575656
  this._completionCaveat = null;
575444
575657
  this._completionLedger = null;
575658
+ this._staleEditFamilies.clear();
575445
575659
  this._lastWorldStateTurn = -1;
575446
575660
  this._fileWritesSinceLastWorldState = 0;
575447
575661
  this._resetVisualEvidenceState();
@@ -575537,7 +575751,8 @@ Respond with your assessment, then take action.`;
575537
575751
  this._loadResolutionMemory();
575538
575752
  this._pauseResolve = null;
575539
575753
  this.pendingUserMessages.length = 0;
575540
- const userGoal = (actualUserGoal || cleanedTask).slice(0, 500);
575754
+ const persistentTaskGoal = cleanForStorage(actualUserGoal || "") || cleanedTask;
575755
+ const userGoal = persistentTaskGoal.slice(0, 500);
575541
575756
  this._taskState = {
575542
575757
  goal: userGoal,
575543
575758
  originalGoal: userGoal,
@@ -575583,11 +575798,16 @@ Respond with your assessment, then take action.`;
575583
575798
  verbose: false
575584
575799
  });
575585
575800
  this._hookManager.runSessionHook("session_start", this._sessionId);
575586
- this._initializeCompletionContract(task, context2, actualUserGoal);
575587
- this._completionLedger = createCompletionLedger({
575588
- runId: this._sessionId,
575589
- goal: userGoal
575590
- });
575801
+ if (this.writesUserTaskArtifacts()) {
575802
+ this._initializeCompletionContract(task, context2, actualUserGoal);
575803
+ this._completionLedger = createCompletionLedger({
575804
+ runId: this._sessionId,
575805
+ goal: userGoal
575806
+ });
575807
+ } else {
575808
+ this._completionContract = null;
575809
+ this._completionLedger = null;
575810
+ }
575591
575811
  this._sessionStartMs = Date.now();
575592
575812
  if (process.env["OMNIUS_DISABLE_PREFLIGHT"] !== "1") {
575593
575813
  try {
@@ -575737,8 +575957,8 @@ ${_notes}`;
575737
575957
  }
575738
575958
  try {
575739
575959
  if (!this.options.subAgent) {
575740
- const _imp = Math.min(9, Math.max(3, 3 + Math.floor((task?.length || 0) / 250)));
575741
- const _kw = String(task || "").toLowerCase().match(/[a-z][a-z0-9_-]{3,}/g)?.filter((w) => ![
575960
+ const _imp = Math.min(9, Math.max(3, 3 + Math.floor((persistentTaskGoal.length || 0) / 250)));
575961
+ const _kw = String(persistentTaskGoal || "").toLowerCase().match(/[a-z][a-z0-9_-]{3,}/g)?.filter((w) => ![
575742
575962
  "this",
575743
575963
  "that",
575744
575964
  "with",
@@ -575750,14 +575970,14 @@ ${_notes}`;
575750
575970
  "please"
575751
575971
  ].includes(w)).slice(0, 6) ?? [];
575752
575972
  noteAfterTask({
575753
- summary: `Task: ${String(task).slice(0, 200)}`,
575973
+ summary: `Task: ${persistentTaskGoal.slice(0, 200)}`,
575754
575974
  importance: _imp,
575755
575975
  keywords: _kw
575756
575976
  });
575757
575977
  }
575758
575978
  } catch {
575759
575979
  }
575760
- this._contextTree = new ContextTree(`sys-${systemPrompt.length}`, cleanedTask.slice(0, 200));
575980
+ this._contextTree = new ContextTree(`sys-${systemPrompt.length}`, persistentTaskGoal.slice(0, 200));
575761
575981
  this.emit({
575762
575982
  type: "status",
575763
575983
  content: `Context assembled: ${contextComposition.sections.map((s2) => `${s2.label}(${s2.tokenEstimate}t)`).join(" + ")} = ~${contextComposition.totalTokenEstimate}t`,
@@ -575780,7 +576000,7 @@ TASK: ${scrubbedTask}` : scrubbedTask;
575780
576000
  contextFrame: {
575781
576001
  kind: "system_prompt",
575782
576002
  capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
575783
- taskPreview: cleanedTask.slice(0, 240),
576003
+ taskPreview: persistentTaskGoal.slice(0, 240),
575784
576004
  assembledCharCount: systemPrompt.length,
575785
576005
  totalTokenEstimate: contextComposition.totalTokenEstimate,
575786
576006
  sections: contextComposition.sections.map((section, order) => ({
@@ -575801,13 +576021,13 @@ TASK: ${scrubbedTask}` : scrubbedTask;
575801
576021
  }
575802
576022
  });
575803
576023
  }
575804
- const missionCompletionContract = this.buildMissionCompletionContract(cleanedTask, context2);
576024
+ const missionCompletionContract = this.buildMissionCompletionContract(persistentTaskGoal, context2);
575805
576025
  const messages2 = [
575806
576026
  { role: "system", content: systemPrompt },
575807
576027
  ...missionCompletionContract ? [{ role: "system", content: missionCompletionContract }] : [],
575808
576028
  { role: "user", content: userContent }
575809
576029
  ];
575810
- const preflightMemoryRecall = this._buildPreflightTaskMemoryRecall(cleanedTask);
576030
+ const preflightMemoryRecall = this._buildPreflightTaskMemoryRecall(persistentTaskGoal);
575811
576031
  if (preflightMemoryRecall) {
575812
576032
  messages2.splice(messages2.length - 1, 0, {
575813
576033
  role: "system",
@@ -575863,6 +576083,8 @@ TASK: ${scrubbedTask}` : scrubbedTask;
575863
576083
  try {
575864
576084
  if (this.options.subAgent)
575865
576085
  throw "skip-handoff-subagent";
576086
+ if (!this.writesUserTaskArtifacts())
576087
+ throw "skip-handoff-artifact-mode";
575866
576088
  if (this.options.skipCrossTaskHandoff)
575867
576089
  throw "skip-handoff-caller-optout";
575868
576090
  if (/<task-handoff>|<session-recap>/.test(task))
@@ -575871,7 +576093,7 @@ TASK: ${scrubbedTask}` : scrubbedTask;
575871
576093
  throw "skip-handoff-fresh";
575872
576094
  const omniusDir = this._workingDirectory ? _pathJoin(this._workingDirectory, ".omnius") : _pathJoin(process.cwd(), ".omnius");
575873
576095
  const chainPairs = loadMessagePairsFromLog(omniusDir, {
575874
- currentTask: cleanedTask
576096
+ currentTask: persistentTaskGoal
575875
576097
  });
575876
576098
  if (chainPairs.length > 0) {
575877
576099
  messages2.splice(1, 0, ...chainPairs);
@@ -575883,7 +576105,7 @@ TASK: ${scrubbedTask}` : scrubbedTask;
575883
576105
  } else {
575884
576106
  const prior = readTaskHandoff(omniusDir, {
575885
576107
  currentSessionId: this._sessionId,
575886
- currentTask: cleanedTask
576108
+ currentTask: persistentTaskGoal
575887
576109
  });
575888
576110
  if (prior) {
575889
576111
  const handoffPair = buildHandoffMessagePair(prior);
@@ -575899,6 +576121,8 @@ TASK: ${scrubbedTask}` : scrubbedTask;
575899
576121
  } catch {
575900
576122
  }
575901
576123
  try {
576124
+ if (!this.writesUserTaskArtifacts())
576125
+ throw "skip-reflection-artifact-mode";
575902
576126
  if (!this._reflectionBuffer) {
575903
576127
  const omniusDir = this.options.stateDir ? _pathJoin(this.omniusStateDir(), "memory") : this._workingDirectory ? _pathJoin(this._workingDirectory, ".omnius", "memory") : null;
575904
576128
  if (omniusDir) {
@@ -575906,7 +576130,7 @@ TASK: ${scrubbedTask}` : scrubbedTask;
575906
576130
  }
575907
576131
  }
575908
576132
  if (this._reflectionBuffer) {
575909
- const reflections = this._reflectionBuffer.getRelevantReflections(cleanedTask, 3);
576133
+ const reflections = this._reflectionBuffer.getRelevantReflections(persistentTaskGoal, 3);
575910
576134
  if (reflections.length > 0) {
575911
576135
  const reflectionCtx = this._reflectionBuffer.formatForContext(reflections);
575912
576136
  messages2.push({ role: "system", content: reflectionCtx });
@@ -575922,7 +576146,7 @@ TASK: ${scrubbedTask}` : scrubbedTask;
575922
576146
  if (process.env["OMNIUS_DISABLE_FAILURE_HANDOFF"] !== "1") {
575923
576147
  try {
575924
576148
  const failureHandoff = buildFailureModeHandoff({
575925
- taskGoal: cleanedTask,
576149
+ taskGoal: persistentTaskGoal,
575926
576150
  errorPatterns: this._errorPatterns,
575927
576151
  toolCallLog,
575928
576152
  taskState: this._taskState,
@@ -576127,7 +576351,7 @@ TASK: ${scrubbedTask}` : scrubbedTask;
576127
576351
  })();
576128
576352
  const gate = this._evaluateCompletionProvenanceGate({
576129
576353
  summary: proposedSummary,
576130
- taskGoal: cleanedTask,
576354
+ taskGoal: persistentTaskGoal,
576131
576355
  toolCallLog,
576132
576356
  answerText: lastAssistantText
576133
576357
  });
@@ -578497,6 +578721,43 @@ Use the saved fact to continue the promised synthesis or next concrete step, or
578497
578721
  systemGuidance: repeatNoopMsg
578498
578722
  };
578499
578723
  }
578724
+ const staleEditBlock = this.staleEditPreflightBlock(tc.name, tc.arguments ?? {});
578725
+ if (staleEditBlock) {
578726
+ this.emit({
578727
+ type: "tool_call",
578728
+ toolName: tc.name,
578729
+ toolArgs: tc.arguments,
578730
+ turn,
578731
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
578732
+ });
578733
+ this.emit({
578734
+ type: "tool_result",
578735
+ toolName: tc.name,
578736
+ success: false,
578737
+ content: staleEditBlock.slice(0, 120),
578738
+ turn,
578739
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
578740
+ });
578741
+ this._tagSyntheticFailure({
578742
+ mode: "step_repetition",
578743
+ rationale: "stale edit family retried without fresh read or mutation"
578744
+ });
578745
+ if (this._completionLedger) {
578746
+ this._completionLedger = recordToolEvidence(this._completionLedger, {
578747
+ name: tc.name,
578748
+ success: false,
578749
+ outputPreview: staleEditBlock.slice(0, 500),
578750
+ argsKey: tc.arguments ? JSON.stringify(tc.arguments).slice(0, 300) : ""
578751
+ });
578752
+ this._saveCompletionLedgerSafe();
578753
+ }
578754
+ return {
578755
+ tc,
578756
+ output: staleEditBlock,
578757
+ success: false,
578758
+ systemGuidance: staleEditBlock
578759
+ };
578760
+ }
578500
578761
  const baseIsReadLike = ![
578501
578762
  "file_write",
578502
578763
  "file_edit",
@@ -579977,6 +580238,7 @@ Evidence: ${evidencePreview}`.slice(0, 500);
579977
580238
  }
579978
580239
  const filePath = typeof tc.arguments?.path === "string" ? tc.arguments.path : "";
579979
580240
  recordToolExecution(this._appState, tc.name, performance.now() - toolStart, result.success, filePath || void 0);
580241
+ this.noteStaleEditGuardOutcome(tc.name, tc.arguments ?? {}, result, turn);
579980
580242
  if (tc.name === "todo_write") {
579981
580243
  this._lastTodoWriteTurn = turn;
579982
580244
  }
@@ -580158,7 +580420,7 @@ Then use file_read on individual FILES inside it.`);
580158
580420
  }
580159
580421
  if (process.env["OMNIUS_DISABLE_FAILURE_HANDOFF"] !== "1" && !result.success && turn - lastFailureHandoffTurn >= 4) {
580160
580422
  const runtimeHandoff = buildFailureModeHandoff({
580161
- taskGoal: cleanedTask,
580423
+ taskGoal: persistentTaskGoal,
580162
580424
  errorPatterns: this._errorPatterns,
580163
580425
  toolCallLog,
580164
580426
  taskState: this._taskState,
@@ -580531,7 +580793,10 @@ ${sr.result.output}`;
580531
580793
  loopInterventionCount++;
580532
580794
  const loopTier2 = this.options.modelTier ?? "large";
580533
580795
  const maxInterventions = loopTier2 === "small" ? 3 : loopTier2 === "medium" ? 5 : 8;
580796
+ let loopCircuitBreakerFired = false;
580797
+ const loopInterventionOrdinal = loopInterventionCount;
580534
580798
  if (loopInterventionCount >= maxInterventions) {
580799
+ loopCircuitBreakerFired = true;
580535
580800
  this.emit({
580536
580801
  type: "status",
580537
580802
  content: `Loop circuit breaker: ${loopInterventionCount} interventions — requesting user guidance`,
@@ -580542,15 +580807,16 @@ ${sr.result.output}`;
580542
580807
  role: "system",
580543
580808
  content: `LOOP CIRCUIT BREAKER: ${loopInterventionCount} interventions attempted but repetition continues.
580544
580809
 
580545
- MANDATORY: You MUST call ask_user to request guidance from the user. Present your options:
580810
+ MANDATORY: Change state before taking another action. Choose one:
580546
580811
  1. Try a completely different approach
580547
- 2. Provide more context or clarification
580548
- 3. Accept partial results and pivot to a related task
580549
- 4. Abort this specific subtask and move on
580812
+ 2. Re-read the exact current target once if the loop is caused by a stale edit
580813
+ 3. Run verification if files changed since the last successful check
580814
+ 4. Call ask_user if user input is the real blocker
580815
+ 5. Report blocked/incomplete with the concrete blocker if no safe progress path remains
580550
580816
 
580551
580817
  Your partial progress: ${partialResults}
580552
580818
 
580553
- Call ask_user(question="I've hit a repetitive loop. How would you like me to proceed?", options=[...]) NOW.`
580819
+ Do not call task_complete solely because this circuit breaker fired.`
580554
580820
  });
580555
580821
  loopInterventionCount = 0;
580556
580822
  }
@@ -580568,19 +580834,19 @@ Call ask_user(question="I've hit a repetitive loop. How would you like me to pro
580568
580834
  }
580569
580835
  const findingsSummary = findings.length > 0 ? `
580570
580836
 
580571
- Here is what you ALREADY found (use this for your summary):
580572
- ${findings.slice(0, 8).map((f2, i2) => `${i2 + 1}. ${f2}`).join("\n")}` : "\n\nYou found no actionable results. Report that the information could not be found.";
580837
+ Here is what you ALREADY found. Use this to choose the next non-repetitive action:
580838
+ ${findings.slice(0, 8).map((f2, i2) => `${i2 + 1}. ${f2}`).join("\n")}` : "\n\nYou found no actionable results. Ask for guidance or report the specific blocker; do not invent completion.";
580573
580839
  messages2.push({
580574
580840
  role: "system",
580575
- content: `LOOP DETECTED (intervention ${loopInterventionCount}/${maxInterventions}). The last ${repetitionWindow} tool calls are ${Math.round(currentRepScore * 100)}% repetitive: ${topRepeated}.
580841
+ content: `LOOP DETECTED (intervention ${loopInterventionOrdinal}/${maxInterventions}). The last ${repetitionWindow} tool calls are ${Math.round(currentRepScore * 100)}% repetitive: ${topRepeated}.
580576
580842
 
580577
- MANDATORY: You MUST call task_complete on your next response. Do NOT call ${[...freqMap.keys()].map((k) => k.split("(")[0]).filter((v, i2, a2) => a2.indexOf(v) === i2).join(", ")} again. These tools have returned the same results multiple times.` + findingsSummary + `
580843
+ RECOVERY REQUIRED: Do not call task_complete just because repetition was detected. Do NOT call ${[...freqMap.keys()].map((k) => k.split("(")[0]).filter((v, i2, a2) => a2.indexOf(v) === i2).join(", ")} again with the same arguments. These tools have returned the same results multiple times. Choose a different concrete action: read the exact current target once if an edit failed, run verification if files changed, use a different tool or target, or call ask_user when user input is the real blocker.` + findingsSummary + `
580578
580844
 
580579
- Call task_complete(summary="...") NOW with whatever you have.`
580845
+ Only call task_complete when the task is actually complete and the evidence is fresh. If work is partial, report the blocker explicitly instead of completing.`
580580
580846
  });
580581
580847
  this.emit({
580582
580848
  type: "status",
580583
- content: `Loop intervention ${loopInterventionCount}/${maxInterventions}: ${Math.round(currentRepScore * 100)}% repetitive (${topRepeated})`,
580849
+ content: `Loop intervention ${loopInterventionOrdinal}/${maxInterventions}: ${Math.round(currentRepScore * 100)}% repetitive (${topRepeated})${loopCircuitBreakerFired ? " (circuit breaker advisory)" : ""}`,
580584
580850
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
580585
580851
  });
580586
580852
  try {
@@ -581482,7 +581748,19 @@ Full content available via: repl_exec(code="data = retrieve('${handleId}')") or
581482
581748
 
581483
581749
  ${this._completionCaveat}` : this._completionCaveat;
581484
581750
  }
581485
- const runStatus = completed ? "completed" : this._completionLedger?.status === "blocked" || this._completionLedger?.status === "request_changes" ? "incomplete_verification" : this._completionIncompleteVerification ? "incomplete_verification" : "incomplete";
581751
+ if (this._completionLedger) {
581752
+ this._completionLedger = finalizeCompletionLedgerTruth(this._completionLedger);
581753
+ this._saveCompletionLedgerSafe();
581754
+ if (completed && this._completionLedger.status === "incomplete_verification") {
581755
+ completed = false;
581756
+ const unresolvedSummary = this._completionLedger.unresolved.slice(-3).map((item) => item.text).join("; ");
581757
+ const caveat = unresolvedSummary ? `Incomplete verification: ${unresolvedSummary}` : "Incomplete verification: unresolved evidence remains in the completion ledger.";
581758
+ summary = summary ? `${summary}
581759
+
581760
+ ${caveat}` : caveat;
581761
+ }
581762
+ }
581763
+ const runStatus = completed ? "completed" : this._completionLedger?.status === "blocked" || this._completionLedger?.status === "request_changes" || this._completionLedger?.status === "incomplete_verification" ? "incomplete_verification" : this._completionIncompleteVerification ? "incomplete_verification" : "incomplete";
581486
581764
  this._emitMASTSummary("run-end");
581487
581765
  this.emit({
581488
581766
  type: "complete",
@@ -581530,6 +581808,8 @@ ${this._completionCaveat}` : this._completionCaveat;
581530
581808
  }
581531
581809
  }
581532
581810
  try {
581811
+ if (!this.writesUserTaskArtifacts())
581812
+ throw "skip-user-task-consolidation";
581533
581813
  const extractPaths = (entries, toolNames) => {
581534
581814
  return [
581535
581815
  ...new Set(entries.filter((tc) => toolNames.includes(tc.name)).map((tc) => {
@@ -581540,7 +581820,7 @@ ${this._completionCaveat}` : this._completionCaveat;
581540
581820
  };
581541
581821
  const consolidation = {
581542
581822
  sessionId: this._sessionId,
581543
- task: cleanedTask.slice(0, 500),
581823
+ task: persistentTaskGoal.slice(0, 500),
581544
581824
  outcome: completed ? "success" : this.aborted ? "aborted" : runStatus === "incomplete_verification" ? "incomplete_verification" : "timeout",
581545
581825
  turns: messages2.filter((m2) => m2.role === "assistant").length,
581546
581826
  toolsUsed: [...new Set(toolCallLog.map((tc) => tc.name))],
@@ -581565,7 +581845,7 @@ ${this._completionCaveat}` : this._completionCaveat;
581565
581845
  fs11.mkdirSync(provenanceDir, { recursive: true });
581566
581846
  const provenanceGraph = {
581567
581847
  sessionId: this._sessionId,
581568
- task: cleanedTask.slice(0, 500),
581848
+ task: persistentTaskGoal.slice(0, 500),
581569
581849
  outcome: consolidation.outcome,
581570
581850
  timestamp: consolidation.timestamp,
581571
581851
  // Full action trace — every tool call with sequence ordering
@@ -581615,7 +581895,7 @@ ${this._completionCaveat}` : this._completionCaveat;
581615
581895
  });
581616
581896
  if (completed && this.tools.has("memory_write")) {
581617
581897
  const memTool = this.tools.get("memory_write");
581618
- const lessonContent = `Task "${cleanedTask.slice(0, 100)}" completed successfully. Tools: ${consolidation.toolsUsed.join(", ")}. Files: ${consolidation.filesModified.slice(0, 3).join(", ")}. Duration: ${Math.round(durationMs / 1e3)}s, ${consolidation.turns} turns.`;
581898
+ const lessonContent = `Task "${persistentTaskGoal.slice(0, 100)}" completed successfully. Tools: ${consolidation.toolsUsed.join(", ")}. Files: ${consolidation.filesModified.slice(0, 3).join(", ")}. Duration: ${Math.round(durationMs / 1e3)}s, ${consolidation.turns} turns.`;
581619
581899
  try {
581620
581900
  await memTool.execute({
581621
581901
  topic: "task_lessons",
@@ -581633,15 +581913,18 @@ ${this._completionCaveat}` : this._completionCaveat;
581633
581913
  const transcriptPath = _pathJoin(omniusDir, "consolidations", `${this._sessionId}.json`);
581634
581914
  const handoff = buildTaskHandoff({
581635
581915
  sessionId: this._sessionId,
581636
- goal: cleanedTask,
581916
+ goal: persistentTaskGoal,
581637
581917
  outcome,
581638
581918
  summary,
581639
581919
  filesModified: consolidation.filesModified,
581640
581920
  toolsUsed: consolidation.toolsUsed,
581641
581921
  toolCallLog,
581642
581922
  turns: consolidation.turns,
581643
- transcriptPath
581923
+ transcriptPath,
581924
+ artifactMode: this.options.artifactMode
581644
581925
  });
581926
+ if (!shouldPersistTaskHandoff(handoff))
581927
+ throw "skip-low-quality-handoff";
581645
581928
  writeTaskHandoff(omniusDir, handoff);
581646
581929
  appendBoundaryToLog(omniusDir, handoff);
581647
581930
  trimLog(omniusDir);
@@ -581654,10 +581937,10 @@ ${this._completionCaveat}` : this._completionCaveat;
581654
581937
  });
581655
581938
  } catch {
581656
581939
  }
581657
- if (this._reflectionBuffer && !completed) {
581940
+ if (this._reflectionBuffer && !completed && this.writesUserTaskArtifacts()) {
581658
581941
  try {
581659
581942
  const reflection = this._reflectionBuffer.addReflection({
581660
- taskGoal: cleanedTask,
581943
+ taskGoal: persistentTaskGoal,
581661
581944
  sessionId: this._sessionId,
581662
581945
  turnsSpent: this._taskState.toolCallCount,
581663
581946
  failedApproaches: this._taskState.failedApproaches,
@@ -581677,9 +581960,9 @@ ${this._completionCaveat}` : this._completionCaveat;
581677
581960
  } catch {
581678
581961
  }
581679
581962
  }
581680
- if (this._episodeStore) {
581963
+ if (this._episodeStore && this.writesUserTaskArtifacts()) {
581681
581964
  try {
581682
- const taskSummaryContent = `Task "${cleanedTask.slice(0, 200)}" ${completed ? "completed" : "ended"}: ${cleanForStorage(summary).slice(0, 300)}`;
581965
+ const taskSummaryContent = `Task "${persistentTaskGoal.slice(0, 200)}" ${completed ? "completed" : "ended"}: ${cleanForStorage(summary).slice(0, 300)}`;
581683
581966
  if (this._crlMemoryStore) {
581684
581967
  this._crlMemoryStore.storeCRL({
581685
581968
  content: taskSummaryContent,
@@ -581908,7 +582191,7 @@ ${this._completionCaveat}` : this._completionCaveat;
581908
582191
  await this.processPendingEmbeddings();
581909
582192
  }
581910
582193
  this.stopEmbeddingPipeline();
581911
- const gist = compressAndStore(this._episodeStore, this._sessionId, task, 10);
582194
+ const gist = compressAndStore(this._episodeStore, this._sessionId, persistentTaskGoal, 10);
581912
582195
  if (gist) {
581913
582196
  this.emit({
581914
582197
  type: "status",
@@ -581934,7 +582217,7 @@ ${this._completionCaveat}` : this._completionCaveat;
581934
582217
  this._taskMemoryStore.insert({
581935
582218
  sessionId: this._sessionId,
581936
582219
  repoRoot: process.cwd(),
581937
- goal: task.slice(0, 500),
582220
+ goal: persistentTaskGoal.slice(0, 500),
581938
582221
  constraints: [],
581939
582222
  filesTouched: [],
581940
582223
  patches: [],
@@ -581961,7 +582244,7 @@ ${this._completionCaveat}` : this._completionCaveat;
581961
582244
  }
581962
582245
  if (this._proceduralMemoryStore && completed) {
581963
582246
  try {
581964
- const strategy = `Task "${task.slice(0, 100)}" completed using tools: ${(this._toolSequence || []).slice(0, 5).join(" → ")}`;
582247
+ const strategy = `Task "${persistentTaskGoal.slice(0, 100)}" completed using tools: ${(this._toolSequence || []).slice(0, 5).join(" → ")}`;
581965
582248
  this._proceduralMemoryStore.create({
581966
582249
  content: strategy,
581967
582250
  category: "strategy"
@@ -582117,7 +582400,7 @@ ${this._completionCaveat}` : this._completionCaveat;
582117
582400
  // CLEAN task — this file is the prerequisite for ALL future RL/RFT
582118
582401
  // training. Storing the scaffolded version would teach future models
582119
582402
  // to reproduce signpost text as part of their task understanding.
582120
- task: cleanedTask.slice(0, 1e3),
582403
+ task: persistentTaskGoal.slice(0, 1e3),
582121
582404
  outcome: completed ? "pass" : this.aborted ? "aborted" : runStatus === "incomplete_verification" ? "incomplete_verification" : "timeout",
582122
582405
  model: this.backend.model ?? "unknown",
582123
582406
  modelTier: this.options.modelTier ?? "large",
@@ -582290,6 +582573,150 @@ ${marker}` : marker);
582290
582573
  // -------------------------------------------------------------------------
582291
582574
  // Output folding — keep head + tail, omit middle (preserves errors at end)
582292
582575
  // -------------------------------------------------------------------------
582576
+ isEditToolName(name10) {
582577
+ return name10 === "file_edit" || name10 === "file_patch" || name10 === "batch_edit";
582578
+ }
582579
+ extractPrimaryToolPath(args) {
582580
+ if (!args)
582581
+ return "";
582582
+ const direct = args["path"] ?? args["file"] ?? args["filePath"] ?? args["file_path"];
582583
+ if (typeof direct === "string" && direct.trim())
582584
+ return direct.trim();
582585
+ const edits = args["edits"];
582586
+ if (Array.isArray(edits)) {
582587
+ for (const edit of edits) {
582588
+ if (!edit || typeof edit !== "object")
582589
+ continue;
582590
+ const rec = edit;
582591
+ const editPath = rec["path"] ?? rec["file"] ?? rec["filePath"] ?? rec["file_path"];
582592
+ if (typeof editPath === "string" && editPath.trim())
582593
+ return editPath.trim();
582594
+ }
582595
+ }
582596
+ return "";
582597
+ }
582598
+ staleEditTarget(toolName, args) {
582599
+ if (!this.isEditToolName(toolName) || !args)
582600
+ return null;
582601
+ const path12 = this.extractPrimaryToolPath(args);
582602
+ const parts = [];
582603
+ const directKeys = [
582604
+ "old_string",
582605
+ "oldString",
582606
+ "oldText",
582607
+ "search",
582608
+ "expected_old_string",
582609
+ "expectedHash",
582610
+ "expected_hash",
582611
+ "start_line"
582612
+ ];
582613
+ for (const key of directKeys) {
582614
+ const value2 = args[key];
582615
+ if (typeof value2 === "string" || typeof value2 === "number") {
582616
+ parts.push(key + "=" + String(value2));
582617
+ }
582618
+ }
582619
+ const edits = args["edits"];
582620
+ if (Array.isArray(edits)) {
582621
+ for (const edit of edits.slice(0, 6)) {
582622
+ if (!edit || typeof edit !== "object")
582623
+ continue;
582624
+ const rec = edit;
582625
+ const oldValue = rec["old_string"] ?? rec["oldString"] ?? rec["oldText"] ?? rec["search"] ?? rec["expected_old_string"];
582626
+ const editPath = rec["path"] ?? rec["file"] ?? rec["filePath"] ?? rec["file_path"];
582627
+ if (typeof editPath === "string" || typeof oldValue === "string") {
582628
+ parts.push(String(editPath ?? path12) + "=" + String(oldValue ?? ""));
582629
+ }
582630
+ }
582631
+ }
582632
+ if (!path12 && parts.length === 0)
582633
+ return null;
582634
+ const normalized = parts.join("\n").replace(/\s+/g, " ").trim() || JSON.stringify(args).slice(0, 1e3);
582635
+ return {
582636
+ path: path12 || "(unknown)",
582637
+ targetHash: this.quickHash(normalized),
582638
+ preview: normalized.slice(0, 160)
582639
+ };
582640
+ }
582641
+ classifyStaleEditFailure(toolName, result) {
582642
+ if (!this.isEditToolName(toolName) || result.success)
582643
+ return null;
582644
+ const text2 = String((result.error ?? "") + "\n" + (result.output ?? "")).toLowerCase();
582645
+ if (/ambiguous|multiple occurrences|matches more than once/.test(text2))
582646
+ return "ambiguous_old_string";
582647
+ if (/expected[_ -]?hash|hash mismatch|stale hash|beforehash|afterhash/.test(text2))
582648
+ return "stale_expected_hash";
582649
+ if (/expected.*content|content.*did not match|context mismatch|patch failed|hunk failed/.test(text2))
582650
+ return "stale_expected_content";
582651
+ if (/atomic.*abort|batch.*abort|skipped/.test(text2) && toolName === "batch_edit")
582652
+ return "atomic_batch_abort";
582653
+ if (/old[_ -]?string|old text|old content|not found|no occurrences|0 occurrences|could not find/.test(text2))
582654
+ return "stale_old_string";
582655
+ return null;
582656
+ }
582657
+ staleEditFamilyKey(toolName, path12, errorKind, targetHash) {
582658
+ return toolName + ":" + path12 + ":" + errorKind + ":" + targetHash;
582659
+ }
582660
+ staleEditPreflightBlock(toolName, args) {
582661
+ const target = this.staleEditTarget(toolName, args);
582662
+ if (!target)
582663
+ return null;
582664
+ for (const entry of this._staleEditFamilies.values()) {
582665
+ if (entry.tool !== toolName || entry.path !== target.path || entry.targetHash !== target.targetHash)
582666
+ continue;
582667
+ const hasFreshRead = entry.lastReadTurn > entry.lastFailureTurn;
582668
+ const hasFreshMutation = entry.lastMutationTurn > entry.lastFailureTurn;
582669
+ if (entry.count >= 2 && !hasFreshRead && !hasFreshMutation) {
582670
+ return [
582671
+ "[STALE EDIT LOOP BLOCKED] " + toolName + " has already failed " + entry.count + " times for " + entry.path + " (" + entry.errorKind + ").",
582672
+ "Do not retry the same stale edit target again. First file_read the current file content once, then either:",
582673
+ "1. confirm the desired change is already present and run verification,",
582674
+ "2. build a new edit from the exact current text, or",
582675
+ "3. choose a different target if this edit is no longer relevant.",
582676
+ "Previous stale target preview: " + entry.preview
582677
+ ].join("\n");
582678
+ }
582679
+ }
582680
+ return null;
582681
+ }
582682
+ noteStaleEditGuardOutcome(toolName, args, result, turn) {
582683
+ const path12 = this.extractPrimaryToolPath(args);
582684
+ if (toolName === "file_read" && path12 && result.success) {
582685
+ for (const entry of this._staleEditFamilies.values()) {
582686
+ if (entry.path === path12)
582687
+ entry.lastReadTurn = turn;
582688
+ }
582689
+ return;
582690
+ }
582691
+ if (!this.isEditToolName(toolName))
582692
+ return;
582693
+ const target = this.staleEditTarget(toolName, args);
582694
+ if (!target)
582695
+ return;
582696
+ if (result.success && result.mutated !== false) {
582697
+ for (const [key2, entry] of this._staleEditFamilies) {
582698
+ if (entry.path === target.path)
582699
+ this._staleEditFamilies.delete(key2);
582700
+ }
582701
+ return;
582702
+ }
582703
+ const errorKind = this.classifyStaleEditFailure(toolName, result);
582704
+ if (!errorKind)
582705
+ return;
582706
+ const key = this.staleEditFamilyKey(toolName, target.path, errorKind, target.targetHash);
582707
+ const existing = this._staleEditFamilies.get(key);
582708
+ this._staleEditFamilies.set(key, {
582709
+ count: (existing?.count ?? 0) + 1,
582710
+ path: target.path,
582711
+ tool: toolName,
582712
+ errorKind,
582713
+ targetHash: target.targetHash,
582714
+ lastFailureTurn: turn,
582715
+ lastReadTurn: existing?.lastReadTurn ?? -1,
582716
+ lastMutationTurn: existing?.lastMutationTurn ?? -1,
582717
+ preview: target.preview
582718
+ });
582719
+ }
582293
582720
  /**
582294
582721
  * Quick non-cryptographic hash for Memex experience IDs.
582295
582722
  * Produces a short hex string (8 chars) from input.
@@ -654399,6 +654826,7 @@ Call task_complete with the JSON array when done.`,
654399
654826
  const runner = new AgenticRunner(backend, {
654400
654827
  maxTurns: 5,
654401
654828
  // Evaluators are very focused — 5 turns max
654829
+ artifactMode: "internal",
654402
654830
  maxTokens: 4096,
654403
654831
  temperature: 0,
654404
654832
  // Deterministic scoring
@@ -656908,6 +657336,7 @@ var init_dmn_engine = __esm({
656908
657336
  const runner = new AgenticRunner(backend, {
656909
657337
  maxTurns: 15,
656910
657338
  // DMN is lightweight — don't burn too many turns
657339
+ artifactMode: "internal",
656911
657340
  maxTokens: 8192,
656912
657341
  temperature: 0.4,
656913
657342
  // Slightly creative for exploratory reasoning
@@ -657205,6 +657634,7 @@ If decision is GO, selectedTask MUST be a fully-populated object (NEVER null)
657205
657634
  const runner = new AgenticRunner(backend, {
657206
657635
  maxTurns: 8,
657207
657636
  // Brain regions are fast, focused
657637
+ artifactMode: "internal",
657208
657638
  maxTokens: 4096,
657209
657639
  temperature: role === "amygdala" ? 0.2 : role === "pfc_planner" ? 0.5 : 0.3,
657210
657640
  requestTimeoutMs: this.config.timeoutMs,
@@ -657791,6 +658221,7 @@ var init_emotion_engine = __esm({
657791
658221
  );
657792
658222
  const runner = new AgenticRunner(backend, {
657793
658223
  maxTurns: 1,
658224
+ artifactMode: "internal",
657794
658225
  maxTokens: 64,
657795
658226
  temperature: 0.9,
657796
658227
  // High temperature for creative expression
@@ -657874,6 +658305,7 @@ var init_emotion_engine = __esm({
657874
658305
  );
657875
658306
  const runner = new AgenticRunner(backend, {
657876
658307
  maxTurns: 3,
658308
+ artifactMode: "internal",
657877
658309
  maxTokens: 512,
657878
658310
  temperature: 0.35,
657879
658311
  requestTimeoutMs: 2e4,
@@ -661267,6 +661699,38 @@ ${artifact.personaContext.slice(0, 3e3)}
661267
661699
  ""
661268
661700
  ].join("\n");
661269
661701
  }
661702
+ function daydreamExtractionFailed(artifact) {
661703
+ const hasStructuredExtraction = artifact.tagging.length > 0 || artifact.summation.length > 0 || artifact.titling.length > 0 || artifact.extraction.length > 0 || artifact.linking.length > 0 || artifact.extractionFollowups.length > 0;
661704
+ if (hasStructuredExtraction) return false;
661705
+ return artifact.corpus.fallbacks.some(
661706
+ (fallback) => /structured extraction unavailable|timeout|no valid json|backend unavailable/i.test(
661707
+ fallback
661708
+ )
661709
+ );
661710
+ }
661711
+ function formatTelegramChannelDaydreamMetadataMarkdown(artifact) {
661712
+ return [
661713
+ `# Telegram Channel Daydream ${artifact.id}`,
661714
+ "",
661715
+ `Generated: ${artifact.generatedAt}`,
661716
+ `Session: ${artifact.sessionKey}`,
661717
+ `Chat: ${artifact.chatTitle || artifact.chatId} (${artifact.chatType})`,
661718
+ "Mode: metadata-only; structured extraction did not complete.",
661719
+ "",
661720
+ "## Reflection Corpus",
661721
+ artifact.corpus.stats ? [
661722
+ `- retained messages: ${artifact.corpus.stats.retainedMessages}`,
661723
+ `- candidate episodes: ${artifact.corpus.stats.candidateEpisodes}`,
661724
+ `- selected episodes: ${artifact.corpus.stats.selectedEpisodes}`,
661725
+ `- graph walk: ${artifact.corpus.stats.graphNodesVisited} node(s), ${artifact.corpus.stats.graphEdgesTraversed} edge(s)`
661726
+ ].join("\n") : "- Not available",
661727
+ artifact.corpus.fallbacks.length ? `- fallbacks: ${artifact.corpus.fallbacks.join("; ")}` : "- fallbacks: none",
661728
+ "",
661729
+ "## Context Notes",
661730
+ artifact.contextEngineeringNotes.length ? artifact.contextEngineeringNotes.map((line) => `- ${line}`).join("\n") : "- None",
661731
+ ""
661732
+ ].join("\n");
661733
+ }
661270
661734
  function pruneTelegramChannelDaydreams(repoRoot, sessionKey, keep = DAYDREAM_ARTIFACT_RETENTION_LIMIT) {
661271
661735
  const dir = sessionDir(repoRoot, sessionKey);
661272
661736
  if (!existsSync144(dir)) return 0;
@@ -661292,7 +661756,7 @@ function writeTelegramChannelDaydream(repoRoot, artifact) {
661292
661756
  writeFileSync76(jsonPath, JSON.stringify(artifact, null, 2) + "\n", "utf8");
661293
661757
  writeFileSync76(
661294
661758
  markdownPath,
661295
- formatTelegramChannelDaydreamMarkdown(artifact),
661759
+ daydreamExtractionFailed(artifact) ? formatTelegramChannelDaydreamMetadataMarkdown(artifact) : formatTelegramChannelDaydreamMarkdown(artifact),
661296
661760
  "utf8"
661297
661761
  );
661298
661762
  pruneTelegramChannelDaydreams(repoRoot, artifact.sessionKey);
@@ -661659,7 +662123,8 @@ async function buildTelegramReflectionCorpus(options2) {
661659
662123
  maxDepth: 2,
661660
662124
  maxVisitedNodes: 72,
661661
662125
  maxTraversedEdges: 180,
661662
- maxSourceEpisodes: 100
662126
+ maxSourceEpisodes: 100,
662127
+ allowedSourceEpisodeIds: candidateEpisodes.map((episode) => episode.id)
661663
662128
  }
661664
662129
  );
661665
662130
  if (graphWalk.seed) {
@@ -661673,7 +662138,8 @@ async function buildTelegramReflectionCorpus(options2) {
661673
662138
  maxDepth: 3,
661674
662139
  maxVisitedNodes: 96,
661675
662140
  maxTraversedEdges: 240,
661676
- maxSourceEpisodes: 120
662141
+ maxSourceEpisodes: 120,
662142
+ allowedSourceEpisodeIds: candidateEpisodes.map((episode) => episode.id)
661677
662143
  }
661678
662144
  );
661679
662145
  if (deeper.seed && deeper.sourceEpisodeIds.length >= graphWalk.sourceEpisodeIds.length) graphWalk = deeper;
@@ -663173,6 +663639,20 @@ function formatModelBytes(bytes) {
663173
663639
  }
663174
663640
  return `${value2 >= 10 || unit === 0 ? value2.toFixed(0) : value2.toFixed(1)} ${units[unit]}`;
663175
663641
  }
663642
+ function telegramMediaContextEvidenceTools(mediaContext) {
663643
+ const tools = [];
663644
+ if (/\[Vision analysis of image|Moondream|auxiliary_vision|vision model/i.test(
663645
+ mediaContext
663646
+ ))
663647
+ tools.push("vision_ingress");
663648
+ if (/\[OCR Text from image|ocr_image_advanced|Advanced text extraction|OCR:/i.test(
663649
+ mediaContext
663650
+ ))
663651
+ tools.push("ocr_ingress");
663652
+ if (/voice message transcribed|transcription/i.test(mediaContext))
663653
+ tools.push("transcribe_ingress");
663654
+ return tools;
663655
+ }
663176
663656
  function cleanTelegramDecisionNote(value2, maxLength = 260) {
663177
663657
  if (typeof value2 !== "string") return void 0;
663178
663658
  const clean5 = stripTelegramHiddenThinking(value2).replace(/\s+/g, " ").trim();
@@ -667351,7 +667831,7 @@ ${message2}`)
667351
667831
  if (isGroup) {
667352
667832
  this.markLastTelegramUserMessageMode(msg, "steering");
667353
667833
  } else {
667354
- this.recordTelegramUserMessage(msg, "steering", context2);
667834
+ this.recordTelegramUserMessage(msg, "steering");
667355
667835
  }
667356
667836
  this.enqueueTelegramSubAgentContext(
667357
667837
  sessionKey,
@@ -670123,7 +670603,11 @@ ${mediaContext}` : ""
670123
670603
  recordTelegramUserMessage(msg, mode, textOverride) {
670124
670604
  const sessionKey = this.sessionKeyForMessage(msg);
670125
670605
  const mediaSummary = summarizeTelegramMessageAttachments(msg);
670126
- const text2 = normalizeTelegramOutboundLinks((textOverride ?? msg.text ?? "").trim()) || mediaSummary || "[non-text Telegram message]";
670606
+ const overrideText = (textOverride ?? "").trim();
670607
+ const useOverride = textOverride !== void 0 && !_TelegramBridge.isSyntheticMemoryText(overrideText);
670608
+ const text2 = normalizeTelegramOutboundLinks(
670609
+ (useOverride ? overrideText : msg.text ?? "").trim()
670610
+ ) || mediaSummary || "[non-text Telegram message]";
670127
670611
  this.ensureTelegramConversationLoaded(sessionKey);
670128
670612
  const history = this.chatHistory.get(sessionKey) ?? [];
670129
670613
  const existing = Number.isFinite(msg.messageId) ? history.find(
@@ -670132,7 +670616,7 @@ ${mediaContext}` : ""
670132
670616
  if (existing) {
670133
670617
  if (existing.mode === "ambient" || mode !== "ambient")
670134
670618
  existing.mode = mode;
670135
- if (textOverride !== void 0 || !existing.text || existing.text === "[non-text Telegram message]") {
670619
+ if (useOverride || !existing.text || existing.text === "[non-text Telegram message]" || _TelegramBridge.isSyntheticMemoryText(existing.text)) {
670136
670620
  existing.text = text2;
670137
670621
  }
670138
670622
  existing.speaker = existing.speaker || telegramSpeakerLabel(msg);
@@ -670544,6 +671028,8 @@ ${mediaContext}` : ""
670544
671028
  this.chatParticipants.set(sessionKey, participants);
670545
671029
  }
670546
671030
  updateTelegramAssociativeMemory(sessionKey, entry) {
671031
+ const cleanText2 = stripTelegramHiddenThinking(entry.text || "").replace(/\s+/g, " ").trim();
671032
+ if (!cleanText2 || _TelegramBridge.isSyntheticMemoryText(cleanText2)) return;
670547
671033
  const memory = this.telegramAssociativeMemoryForSession(sessionKey);
670548
671034
  const now2 = entry.ts ?? Date.now();
670549
671035
  memory.updatedAt = now2;
@@ -670760,11 +671246,14 @@ ${mediaContext}` : ""
670760
671246
  /Telegram response contract/,
670761
671247
  /Telegram link integrity contract/
670762
671248
  ];
671249
+ static isSyntheticMemoryText(text2) {
671250
+ const clean5 = String(text2 || "").replace(/\s+/g, " ").trim();
671251
+ return _TelegramBridge.SYNTHETIC_MEMORY_PATTERNS.some((p2) => p2.test(clean5));
671252
+ }
670763
671253
  updateTelegramMemoryCards(sessionKey, entry) {
670764
671254
  const text2 = stripTelegramHiddenThinking(entry.text || "").replace(/\s+/g, " ").trim();
670765
671255
  if (!text2 || text2.length < 3) return;
670766
- if (_TelegramBridge.SYNTHETIC_MEMORY_PATTERNS.some((p2) => p2.test(text2)))
670767
- return;
671256
+ if (_TelegramBridge.isSyntheticMemoryText(text2)) return;
670768
671257
  if (entry.role === "assistant" && entry.mode && !["chat", "action"].includes(entry.mode))
670769
671258
  return;
670770
671259
  const speaker = telegramHistorySpeaker(entry);
@@ -676627,7 +677116,7 @@ ${creativeWorkspace}` : ""}`;
676627
677116
  if (result.status === "incomplete_verification") {
676628
677117
  subAgent.completionBoundarySeen = false;
676629
677118
  }
676630
- return selectTelegramFinalResponse({
677119
+ const finalResponse = selectTelegramFinalResponse({
676631
677120
  visibleReplyText: subAgent.visibleReplyText,
676632
677121
  assistantText: subAgent.assistantText,
676633
677122
  streamText: subAgent.streamText,
@@ -676635,6 +677124,26 @@ ${creativeWorkspace}` : ""}`;
676635
677124
  summary: result.summary,
676636
677125
  completionBoundarySeen: subAgent.completionBoundarySeen
676637
677126
  });
677127
+ const scrutinyText = [finalResponse, result.summary].filter(Boolean).join("\n");
677128
+ const violations = auditPerceptionClaims(scrutinyText, {
677129
+ toolsUsed: [
677130
+ ...subAgent.successfulToolNamesThisRun,
677131
+ ...telegramMediaContextEvidenceTools(mediaContext)
677132
+ ]
677133
+ });
677134
+ if (violations.length > 0) {
677135
+ this.subAgentViewCallbacks?.onWrite(
677136
+ subAgent.viewId,
677137
+ confabulationDirective(violations)
677138
+ );
677139
+ const needed = [...new Set(violations.map((v) => v.requiredTool))].join(
677140
+ "; "
677141
+ );
677142
+ return finalResponse ? `${finalResponse}
677143
+
677144
+ Verification note: I have not actually run ${needed} for the unsupported action claim above.` : `I have not actually run ${needed} yet, so I cannot verify that claim.`;
677145
+ }
677146
+ return finalResponse;
676638
677147
  }
676639
677148
  telegramScopedMemoryPrefix(chatId) {
676640
677149
  const raw = chatId === void 0 ? "unknown" : String(chatId);