@kl-c/matrixos 0.1.6 → 0.1.7

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/cli/index.js CHANGED
@@ -2145,7 +2145,7 @@ var package_default;
2145
2145
  var init_package = __esm(() => {
2146
2146
  package_default = {
2147
2147
  name: "@kl-c/matrixos",
2148
- version: "0.1.6",
2148
+ version: "0.1.7",
2149
2149
  description: "MaTrixOS \u2014 Agentic OS for OpenCode. Personalizable, communicating, self-improving, resilient.",
2150
2150
  main: "./dist/index.js",
2151
2151
  types: "dist/index.d.ts",
@@ -122361,6 +122361,7 @@ init_zod();
122361
122361
  var WatchdogConfigSchema = object({
122362
122362
  enabled: boolean2().default(true),
122363
122363
  doomLoopThreshold: number2().int().positive().max(20).default(3),
122364
+ softDoomLoopThreshold: number2().int().positive().max(20).default(5),
122364
122365
  stuckTimeoutSec: number2().int().positive().max(600).default(90),
122365
122366
  auditLogPath: string2().optional(),
122366
122367
  recoveryStrategy: _enum2(["nudge", "fallback", "restart", "notify", "safe"]).default("nudge"),
@@ -2145,7 +2145,7 @@ var package_default;
2145
2145
  var init_package = __esm(() => {
2146
2146
  package_default = {
2147
2147
  name: "@kl-c/matrixos",
2148
- version: "0.1.6",
2148
+ version: "0.1.7",
2149
2149
  description: "MaTrixOS \u2014 Agentic OS for OpenCode. Personalizable, communicating, self-improving, resilient.",
2150
2150
  main: "./dist/index.js",
2151
2151
  types: "dist/index.d.ts",
@@ -122416,6 +122416,7 @@ init_zod();
122416
122416
  var WatchdogConfigSchema = object({
122417
122417
  enabled: boolean2().default(true),
122418
122418
  doomLoopThreshold: number2().int().positive().max(20).default(3),
122419
+ softDoomLoopThreshold: number2().int().positive().max(20).default(5),
122419
122420
  stuckTimeoutSec: number2().int().positive().max(600).default(90),
122420
122421
  auditLogPath: string2().optional(),
122421
122422
  recoveryStrategy: _enum2(["nudge", "fallback", "restart", "notify", "safe"]).default("nudge"),
@@ -2449,6 +2449,7 @@ export declare const OhMyOpenCodeConfigSchema: z.ZodObject<{
2449
2449
  watchdog: z.ZodOptional<z.ZodObject<{
2450
2450
  enabled: z.ZodDefault<z.ZodBoolean>;
2451
2451
  doomLoopThreshold: z.ZodDefault<z.ZodNumber>;
2452
+ softDoomLoopThreshold: z.ZodDefault<z.ZodNumber>;
2452
2453
  stuckTimeoutSec: z.ZodDefault<z.ZodNumber>;
2453
2454
  auditLogPath: z.ZodOptional<z.ZodString>;
2454
2455
  recoveryStrategy: z.ZodDefault<z.ZodEnum<{
@@ -6,6 +6,7 @@ import * as z from "zod";
6
6
  export declare const WatchdogConfigSchema: z.ZodObject<{
7
7
  enabled: z.ZodDefault<z.ZodBoolean>;
8
8
  doomLoopThreshold: z.ZodDefault<z.ZodNumber>;
9
+ softDoomLoopThreshold: z.ZodDefault<z.ZodNumber>;
9
10
  stuckTimeoutSec: z.ZodDefault<z.ZodNumber>;
10
11
  auditLogPath: z.ZodOptional<z.ZodString>;
11
12
  recoveryStrategy: z.ZodDefault<z.ZodEnum<{
@@ -0,0 +1,15 @@
1
+ export type AntiLoopPattern = "doom_loop" | "soft_doom_loop" | "stuck" | "zombie_session" | "hook_recursion" | "agent_avoidance" | "context_bloat" | "runaway_task";
2
+ export interface AntiLoopEvent {
3
+ timestamp: string;
4
+ sessionId?: string;
5
+ pattern: AntiLoopPattern;
6
+ agent?: string;
7
+ toolName?: string;
8
+ detail: string;
9
+ actionTaken: string;
10
+ outcome?: string;
11
+ }
12
+ export interface AntiLoopLogger {
13
+ log(event: Omit<AntiLoopEvent, "timestamp">): void;
14
+ }
15
+ export declare function createAntiLoopLogger(logsDir: string): AntiLoopLogger;
@@ -1,49 +1,28 @@
1
- /**
2
- * Agent-level anti-loop watchdog (§4.12).
3
- *
4
- * Attaches to `tool.execute.after` to track tool call signatures and detect
5
- * doom loops (same tool ≥3x identical). Also monitors a stuck timer (90s
6
- * default) since the last meaningful tool call.
7
- *
8
- * Recovery escalation (CDC §4.12.3):
9
- * Level 1 — Nudge: inject a message telling the agent it appears stuck
10
- * Level 2 — Fallback: switch model (delegates to runtime-fallback)
11
- * Level 3 — Restart: kill + restart session (future phase)
12
- *
13
- * The watchdog is opt-in via `watchdog.enabled` in the config.
14
- */
15
1
  import type { WatchdogConfig } from "../../config/schema/watchdog";
16
2
  export interface ToolCallRecord {
17
- /** Tool name, e.g. "bash", "edit", "read". */
18
3
  tool: string;
19
- /** Stable signature: toolName + sorted JSON args. */
20
4
  signature: string;
21
- /** Timestamp of the call. */
22
5
  timestamp: number;
23
- /** Whether the tool produced meaningful output (text > 0). */
24
6
  meaningful: boolean;
25
7
  }
8
+ export declare function signaturesSimilar(a: string, b: string): boolean;
26
9
  export interface AgentAntiLoopState {
27
- /** Per-signature consecutive count. */
28
10
  consecutiveBySignature: Map<string, number>;
29
- /** Last meaningful tool timestamp (epoch ms). */
11
+ recentSignatures: string[];
30
12
  lastMeaningfulToolAt: number;
31
- /** Whether a nudge has already been sent (cooldown). */
32
13
  nudged: boolean;
33
- /** Whether fallback has been triggered. */
34
14
  fallbackTriggered: boolean;
15
+ avoidanceCount: number;
16
+ avoidanceEscalated: boolean;
35
17
  }
36
18
  export interface AgentAntiLoopHooks {
37
- /** Call on every tool.execute.after. */
38
19
  onToolAfter(tool: string, args: unknown, output: unknown): void;
39
- /** Call periodically (e.g. on session.idle) to check stuck timer. */
20
+ onAgentMessage(content: string): void;
40
21
  checkStuck(): {
41
22
  stuck: boolean;
42
23
  secondsSinceLastMeaningful: number;
43
24
  };
44
- /** Reset the state (e.g. on session start). */
45
25
  reset(): void;
46
- /** Get the current state (for audit). */
47
26
  getState(): AgentAntiLoopState;
48
27
  }
49
28
  export declare function createAgentAntiLoopHooks(config: WatchdogConfig): AgentAntiLoopHooks;
package/dist/index.js CHANGED
@@ -57285,7 +57285,7 @@ var require_dist10 = __commonJS((exports, module) => {
57285
57285
  var import_node_worker_threads2 = __require("worker_threads");
57286
57286
  var import_collection2 = require_dist3();
57287
57287
  var import_node_events = __require("events");
57288
- var import_node_path126 = __require("path");
57288
+ var import_node_path128 = __require("path");
57289
57289
  var import_node_worker_threads = __require("worker_threads");
57290
57290
  var import_collection = require_dist3();
57291
57291
  var WorkerSendPayloadOp = /* @__PURE__ */ ((WorkerSendPayloadOp2) => {
@@ -57420,18 +57420,18 @@ var require_dist10 = __commonJS((exports, module) => {
57420
57420
  resolveWorkerPath() {
57421
57421
  const path28 = this.options.workerPath;
57422
57422
  if (!path28) {
57423
- return (0, import_node_path126.join)(__dirname, "defaultWorker.js");
57423
+ return (0, import_node_path128.join)(__dirname, "defaultWorker.js");
57424
57424
  }
57425
- if ((0, import_node_path126.isAbsolute)(path28)) {
57425
+ if ((0, import_node_path128.isAbsolute)(path28)) {
57426
57426
  return path28;
57427
57427
  }
57428
57428
  if (/^\.\.?[/\\]/.test(path28)) {
57429
- return (0, import_node_path126.resolve)(path28);
57429
+ return (0, import_node_path128.resolve)(path28);
57430
57430
  }
57431
57431
  try {
57432
57432
  return __require.resolve(path28);
57433
57433
  } catch {
57434
- return (0, import_node_path126.resolve)(path28);
57434
+ return (0, import_node_path128.resolve)(path28);
57435
57435
  }
57436
57436
  }
57437
57437
  async waitForWorkerReady(worker) {
@@ -233073,7 +233073,7 @@ import * as Crypto from "crypto";
233073
233073
  import { once } from "events";
233074
233074
  import { createReadStream, createWriteStream, promises as fs26, WriteStream } from "fs";
233075
233075
  import { tmpdir as tmpdir8 } from "os";
233076
- import { join as join134 } from "path";
233076
+ import { join as join136 } from "path";
233077
233077
  import { Readable as Readable2, Transform } from "stream";
233078
233078
  import { URL as URL2 } from "url";
233079
233079
  async function getMediaKeys(buffer2, mediaType) {
@@ -233151,7 +233151,7 @@ async function generateThumbnail(file2, mediaType, options) {
233151
233151
  };
233152
233152
  }
233153
233153
  } else if (mediaType === "video") {
233154
- const imgFilename = join134(getTmpFilesDirectory(), generateMessageIDV2() + ".jpg");
233154
+ const imgFilename = join136(getTmpFilesDirectory(), generateMessageIDV2() + ".jpg");
233155
233155
  try {
233156
233156
  await extractVideoThumb(file2, imgFilename, "00:00:00", { width: 32, height: 32 });
233157
233157
  const buff = await fs26.readFile(imgFilename);
@@ -233194,7 +233194,7 @@ var import_boom5, getTmpFilesDirectory = () => tmpdir8(), getImageProcessingLibr
233194
233194
  const { stream: stream3 } = await getStream(media);
233195
233195
  logger7?.debug("got stream for raw upload");
233196
233196
  const hasher = Crypto.createHash("sha256");
233197
- const filePath = join134(tmpdir8(), mediaType + generateMessageIDV2());
233197
+ const filePath = join136(tmpdir8(), mediaType + generateMessageIDV2());
233198
233198
  const fileWriteStream = createWriteStream(filePath);
233199
233199
  let fileLength = 0;
233200
233200
  try {
@@ -233334,12 +233334,12 @@ var import_boom5, getTmpFilesDirectory = () => tmpdir8(), getImageProcessingLibr
233334
233334
  logger7?.debug("fetched media stream");
233335
233335
  const mediaKey = Crypto.randomBytes(32);
233336
233336
  const { cipherKey, iv, macKey } = await getMediaKeys(mediaKey, mediaType);
233337
- const encFilePath = join134(getTmpFilesDirectory(), mediaType + generateMessageIDV2() + "-enc");
233337
+ const encFilePath = join136(getTmpFilesDirectory(), mediaType + generateMessageIDV2() + "-enc");
233338
233338
  const encFileWriteStream = createWriteStream(encFilePath);
233339
233339
  let originalFileStream;
233340
233340
  let originalFilePath;
233341
233341
  if (saveOriginalFileIfRequired) {
233342
- originalFilePath = join134(getTmpFilesDirectory(), mediaType + generateMessageIDV2() + "-original");
233342
+ originalFilePath = join136(getTmpFilesDirectory(), mediaType + generateMessageIDV2() + "-original");
233343
233343
  originalFileStream = createWriteStream(originalFilePath);
233344
233344
  }
233345
233345
  let fileLength = 0;
@@ -242744,7 +242744,7 @@ var init_auth_utils = __esm(() => {
242744
242744
 
242745
242745
  // node_modules/.bun/@whiskeysockets+baileys@7.0.0-rc13+13d21a63c79191a3/node_modules/@whiskeysockets/baileys/lib/Utils/use-multi-file-auth-state.js
242746
242746
  import { mkdir as mkdir11, readFile as readFile14, stat as stat8, unlink as unlink3, writeFile as writeFile4 } from "fs/promises";
242747
- import { join as join135 } from "path";
242747
+ import { join as join137 } from "path";
242748
242748
  var fileLocks, getFileLock = (path28) => {
242749
242749
  let mutex = fileLocks.get(path28);
242750
242750
  if (!mutex) {
@@ -242754,7 +242754,7 @@ var fileLocks, getFileLock = (path28) => {
242754
242754
  return mutex;
242755
242755
  }, useMultiFileAuthState = async (folder) => {
242756
242756
  const writeData = async (data3, file2) => {
242757
- const filePath = join135(folder, fixFileName(file2));
242757
+ const filePath = join137(folder, fixFileName(file2));
242758
242758
  const mutex = getFileLock(filePath);
242759
242759
  return mutex.acquire().then(async (release2) => {
242760
242760
  try {
@@ -242766,7 +242766,7 @@ var fileLocks, getFileLock = (path28) => {
242766
242766
  };
242767
242767
  const readData = async (file2) => {
242768
242768
  try {
242769
- const filePath = join135(folder, fixFileName(file2));
242769
+ const filePath = join137(folder, fixFileName(file2));
242770
242770
  const mutex = getFileLock(filePath);
242771
242771
  return await mutex.acquire().then(async (release2) => {
242772
242772
  try {
@@ -242782,7 +242782,7 @@ var fileLocks, getFileLock = (path28) => {
242782
242782
  };
242783
242783
  const removeData = async (file2) => {
242784
242784
  try {
242785
- const filePath = join135(folder, fixFileName(file2));
242785
+ const filePath = join137(folder, fixFileName(file2));
242786
242786
  const mutex = getFileLock(filePath);
242787
242787
  return mutex.acquire().then(async (release2) => {
242788
242788
  try {
@@ -263753,7 +263753,7 @@ var require_thread_stream = __commonJS((exports, module) => {
263753
263753
  var { version: version2 } = require_package3();
263754
263754
  var { EventEmitter: EventEmitter4 } = __require("events");
263755
263755
  var { Worker } = __require("worker_threads");
263756
- var { join: join136 } = __require("path");
263756
+ var { join: join138 } = __require("path");
263757
263757
  var { pathToFileURL: pathToFileURL2 } = __require("url");
263758
263758
  var { wait: wait2 } = require_wait2();
263759
263759
  var {
@@ -263796,7 +263796,7 @@ var require_thread_stream = __commonJS((exports, module) => {
263796
263796
  function createWorker(stream3, opts) {
263797
263797
  const { filename, workerData } = opts;
263798
263798
  const bundlerOverrides = "__bundlerPathsOverrides" in globalThis ? globalThis.__bundlerPathsOverrides : {};
263799
- const toExecute = bundlerOverrides["thread-stream-worker"] || join136(__dirname, "lib", "worker.js");
263799
+ const toExecute = bundlerOverrides["thread-stream-worker"] || join138(__dirname, "lib", "worker.js");
263800
263800
  const worker = new Worker(toExecute, {
263801
263801
  ...opts.workerOpts,
263802
263802
  trackUnmanagedFds: false,
@@ -264197,7 +264197,7 @@ var require_transport = __commonJS((exports, module) => {
264197
264197
  var __dirname = "/home/shiro/MaTrixOS/node_modules/.bun/pino@9.14.0/node_modules/pino/lib";
264198
264198
  var { createRequire: createRequire4 } = __require("module");
264199
264199
  var getCallers = require_caller();
264200
- var { join: join136, isAbsolute: isAbsolute21, sep: sep3 } = __require("path");
264200
+ var { join: join138, isAbsolute: isAbsolute21, sep: sep3 } = __require("path");
264201
264201
  var sleep3 = require_atomic_sleep();
264202
264202
  var onExit = require_on_exit_leak_free();
264203
264203
  var ThreadStream = require_thread_stream();
@@ -264260,7 +264260,7 @@ var require_transport = __commonJS((exports, module) => {
264260
264260
  throw new Error("only one of target or targets can be specified");
264261
264261
  }
264262
264262
  if (targets) {
264263
- target = bundlerOverrides["pino-worker"] || join136(__dirname, "worker.js");
264263
+ target = bundlerOverrides["pino-worker"] || join138(__dirname, "worker.js");
264264
264264
  options.targets = targets.filter((dest) => dest.target).map((dest) => {
264265
264265
  return {
264266
264266
  ...dest,
@@ -264277,7 +264277,7 @@ var require_transport = __commonJS((exports, module) => {
264277
264277
  });
264278
264278
  });
264279
264279
  } else if (pipeline3) {
264280
- target = bundlerOverrides["pino-worker"] || join136(__dirname, "worker.js");
264280
+ target = bundlerOverrides["pino-worker"] || join138(__dirname, "worker.js");
264281
264281
  options.pipelines = [pipeline3.map((dest) => {
264282
264282
  return {
264283
264283
  ...dest,
@@ -264299,7 +264299,7 @@ var require_transport = __commonJS((exports, module) => {
264299
264299
  return origin;
264300
264300
  }
264301
264301
  if (origin === "pino/file") {
264302
- return join136(__dirname, "..", "file.js");
264302
+ return join138(__dirname, "..", "file.js");
264303
264303
  }
264304
264304
  let fixTarget2;
264305
264305
  for (const filePath of callers) {
@@ -265237,7 +265237,7 @@ var require_safe_stable_stringify = __commonJS((exports, module) => {
265237
265237
  return circularValue;
265238
265238
  }
265239
265239
  let res = "";
265240
- let join136 = ",";
265240
+ let join138 = ",";
265241
265241
  const originalIndentation = indentation;
265242
265242
  if (Array.isArray(value)) {
265243
265243
  if (value.length === 0) {
@@ -265251,7 +265251,7 @@ var require_safe_stable_stringify = __commonJS((exports, module) => {
265251
265251
  indentation += spacer;
265252
265252
  res += `
265253
265253
  ${indentation}`;
265254
- join136 = `,
265254
+ join138 = `,
265255
265255
  ${indentation}`;
265256
265256
  }
265257
265257
  const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
@@ -265259,13 +265259,13 @@ ${indentation}`;
265259
265259
  for (;i < maximumValuesToStringify - 1; i++) {
265260
265260
  const tmp2 = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
265261
265261
  res += tmp2 !== undefined ? tmp2 : "null";
265262
- res += join136;
265262
+ res += join138;
265263
265263
  }
265264
265264
  const tmp = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
265265
265265
  res += tmp !== undefined ? tmp : "null";
265266
265266
  if (value.length - 1 > maximumBreadth) {
265267
265267
  const removedKeys = value.length - maximumBreadth - 1;
265268
- res += `${join136}"... ${getItemCount(removedKeys)} not stringified"`;
265268
+ res += `${join138}"... ${getItemCount(removedKeys)} not stringified"`;
265269
265269
  }
265270
265270
  if (spacer !== "") {
265271
265271
  res += `
@@ -265286,7 +265286,7 @@ ${originalIndentation}`;
265286
265286
  let separator = "";
265287
265287
  if (spacer !== "") {
265288
265288
  indentation += spacer;
265289
- join136 = `,
265289
+ join138 = `,
265290
265290
  ${indentation}`;
265291
265291
  whitespace = " ";
265292
265292
  }
@@ -265300,13 +265300,13 @@ ${indentation}`;
265300
265300
  const tmp = stringifyFnReplacer(key2, value, stack, replacer, spacer, indentation);
265301
265301
  if (tmp !== undefined) {
265302
265302
  res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
265303
- separator = join136;
265303
+ separator = join138;
265304
265304
  }
265305
265305
  }
265306
265306
  if (keyLength > maximumBreadth) {
265307
265307
  const removedKeys = keyLength - maximumBreadth;
265308
265308
  res += `${separator}"...":${whitespace}"${getItemCount(removedKeys)} not stringified"`;
265309
- separator = join136;
265309
+ separator = join138;
265310
265310
  }
265311
265311
  if (spacer !== "" && separator.length > 1) {
265312
265312
  res = `
@@ -265346,7 +265346,7 @@ ${originalIndentation}`;
265346
265346
  }
265347
265347
  const originalIndentation = indentation;
265348
265348
  let res = "";
265349
- let join136 = ",";
265349
+ let join138 = ",";
265350
265350
  if (Array.isArray(value)) {
265351
265351
  if (value.length === 0) {
265352
265352
  return "[]";
@@ -265359,7 +265359,7 @@ ${originalIndentation}`;
265359
265359
  indentation += spacer;
265360
265360
  res += `
265361
265361
  ${indentation}`;
265362
- join136 = `,
265362
+ join138 = `,
265363
265363
  ${indentation}`;
265364
265364
  }
265365
265365
  const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
@@ -265367,13 +265367,13 @@ ${indentation}`;
265367
265367
  for (;i < maximumValuesToStringify - 1; i++) {
265368
265368
  const tmp2 = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
265369
265369
  res += tmp2 !== undefined ? tmp2 : "null";
265370
- res += join136;
265370
+ res += join138;
265371
265371
  }
265372
265372
  const tmp = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
265373
265373
  res += tmp !== undefined ? tmp : "null";
265374
265374
  if (value.length - 1 > maximumBreadth) {
265375
265375
  const removedKeys = value.length - maximumBreadth - 1;
265376
- res += `${join136}"... ${getItemCount(removedKeys)} not stringified"`;
265376
+ res += `${join138}"... ${getItemCount(removedKeys)} not stringified"`;
265377
265377
  }
265378
265378
  if (spacer !== "") {
265379
265379
  res += `
@@ -265386,7 +265386,7 @@ ${originalIndentation}`;
265386
265386
  let whitespace = "";
265387
265387
  if (spacer !== "") {
265388
265388
  indentation += spacer;
265389
- join136 = `,
265389
+ join138 = `,
265390
265390
  ${indentation}`;
265391
265391
  whitespace = " ";
265392
265392
  }
@@ -265395,7 +265395,7 @@ ${indentation}`;
265395
265395
  const tmp = stringifyArrayReplacer(key2, value[key2], stack, replacer, spacer, indentation);
265396
265396
  if (tmp !== undefined) {
265397
265397
  res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
265398
- separator = join136;
265398
+ separator = join138;
265399
265399
  }
265400
265400
  }
265401
265401
  if (spacer !== "" && separator.length > 1) {
@@ -265452,20 +265452,20 @@ ${originalIndentation}`;
265452
265452
  indentation += spacer;
265453
265453
  let res2 = `
265454
265454
  ${indentation}`;
265455
- const join137 = `,
265455
+ const join139 = `,
265456
265456
  ${indentation}`;
265457
265457
  const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
265458
265458
  let i = 0;
265459
265459
  for (;i < maximumValuesToStringify - 1; i++) {
265460
265460
  const tmp2 = stringifyIndent(String(i), value[i], stack, spacer, indentation);
265461
265461
  res2 += tmp2 !== undefined ? tmp2 : "null";
265462
- res2 += join137;
265462
+ res2 += join139;
265463
265463
  }
265464
265464
  const tmp = stringifyIndent(String(i), value[i], stack, spacer, indentation);
265465
265465
  res2 += tmp !== undefined ? tmp : "null";
265466
265466
  if (value.length - 1 > maximumBreadth) {
265467
265467
  const removedKeys = value.length - maximumBreadth - 1;
265468
- res2 += `${join137}"... ${getItemCount(removedKeys)} not stringified"`;
265468
+ res2 += `${join139}"... ${getItemCount(removedKeys)} not stringified"`;
265469
265469
  }
265470
265470
  res2 += `
265471
265471
  ${originalIndentation}`;
@@ -265481,16 +265481,16 @@ ${originalIndentation}`;
265481
265481
  return '"[Object]"';
265482
265482
  }
265483
265483
  indentation += spacer;
265484
- const join136 = `,
265484
+ const join138 = `,
265485
265485
  ${indentation}`;
265486
265486
  let res = "";
265487
265487
  let separator = "";
265488
265488
  let maximumPropertiesToStringify = Math.min(keyLength, maximumBreadth);
265489
265489
  if (isTypedArrayWithEntries(value)) {
265490
- res += stringifyTypedArray(value, join136, maximumBreadth);
265490
+ res += stringifyTypedArray(value, join138, maximumBreadth);
265491
265491
  keys = keys.slice(value.length);
265492
265492
  maximumPropertiesToStringify -= value.length;
265493
- separator = join136;
265493
+ separator = join138;
265494
265494
  }
265495
265495
  if (deterministic) {
265496
265496
  keys = sort(keys, comparator);
@@ -265501,13 +265501,13 @@ ${indentation}`;
265501
265501
  const tmp = stringifyIndent(key2, value[key2], stack, spacer, indentation);
265502
265502
  if (tmp !== undefined) {
265503
265503
  res += `${separator}${strEscape(key2)}: ${tmp}`;
265504
- separator = join136;
265504
+ separator = join138;
265505
265505
  }
265506
265506
  }
265507
265507
  if (keyLength > maximumBreadth) {
265508
265508
  const removedKeys = keyLength - maximumBreadth;
265509
265509
  res += `${separator}"...": "${getItemCount(removedKeys)} not stringified"`;
265510
- separator = join136;
265510
+ separator = join138;
265511
265511
  }
265512
265512
  if (separator !== "") {
265513
265513
  res = `
@@ -266157,7 +266157,7 @@ var init_Defaults = __esm(() => {
266157
266157
  import { createHash as createHash13 } from "crypto";
266158
266158
  import { createWriteStream as createWriteStream2, promises as fs28 } from "fs";
266159
266159
  import { tmpdir as tmpdir9 } from "os";
266160
- import { join as join136 } from "path";
266160
+ import { join as join138 } from "path";
266161
266161
  async function uploadingNecessaryImagesOfProduct(product, waUploadToServer, timeoutMs = 30000) {
266162
266162
  product = {
266163
266163
  ...product,
@@ -266335,7 +266335,7 @@ var import_boom12, parseCatalogNode = (node) => {
266335
266335
  }
266336
266336
  const { stream: stream3 } = await getStream(img);
266337
266337
  const hasher = createHash13("sha256");
266338
- const filePath = join136(tmpdir9(), "img" + generateMessageIDV2());
266338
+ const filePath = join138(tmpdir9(), "img" + generateMessageIDV2());
266339
266339
  const encFileWriteStream = createWriteStream2(filePath);
266340
266340
  for await (const block2 of stream3) {
266341
266341
  hasher.update(block2);
@@ -297183,18 +297183,18 @@ var init_merge = __esm(() => {
297183
297183
  });
297184
297184
 
297185
297185
  // packages/omo-config-core/src/loader/types.ts
297186
- import { existsSync as existsSync113, lstatSync as lstatSync5, readFileSync as readFileSync77 } from "fs";
297186
+ import { existsSync as existsSync114, lstatSync as lstatSync5, readFileSync as readFileSync77 } from "fs";
297187
297187
  var DEFAULT_READ_FILE_SYSTEM;
297188
297188
  var init_types7 = __esm(() => {
297189
297189
  DEFAULT_READ_FILE_SYSTEM = {
297190
- existsSync: existsSync113,
297190
+ existsSync: existsSync114,
297191
297191
  lstatSync: lstatSync5,
297192
297192
  readFileSync: readFileSync77
297193
297193
  };
297194
297194
  });
297195
297195
 
297196
297196
  // packages/omo-config-core/src/loader/paths.ts
297197
- import { dirname as dirname45, isAbsolute as isAbsolute21, join as join137, relative as relative15, resolve as resolve42 } from "path";
297197
+ import { dirname as dirname45, isAbsolute as isAbsolute21, join as join139, relative as relative15, resolve as resolve42 } from "path";
297198
297198
  function containsPath2(parent, child) {
297199
297199
  const pathToChild = relative15(parent, child);
297200
297200
  return pathToChild === "" || !pathToChild.startsWith("..") && !isAbsolute21(pathToChild);
@@ -297203,23 +297203,23 @@ function resolveHomeDir(env2 = process.env) {
297203
297203
  return resolve42(env2.HOME ?? env2.USERPROFILE ?? process.cwd());
297204
297204
  }
297205
297205
  function resolveUserMatrixosConfigPath(env2 = process.env, platform4 = process.platform) {
297206
- return join137(resolveUserMatrixosConfigDirectory(env2, platform4), "omo.jsonc");
297206
+ return join139(resolveUserMatrixosConfigDirectory(env2, platform4), "omo.jsonc");
297207
297207
  }
297208
297208
  function resolveUserMatrixosConfigDirectory(env2 = process.env, platform4 = process.platform) {
297209
297209
  if (platform4 === "win32" && env2.APPDATA !== undefined && env2.APPDATA.length > 0) {
297210
- return join137(env2.APPDATA, "omo");
297210
+ return join139(env2.APPDATA, "omo");
297211
297211
  }
297212
297212
  if (env2.XDG_CONFIG_HOME !== undefined && env2.XDG_CONFIG_HOME.length > 0) {
297213
- return join137(env2.XDG_CONFIG_HOME, "omo");
297213
+ return join139(env2.XDG_CONFIG_HOME, "omo");
297214
297214
  }
297215
- return join137(resolveHomeDir(env2), ".config", "omo");
297215
+ return join139(resolveHomeDir(env2), ".config", "omo");
297216
297216
  }
297217
297217
  function detectUserOmoJsonPath(env2, platform4, fileSystem) {
297218
297218
  const configDir = resolveUserMatrixosConfigDirectory(env2, platform4);
297219
- const jsoncPath = join137(configDir, "omo.jsonc");
297219
+ const jsoncPath = join139(configDir, "omo.jsonc");
297220
297220
  if (fileSystem.existsSync(jsoncPath))
297221
297221
  return jsoncPath;
297222
- const jsonPath = join137(configDir, "omo.json");
297222
+ const jsonPath = join139(configDir, "omo.json");
297223
297223
  return fileSystem.existsSync(jsonPath) ? jsonPath : jsoncPath;
297224
297224
  }
297225
297225
  function isSymlinkedProjectPath(path28, fileSystem) {
@@ -297237,13 +297237,13 @@ function isLoadableProjectConfigFile(path28, fileSystem) {
297237
297237
  return fileSystem.existsSync(path28) && !isSymlinkedProjectPath(path28, fileSystem);
297238
297238
  }
297239
297239
  function detectOmoJsonPath(dir2, fileSystem) {
297240
- const omoDir = join137(dir2, ".omo");
297240
+ const omoDir = join139(dir2, ".omo");
297241
297241
  if (isSymlinkedProjectPath(omoDir, fileSystem))
297242
297242
  return null;
297243
- const jsoncPath = join137(omoDir, "omo.jsonc");
297243
+ const jsoncPath = join139(omoDir, "omo.jsonc");
297244
297244
  if (isLoadableProjectConfigFile(jsoncPath, fileSystem))
297245
297245
  return jsoncPath;
297246
- const jsonPath = join137(omoDir, "omo.json");
297246
+ const jsonPath = join139(omoDir, "omo.json");
297247
297247
  return isLoadableProjectConfigFile(jsonPath, fileSystem) ? jsonPath : null;
297248
297248
  }
297249
297249
  function findProjectConfigPathsFarthestFirst(cwd, homeDir, fileSystem) {
@@ -297504,9 +297504,9 @@ var init_loader3 = __esm(() => {
297504
297504
  // packages/omo-config-core/src/writer/types.ts
297505
297505
  import {
297506
297506
  copyFileSync as copyFileSync3,
297507
- existsSync as existsSync114,
297507
+ existsSync as existsSync115,
297508
297508
  lstatSync as lstatSync6,
297509
- mkdirSync as mkdirSync24,
297509
+ mkdirSync as mkdirSync25,
297510
297510
  readFileSync as readFileSync78,
297511
297511
  readdirSync as readdirSync29,
297512
297512
  renameSync as renameSync10,
@@ -297517,9 +297517,9 @@ var DEFAULT_WRITE_FILE_SYSTEM, MatrixosConfigWriteError;
297517
297517
  var init_types8 = __esm(() => {
297518
297518
  DEFAULT_WRITE_FILE_SYSTEM = {
297519
297519
  copyFileSync: copyFileSync3,
297520
- existsSync: existsSync114,
297520
+ existsSync: existsSync115,
297521
297521
  lstatSync: lstatSync6,
297522
- mkdirSync: mkdirSync24,
297522
+ mkdirSync: mkdirSync25,
297523
297523
  readFileSync: readFileSync78,
297524
297524
  readdirSync: readdirSync29,
297525
297525
  renameSync: renameSync10,
@@ -297544,7 +297544,7 @@ var init_types8 = __esm(() => {
297544
297544
 
297545
297545
  // packages/omo-config-core/src/writer/writer.ts
297546
297546
  import { randomUUID as randomUUID13 } from "crypto";
297547
- import { dirname as dirname46, join as join138 } from "path";
297547
+ import { dirname as dirname46, join as join140 } from "path";
297548
297548
  function backupSuffix() {
297549
297549
  return new Date().toISOString().replace(/[:.]/g, "-");
297550
297550
  }
@@ -297575,13 +297575,13 @@ function resolveWritePath(options) {
297575
297575
  const jsoncPath2 = resolveUserMatrixosConfigPath(options.env, options.platform ?? process.platform);
297576
297576
  if (fileSystem.existsSync(jsoncPath2))
297577
297577
  return jsoncPath2;
297578
- const jsonPath2 = join138(dirname46(jsoncPath2), "omo.json");
297578
+ const jsonPath2 = join140(dirname46(jsoncPath2), "omo.json");
297579
297579
  return fileSystem.existsSync(jsonPath2) ? jsonPath2 : jsoncPath2;
297580
297580
  }
297581
- const jsoncPath = join138(options.projectDir ?? process.cwd(), ".omo", "omo.jsonc");
297581
+ const jsoncPath = join140(options.projectDir ?? process.cwd(), ".omo", "omo.jsonc");
297582
297582
  if (fileSystem.existsSync(jsoncPath))
297583
297583
  return jsoncPath;
297584
- const jsonPath = join138(dirname46(jsoncPath), "omo.json");
297584
+ const jsonPath = join140(dirname46(jsoncPath), "omo.json");
297585
297585
  return fileSystem.existsSync(jsonPath) ? jsonPath : jsoncPath;
297586
297586
  }
297587
297587
  function writeAtomically(path28, content, fileSystem) {
@@ -297693,8 +297693,8 @@ var init_writer2 = __esm(() => {
297693
297693
  });
297694
297694
 
297695
297695
  // packages/omo-config-core/src/generator/mini-os-generator.ts
297696
- import { existsSync as existsSync115, mkdirSync as mkdirSync25, writeFileSync as writeFileSync26 } from "fs";
297697
- import { dirname as dirname47, join as join139 } from "path";
297696
+ import { existsSync as existsSync116, mkdirSync as mkdirSync26, writeFileSync as writeFileSync26 } from "fs";
297697
+ import { dirname as dirname47, join as join141 } from "path";
297698
297698
  function generateMiniOS(options) {
297699
297699
  const fs29 = options.fs ?? defaultFS;
297700
297700
  const profile3 = options.profile;
@@ -297705,29 +297705,29 @@ function generateMiniOS(options) {
297705
297705
  if (!fs29.existsSync(targetDir)) {
297706
297706
  fs29.mkdirSync(targetDir, { recursive: true });
297707
297707
  }
297708
- const pkgPath = join139(targetDir, "package.json");
297708
+ const pkgPath = join141(targetDir, "package.json");
297709
297709
  writeIfMissing(fs29, pkgPath, TPL_PACKAGE_JSON(packageName, displayName, profile3.version, profile3.description, profile3.extends, profile3.baseAgent));
297710
297710
  filesWritten.push(pkgPath);
297711
- const srcDir = join139(targetDir, "src");
297711
+ const srcDir = join141(targetDir, "src");
297712
297712
  fs29.mkdirSync(srcDir, { recursive: true });
297713
- const indexPath = join139(srcDir, "index.ts");
297713
+ const indexPath = join141(srcDir, "index.ts");
297714
297714
  writeIfMissing(fs29, indexPath, TPL_INDEX_TS(profile3.name, displayName, profile3.description));
297715
297715
  filesWritten.push(indexPath);
297716
- const binDir = join139(targetDir, "bin");
297716
+ const binDir = join141(targetDir, "bin");
297717
297717
  fs29.mkdirSync(binDir, { recursive: true });
297718
- const binPath = join139(binDir, "matrixos-mini.js");
297718
+ const binPath = join141(binDir, "matrixos-mini.js");
297719
297719
  writeIfMissing(fs29, binPath, TPL_BIN);
297720
297720
  filesWritten.push(binPath);
297721
- const scriptDir = join139(targetDir, "script");
297721
+ const scriptDir = join141(targetDir, "script");
297722
297722
  fs29.mkdirSync(scriptDir, { recursive: true });
297723
- const buildPath = join139(scriptDir, "build.ts");
297723
+ const buildPath = join141(scriptDir, "build.ts");
297724
297724
  writeIfMissing(fs29, buildPath, TPL_BUILD_TS);
297725
297725
  filesWritten.push(buildPath);
297726
297726
  if (Object.keys(profile3.agents).length > 0) {
297727
- const agentsDir = join139(targetDir, "agents");
297727
+ const agentsDir = join141(targetDir, "agents");
297728
297728
  fs29.mkdirSync(agentsDir, { recursive: true });
297729
297729
  for (const [agentName, agentDef] of Object.entries(profile3.agents)) {
297730
- const agentFile = join139(agentsDir, `${agentName}.ts`);
297730
+ const agentFile = join141(agentsDir, `${agentName}.ts`);
297731
297731
  const description2 = agentDef.description ?? `Custom ${displayName} agent: ${agentName}`;
297732
297732
  writeIfMissing(fs29, agentFile, `// ${description2}
297733
297733
  // Generated by MaTrixOS Mini-OS Profile Generator.
@@ -297738,10 +297738,10 @@ export const description = ${JSON.stringify(description2)}
297738
297738
  }
297739
297739
  }
297740
297740
  if (profile3.skills.length > 0) {
297741
- const skillsDir = join139(targetDir, "skills");
297741
+ const skillsDir = join141(targetDir, "skills");
297742
297742
  fs29.mkdirSync(skillsDir, { recursive: true });
297743
297743
  for (const skill2 of profile3.skills) {
297744
- const skillFile = join139(skillsDir, `${skill2}.md`);
297744
+ const skillFile = join141(skillsDir, `${skill2}.md`);
297745
297745
  writeIfMissing(fs29, skillFile, `# ${skill2}
297746
297746
 
297747
297747
  _Skill required by the ${displayName} profile. Implementation goes here._
@@ -297749,12 +297749,12 @@ _Skill required by the ${displayName} profile. Implementation goes here._
297749
297749
  filesWritten.push(skillFile);
297750
297750
  }
297751
297751
  }
297752
- const readmePath = join139(targetDir, "README.md");
297752
+ const readmePath = join141(targetDir, "README.md");
297753
297753
  const agentsList = Object.keys(profile3.agents);
297754
297754
  const skillsList = profile3.skills;
297755
297755
  writeIfMissing(fs29, readmePath, TPL_README(packageName, displayName, profile3.description, profile3.version, profile3.baseAgent, profile3.extends, agentsList, skillsList));
297756
297756
  filesWritten.push(readmePath);
297757
- const agentsMdPath = join139(targetDir, "AGENTS.md");
297757
+ const agentsMdPath = join141(targetDir, "AGENTS.md");
297758
297758
  writeIfMissing(fs29, agentsMdPath, TPL_AGENT_MD(displayName, profile3.description));
297759
297759
  filesWritten.push(agentsMdPath);
297760
297760
  return { packageName, packageDir: targetDir, filesWritten };
@@ -297906,7 +297906,7 @@ This profile is activated when the user mentions the profile name in their reque
297906
297906
  - Profiles can be composed: multiple profiles in \`profiles: [...]\` are merged in order.
297907
297907
  `;
297908
297908
  var init_mini_os_generator = __esm(() => {
297909
- defaultFS = { existsSync: existsSync115, mkdirSync: mkdirSync25, writeFileSync: writeFileSync26 };
297909
+ defaultFS = { existsSync: existsSync116, mkdirSync: mkdirSync26, writeFileSync: writeFileSync26 };
297910
297910
  });
297911
297911
 
297912
297912
  // packages/omo-config-core/src/generator/index.ts
@@ -367932,7 +367932,7 @@ function getCachedVersion(options = {}) {
367932
367932
  // package.json
367933
367933
  var package_default = {
367934
367934
  name: "@kl-c/matrixos",
367935
- version: "0.1.6",
367935
+ version: "0.1.7",
367936
367936
  description: "MaTrixOS \u2014 Agentic OS for OpenCode. Personalizable, communicating, self-improving, resilient.",
367937
367937
  main: "./dist/index.js",
367938
367938
  types: "dist/index.d.ts",
@@ -441310,12 +441310,30 @@ function createPluginInterface(args) {
441310
441310
  }
441311
441311
 
441312
441312
  // packages/omo-opencode/src/hooks/agent-anti-loop/hook.ts
441313
+ function signaturesSimilar(a, b) {
441314
+ if (a === b)
441315
+ return true;
441316
+ const aTool = a.split(":")[0] ?? "";
441317
+ const bTool = b.split(":")[0] ?? "";
441318
+ if (aTool !== bTool)
441319
+ return false;
441320
+ return true;
441321
+ }
441322
+ var AVOIDANCE_PATTERNS = [
441323
+ /\btry\s+(again|a different|another)\b/i,
441324
+ /\bI'?ll\s+(try|attempt|give\s+it\s+another)\b/i,
441325
+ /\blet\s+me\s+try\s+again\b/i,
441326
+ /\bI\s+apologize\b.*\btry\b/i
441327
+ ];
441313
441328
  function createAgentAntiLoopHooks(config) {
441314
441329
  const state3 = {
441315
441330
  consecutiveBySignature: new Map,
441331
+ recentSignatures: [],
441316
441332
  lastMeaningfulToolAt: Date.now(),
441317
441333
  nudged: false,
441318
- fallbackTriggered: false
441334
+ fallbackTriggered: false,
441335
+ avoidanceCount: 0,
441336
+ avoidanceEscalated: false
441319
441337
  };
441320
441338
  function makeSignature(tool3, args) {
441321
441339
  try {
@@ -441341,6 +441359,7 @@ function createAgentAntiLoopHooks(config) {
441341
441359
  const prev = state3.consecutiveBySignature.get(sig) ?? 0;
441342
441360
  const count = prev + 1;
441343
441361
  state3.consecutiveBySignature.set(sig, count);
441362
+ state3.recentSignatures.push(sig);
441344
441363
  if (isMeaningfulOutput(output)) {
441345
441364
  state3.lastMeaningfulToolAt = now;
441346
441365
  state3.nudged = false;
@@ -441349,29 +441368,68 @@ function createAgentAntiLoopHooks(config) {
441349
441368
  if (count >= config.doomLoopThreshold) {
441350
441369
  state3.consecutiveBySignature.set(sig, 0);
441351
441370
  }
441371
+ const similarCount = state3.recentSignatures.filter((s) => signaturesSimilar(s, sig)).length;
441372
+ if (similarCount >= config.softDoomLoopThreshold) {
441373
+ state3.consecutiveBySignature.set(sig, 0);
441374
+ }
441375
+ },
441376
+ onAgentMessage(content) {
441377
+ const isAvoidance = AVOIDANCE_PATTERNS.some((p) => p.test(content));
441378
+ if (isAvoidance) {
441379
+ state3.avoidanceCount += 1;
441380
+ } else {
441381
+ state3.avoidanceCount = 0;
441382
+ }
441352
441383
  },
441353
441384
  checkStuck() {
441354
441385
  const now = Date.now();
441355
441386
  const elapsed = now - state3.lastMeaningfulToolAt;
441356
- const secondsSinceLastMeaningful = Math.floor(elapsed / 1000);
441357
- const stuck = secondsSinceLastMeaningful >= config.stuckTimeoutSec;
441358
- return { stuck, secondsSinceLastMeaningful };
441387
+ return { stuck: elapsed >= config.stuckTimeoutSec * 1000, secondsSinceLastMeaningful: Math.floor(elapsed / 1000) };
441359
441388
  },
441360
441389
  reset() {
441361
441390
  state3.consecutiveBySignature.clear();
441391
+ state3.recentSignatures = [];
441362
441392
  state3.lastMeaningfulToolAt = Date.now();
441363
441393
  state3.nudged = false;
441364
441394
  state3.fallbackTriggered = false;
441395
+ state3.avoidanceCount = 0;
441396
+ state3.avoidanceEscalated = false;
441365
441397
  },
441366
441398
  getState() {
441367
- return { ...state3, consecutiveBySignature: new Map(state3.consecutiveBySignature) };
441399
+ return { ...state3, consecutiveBySignature: new Map(state3.consecutiveBySignature), recentSignatures: [...state3.recentSignatures] };
441400
+ }
441401
+ };
441402
+ }
441403
+
441404
+ // packages/omo-opencode/src/features/anti-loop/logger.ts
441405
+ import { existsSync as existsSync112, mkdirSync as mkdirSync24, appendFileSync as appendFileSync8 } from "fs";
441406
+ import { join as join134 } from "path";
441407
+ function createAntiLoopLogger(logsDir) {
441408
+ const logFile = join134(logsDir, "anti-loop.jsonl");
441409
+ const parentDir = join134(logsDir, "..");
441410
+ if (!existsSync112(logsDir)) {
441411
+ mkdirSync24(logsDir, { recursive: true });
441412
+ }
441413
+ return {
441414
+ log(event) {
441415
+ const entry = {
441416
+ ...event,
441417
+ timestamp: new Date().toISOString()
441418
+ };
441419
+ try {
441420
+ appendFileSync8(logFile, JSON.stringify(entry) + `
441421
+ `, "utf-8");
441422
+ } catch {}
441368
441423
  }
441369
441424
  };
441370
441425
  }
441371
441426
 
441372
441427
  // packages/omo-opencode/src/plugin/agent-anti-loop.ts
441428
+ import { join as join135 } from "path";
441373
441429
  function createAgentAntiLoopWiring(config, deps) {
441374
441430
  const hooks = createAgentAntiLoopHooks(config);
441431
+ const logDir = deps.projectDir ? join135(deps.projectDir, ".matrixos", "logs") : process.cwd();
441432
+ const antiLoopLog = createAntiLoopLogger(logDir);
441375
441433
  return {
441376
441434
  toolExecuteAfter(input) {
441377
441435
  hooks.onToolAfter(input.tool, input.args, input.output);
@@ -441379,25 +441437,38 @@ function createAgentAntiLoopWiring(config, deps) {
441379
441437
  const sig = `${input.tool}:${JSON.stringify(input.args)}`;
441380
441438
  const count = state3.consecutiveBySignature.get(sig) ?? 0;
441381
441439
  if (count === 0 && state3.consecutiveBySignature.has(sig)) {
441382
- deps.log("[agent-anti-loop] doom loop detected", {
441383
- tool: input.tool,
441384
- threshold: config.doomLoopThreshold
441385
- });
441440
+ deps.log("[agent-anti-loop] doom loop detected", { tool: input.tool, threshold: config.doomLoopThreshold });
441441
+ antiLoopLog.log({ sessionId: process.env.OMO_SESSION_ID, pattern: "doom_loop", toolName: input.tool, detail: `${input.tool} called >=${config.doomLoopThreshold}x identical`, actionTaken: "counter_reset" });
441386
441442
  if (deps.injectNudge) {
441387
- deps.injectNudge(`\u26A0\uFE0F You appear to be repeating the same tool (${input.tool}) without progress. Try a different approach.`);
441443
+ deps.injectNudge(`Watchdog: tool ${input.tool} repeated without progress. Try a different approach.`);
441388
441444
  }
441389
441445
  }
441446
+ const similarCount = state3.recentSignatures.filter((s) => s.startsWith(input.tool + ":")).length;
441447
+ if (similarCount >= config.softDoomLoopThreshold) {
441448
+ deps.log("[agent-anti-loop] soft doom loop warning", { tool: input.tool, similarCalls: similarCount });
441449
+ antiLoopLog.log({ sessionId: process.env.OMO_SESSION_ID, pattern: "soft_doom_loop", toolName: input.tool, detail: `${input.tool} called ${similarCount}x with similar args`, actionTaken: "warning" });
441450
+ }
441451
+ },
441452
+ onAgentMessage(content) {
441453
+ hooks.onAgentMessage(content);
441454
+ const state3 = hooks.getState();
441455
+ if (state3.avoidanceCount >= 3 && !state3.avoidanceEscalated) {
441456
+ deps.log("[agent-anti-loop] agent avoidance detected", { avoidanceCount: state3.avoidanceCount });
441457
+ antiLoopLog.log({ sessionId: process.env.OMO_SESSION_ID, pattern: "agent_avoidance", detail: `"I'll try again" pattern ${state3.avoidanceCount}x`, actionTaken: "nudge" });
441458
+ if (deps.injectNudge) {
441459
+ deps.injectNudge("You appear stuck in a loop. Stop and analyze what went wrong before trying again.");
441460
+ }
441461
+ state3.avoidanceEscalated = true;
441462
+ }
441390
441463
  },
441391
441464
  async sessionIdle() {
441392
441465
  const { stuck, secondsSinceLastMeaningful } = hooks.checkStuck();
441393
441466
  if (!stuck)
441394
441467
  return;
441395
- deps.log("[agent-anti-loop] stuck detected", {
441396
- secondsSinceLastMeaningful,
441397
- recoveryStrategy: config.recoveryStrategy
441398
- });
441468
+ deps.log("[agent-anti-loop] stuck detected", { secondsSinceLastMeaningful, recoveryStrategy: config.recoveryStrategy });
441469
+ antiLoopLog.log({ sessionId: process.env.OMO_SESSION_ID, pattern: "stuck", detail: `No progress for ${secondsSinceLastMeaningful}s`, actionTaken: config.recoveryStrategy });
441399
441470
  if (config.recoveryStrategy === "nudge" && deps.injectNudge) {
441400
- deps.injectNudge(`\u26A0\uFE0F No meaningful progress for ${secondsSinceLastMeaningful}s. Try a different approach or ask for help.`);
441471
+ deps.injectNudge(`Watchdog: no progress for ${secondsSinceLastMeaningful}s. Try a different approach.`);
441401
441472
  }
441402
441473
  },
441403
441474
  sessionCreated() {
@@ -443391,6 +443462,7 @@ import * as z54 from "zod";
443391
443462
  var WatchdogConfigSchema = z54.object({
443392
443463
  enabled: z54.boolean().default(true),
443393
443464
  doomLoopThreshold: z54.number().int().positive().max(20).default(3),
443465
+ softDoomLoopThreshold: z54.number().int().positive().max(20).default(5),
443394
443466
  stuckTimeoutSec: z54.number().int().positive().max(600).default(90),
443395
443467
  auditLogPath: z54.string().optional(),
443396
443468
  recoveryStrategy: z54.enum(["nudge", "fallback", "restart", "notify", "safe"]).default("nudge"),
@@ -444168,22 +444240,22 @@ init_plugin_identity();
444168
444240
  // packages/telemetry-core/src/activity-state.ts
444169
444241
  init_atomic_write();
444170
444242
  init_xdg_data_dir();
444171
- import { existsSync as existsSync118, mkdirSync as mkdirSync26, readFileSync as readFileSync80 } from "fs";
444172
- import { basename as basename26, join as join141 } from "path";
444243
+ import { existsSync as existsSync119, mkdirSync as mkdirSync27, readFileSync as readFileSync80 } from "fs";
444244
+ import { basename as basename26, join as join143 } from "path";
444173
444245
  var POSTHOG_ACTIVITY_STATE_FILE = "posthog-activity.json";
444174
444246
  function resolveTelemetryStateDir(product, options = {}) {
444175
444247
  const dataDir = resolveXdgDataDir(product.cacheDirName, {
444176
444248
  env: options.env,
444177
444249
  osProvider: options.osProvider
444178
444250
  });
444179
- const xdgStateDir = options.env?.XDG_DATA_HOME === undefined ? undefined : join141(options.env.XDG_DATA_HOME, product.cacheDirName);
444251
+ const xdgStateDir = options.env?.XDG_DATA_HOME === undefined ? undefined : join143(options.env.XDG_DATA_HOME, product.cacheDirName);
444180
444252
  if (dataDir === xdgStateDir || xdgStateDir === undefined && basename26(dataDir) === product.cacheDirName) {
444181
444253
  return dataDir;
444182
444254
  }
444183
- return join141(dataDir, product.cacheDirName);
444255
+ return join143(dataDir, product.cacheDirName);
444184
444256
  }
444185
444257
  function getTelemetryActivityStateFilePath(stateDir) {
444186
- return join141(stateDir, POSTHOG_ACTIVITY_STATE_FILE);
444258
+ return join143(stateDir, POSTHOG_ACTIVITY_STATE_FILE);
444187
444259
  }
444188
444260
  function getDailyActiveCaptureState(input2) {
444189
444261
  const state3 = readPostHogActivityState(input2.stateDir, input2.diagnostics);
@@ -444208,7 +444280,7 @@ function isPostHogActivityState(value) {
444208
444280
  }
444209
444281
  function readPostHogActivityState(stateDir, diagnostics) {
444210
444282
  const stateFilePath = getTelemetryActivityStateFilePath(stateDir);
444211
- if (!existsSync118(stateFilePath)) {
444283
+ if (!existsSync119(stateFilePath)) {
444212
444284
  return {};
444213
444285
  }
444214
444286
  try {
@@ -444231,7 +444303,7 @@ function readPostHogActivityState(stateDir, diagnostics) {
444231
444303
  function writePostHogActivityState(stateDir, nextState, diagnostics) {
444232
444304
  const stateFilePath = getTelemetryActivityStateFilePath(stateDir);
444233
444305
  try {
444234
- mkdirSync26(stateDir, { recursive: true });
444306
+ mkdirSync27(stateDir, { recursive: true });
444235
444307
  writeFileAtomically(stateFilePath, `${JSON.stringify(nextState, null, 2)}
444236
444308
  `);
444237
444309
  } catch (error) {
@@ -7091,6 +7091,12 @@
7091
7091
  "exclusiveMinimum": 0,
7092
7092
  "maximum": 20
7093
7093
  },
7094
+ "softDoomLoopThreshold": {
7095
+ "default": 5,
7096
+ "type": "integer",
7097
+ "exclusiveMinimum": 0,
7098
+ "maximum": 20
7099
+ },
7094
7100
  "stuckTimeoutSec": {
7095
7101
  "default": 90,
7096
7102
  "type": "integer",
@@ -7177,6 +7183,7 @@
7177
7183
  "required": [
7178
7184
  "enabled",
7179
7185
  "doomLoopThreshold",
7186
+ "softDoomLoopThreshold",
7180
7187
  "stuckTimeoutSec",
7181
7188
  "recoveryStrategy",
7182
7189
  "heartbeatIntervalSec",
@@ -1,20 +1,17 @@
1
1
  import type { WatchdogConfig } from "../config/schema/watchdog";
2
2
  export interface AgentAntiLoopDeps {
3
- /** Injected logger. */
4
3
  log: (msg: string, meta?: Record<string, unknown>) => void;
5
- /** Injected function to inject a nudge message into the current session. */
6
4
  injectNudge?: (text: string) => Promise<void>;
5
+ projectDir?: string;
7
6
  }
8
7
  export interface AgentAntiLoopWiring {
9
- /** Handler for `tool.execute.after` — tracks tool signatures. */
10
8
  toolExecuteAfter: (input: {
11
9
  tool: string;
12
10
  args: unknown;
13
11
  output: unknown;
14
12
  }) => void;
15
- /** Handler for `session.idle` — checks stuck timer and escalates. */
16
13
  sessionIdle: () => Promise<void>;
17
- /** Reset on session start. */
18
14
  sessionCreated: () => void;
15
+ onAgentMessage: (content: string) => void;
19
16
  }
20
17
  export declare function createAgentAntiLoopWiring(config: WatchdogConfig, deps: AgentAntiLoopDeps): AgentAntiLoopWiring;
package/dist/tui.js CHANGED
@@ -64557,6 +64557,7 @@ var init_watchdog = __esm(() => {
64557
64557
  WatchdogConfigSchema = object({
64558
64558
  enabled: boolean2().default(true),
64559
64559
  doomLoopThreshold: number2().int().positive().max(20).default(3),
64560
+ softDoomLoopThreshold: number2().int().positive().max(20).default(5),
64560
64561
  stuckTimeoutSec: number2().int().positive().max(600).default(90),
64561
64562
  auditLogPath: string2().optional(),
64562
64563
  recoveryStrategy: _enum2(["nudge", "fallback", "restart", "notify", "safe"]).default("nudge"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kl-c/matrixos",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "MaTrixOS — Agentic OS for OpenCode. Personalizable, communicating, self-improving, resilient.",
5
5
  "main": "./dist/index.js",
6
6
  "types": "dist/index.d.ts",