omnius 1.0.87 → 1.0.89

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
@@ -96453,7 +96453,7 @@ var require_md5 = __commonJS({
96453
96453
  if (!_initialized) {
96454
96454
  _init();
96455
96455
  }
96456
- var _state3 = null;
96456
+ var _state4 = null;
96457
96457
  var _input = forge.util.createBuffer();
96458
96458
  var _w = new Array(16);
96459
96459
  var md = {
@@ -96475,7 +96475,7 @@ var require_md5 = __commonJS({
96475
96475
  md.fullMessageLength.push(0);
96476
96476
  }
96477
96477
  _input = forge.util.createBuffer();
96478
- _state3 = {
96478
+ _state4 = {
96479
96479
  h0: 1732584193,
96480
96480
  h1: 4023233417,
96481
96481
  h2: 2562383102,
@@ -96498,7 +96498,7 @@ var require_md5 = __commonJS({
96498
96498
  len[0] = len[1] / 4294967296 >>> 0;
96499
96499
  }
96500
96500
  _input.putBytes(msg);
96501
- _update(_state3, _w, _input);
96501
+ _update(_state4, _w, _input);
96502
96502
  if (_input.read > 2048 || _input.length() === 0) {
96503
96503
  _input.compact();
96504
96504
  }
@@ -96517,10 +96517,10 @@ var require_md5 = __commonJS({
96517
96517
  finalBlock.putInt32Le(bits >>> 0);
96518
96518
  }
96519
96519
  var s2 = {
96520
- h0: _state3.h0,
96521
- h1: _state3.h1,
96522
- h2: _state3.h2,
96523
- h3: _state3.h3
96520
+ h0: _state4.h0,
96521
+ h1: _state4.h1,
96522
+ h2: _state4.h2,
96523
+ h3: _state4.h3
96524
96524
  };
96525
96525
  _update(s2, _w, finalBlock);
96526
96526
  var rval = forge.util.createBuffer();
@@ -97249,7 +97249,7 @@ var require_sha256 = __commonJS({
97249
97249
  if (!_initialized) {
97250
97250
  _init();
97251
97251
  }
97252
- var _state3 = null;
97252
+ var _state4 = null;
97253
97253
  var _input = forge.util.createBuffer();
97254
97254
  var _w = new Array(64);
97255
97255
  var md = {
@@ -97271,7 +97271,7 @@ var require_sha256 = __commonJS({
97271
97271
  md.fullMessageLength.push(0);
97272
97272
  }
97273
97273
  _input = forge.util.createBuffer();
97274
- _state3 = {
97274
+ _state4 = {
97275
97275
  h0: 1779033703,
97276
97276
  h1: 3144134277,
97277
97277
  h2: 1013904242,
@@ -97298,7 +97298,7 @@ var require_sha256 = __commonJS({
97298
97298
  len[0] = len[1] / 4294967296 >>> 0;
97299
97299
  }
97300
97300
  _input.putBytes(msg);
97301
- _update(_state3, _w, _input);
97301
+ _update(_state4, _w, _input);
97302
97302
  if (_input.read > 2048 || _input.length() === 0) {
97303
97303
  _input.compact();
97304
97304
  }
@@ -97321,14 +97321,14 @@ var require_sha256 = __commonJS({
97321
97321
  }
97322
97322
  finalBlock.putInt32(bits);
97323
97323
  var s2 = {
97324
- h0: _state3.h0,
97325
- h1: _state3.h1,
97326
- h2: _state3.h2,
97327
- h3: _state3.h3,
97328
- h4: _state3.h4,
97329
- h5: _state3.h5,
97330
- h6: _state3.h6,
97331
- h7: _state3.h7
97324
+ h0: _state4.h0,
97325
+ h1: _state4.h1,
97326
+ h2: _state4.h2,
97327
+ h3: _state4.h3,
97328
+ h4: _state4.h4,
97329
+ h5: _state4.h5,
97330
+ h6: _state4.h6,
97331
+ h7: _state4.h7
97332
97332
  };
97333
97333
  _update(s2, _w, finalBlock);
97334
97334
  var rval = forge.util.createBuffer();
@@ -99479,7 +99479,7 @@ var require_sha1 = __commonJS({
99479
99479
  if (!_initialized) {
99480
99480
  _init();
99481
99481
  }
99482
- var _state3 = null;
99482
+ var _state4 = null;
99483
99483
  var _input = forge.util.createBuffer();
99484
99484
  var _w = new Array(80);
99485
99485
  var md = {
@@ -99501,7 +99501,7 @@ var require_sha1 = __commonJS({
99501
99501
  md.fullMessageLength.push(0);
99502
99502
  }
99503
99503
  _input = forge.util.createBuffer();
99504
- _state3 = {
99504
+ _state4 = {
99505
99505
  h0: 1732584193,
99506
99506
  h1: 4023233417,
99507
99507
  h2: 2562383102,
@@ -99525,7 +99525,7 @@ var require_sha1 = __commonJS({
99525
99525
  len[0] = len[1] / 4294967296 >>> 0;
99526
99526
  }
99527
99527
  _input.putBytes(msg);
99528
- _update(_state3, _w, _input);
99528
+ _update(_state4, _w, _input);
99529
99529
  if (_input.read > 2048 || _input.length() === 0) {
99530
99530
  _input.compact();
99531
99531
  }
@@ -99548,11 +99548,11 @@ var require_sha1 = __commonJS({
99548
99548
  }
99549
99549
  finalBlock.putInt32(bits);
99550
99550
  var s2 = {
99551
- h0: _state3.h0,
99552
- h1: _state3.h1,
99553
- h2: _state3.h2,
99554
- h3: _state3.h3,
99555
- h4: _state3.h4
99551
+ h0: _state4.h0,
99552
+ h1: _state4.h1,
99553
+ h2: _state4.h2,
99554
+ h3: _state4.h3,
99555
+ h4: _state4.h4
99556
99556
  };
99557
99557
  _update(s2, _w, finalBlock);
99558
99558
  var rval = forge.util.createBuffer();
@@ -107678,7 +107678,7 @@ var require_sha512 = __commonJS({
107678
107678
  if (!(algorithm in _states)) {
107679
107679
  throw new Error("Invalid SHA-512 algorithm: " + algorithm);
107680
107680
  }
107681
- var _state3 = _states[algorithm];
107681
+ var _state4 = _states[algorithm];
107682
107682
  var _h = null;
107683
107683
  var _input = forge.util.createBuffer();
107684
107684
  var _w = new Array(80);
@@ -107717,9 +107717,9 @@ var require_sha512 = __commonJS({
107717
107717
  md.fullMessageLength.push(0);
107718
107718
  }
107719
107719
  _input = forge.util.createBuffer();
107720
- _h = new Array(_state3.length);
107721
- for (var i2 = 0; i2 < _state3.length; ++i2) {
107722
- _h[i2] = _state3[i2].slice(0);
107720
+ _h = new Array(_state4.length);
107721
+ for (var i2 = 0; i2 < _state4.length; ++i2) {
107722
+ _h[i2] = _state4[i2].slice(0);
107723
107723
  }
107724
107724
  return md;
107725
107725
  };
@@ -282169,7 +282169,7 @@ ${lanes.join("\n")}
282169
282169
  initial
282170
282170
  );
282171
282171
  }
282172
- function appendCommentRange(pos, end, kind, hasTrailingNewLine, _state3, comments = []) {
282172
+ function appendCommentRange(pos, end, kind, hasTrailingNewLine, _state4, comments = []) {
282173
282173
  comments.push({ kind, pos, end, hasTrailingNewLine });
282174
282174
  return comments;
282175
282175
  }
@@ -401925,7 +401925,7 @@ ${lanes.join("\n")}
401925
401925
  function onLeft(next, _workArea, parent2) {
401926
401926
  return maybeEmitExpression(next, parent2, "left");
401927
401927
  }
401928
- function onOperator(operatorToken, _state3, node) {
401928
+ function onOperator(operatorToken, _state4, node) {
401929
401929
  const isCommaOperator = operatorToken.kind !== 28;
401930
401930
  const linesBeforeOperator = getLinesBetweenNodes(node, node.left, operatorToken);
401931
401931
  const linesAfterOperator = getLinesBetweenNodes(node, operatorToken, node.right);
@@ -524973,7 +524973,7 @@ function resolveDefaultPoolConfig() {
524973
524973
  const targetGpuInstances = Number(process.env["OMNIUS_OLLAMA_TARGET_GPU_INSTANCES"]) || 0;
524974
524974
  const gpuPlacementRaw = (process.env["OMNIUS_OLLAMA_GPU_PLACEMENT"] ?? "auto").toLowerCase();
524975
524975
  const gpuPlacement = gpuPlacementRaw === "dedicated" || gpuPlacementRaw === "elastic" || gpuPlacementRaw === "auto" ? gpuPlacementRaw : "auto";
524976
- const idleMs = Number(process.env["OMNIUS_OLLAMA_IDLE_MS"]) || 5 * 60 * 1e3;
524976
+ const idleMs = Number(process.env["OMNIUS_OLLAMA_IDLE_MS"]) || 3 * 60 * 60 * 1e3;
524977
524977
  const reaperIntervalMs = Number(process.env["OMNIUS_OLLAMA_REAPER_MS"]) || 3e4;
524978
524978
  const spawnPortStart = Number(process.env["OMNIUS_OLLAMA_SPAWN_PORT"]) || 11435;
524979
524979
  const sharedModelStore = discoverSystemOllamaModelStore();
@@ -525176,6 +525176,19 @@ var init_ollama_pool = __esm({
525176
525176
  activePlacementMode = "constrained";
525177
525177
  gpuCache = null;
525178
525178
  slotWaiters = [];
525179
+ /**
525180
+ * Agent → preferred instance id. Set whenever an acquire resolves an
525181
+ * `agentId` to an instance. Lets two simultaneous sessions (e.g. two
525182
+ * Telegram chats) keep landing on different cards rather than re-racing
525183
+ * the scorer every turn. Trimmed when the referenced instance is reaped.
525184
+ */
525185
+ affinityById = /* @__PURE__ */ new Map();
525186
+ /**
525187
+ * Recent agent ids per instance id (most-recent first, capped at 5).
525188
+ * Surfaced in status() so the TUI can show "instance X serves chats
525189
+ * A,B,C". Bound is small; this is a UX hint, not a routing primary.
525190
+ */
525191
+ recentAgentsByInstance = /* @__PURE__ */ new Map();
525179
525192
  constructor(config, opts) {
525180
525193
  super();
525181
525194
  this.config = { ...resolveDefaultPoolConfig(), ...config };
@@ -525194,10 +525207,50 @@ var init_ollama_pool = __esm({
525194
525207
  lastUsedMs: Date.now(),
525195
525208
  knownModels: /* @__PURE__ */ new Set(),
525196
525209
  maxParallel: this.config.maxParallelPerInstance,
525197
- totalRequests: 0
525210
+ totalRequests: 0,
525211
+ pid: null,
525212
+ spawnedAtMs: Date.now()
525198
525213
  }, null));
525199
525214
  this.startReaper();
525200
525215
  }
525216
+ /**
525217
+ * Resolve the effective agent id for an acquire request. Explicit option
525218
+ * wins; otherwise consult the conventional env vars. Returns null when no
525219
+ * agent context is available so the scorer falls back to model affinity.
525220
+ */
525221
+ resolveAgentId(opts) {
525222
+ if (opts.agentId && opts.agentId.trim())
525223
+ return opts.agentId.trim();
525224
+ const envAgent = process.env["OMNIUS_AGENT_ID"]?.trim();
525225
+ if (envAgent)
525226
+ return envAgent;
525227
+ const envSession = process.env["OMNIUS_SESSION_ID"]?.trim();
525228
+ if (envSession)
525229
+ return envSession;
525230
+ return null;
525231
+ }
525232
+ /**
525233
+ * Record that `agentId` was just routed to `instanceId`. Caps the per-
525234
+ * instance affinity hint list at 5 entries (LRU). Idempotent under
525235
+ * repeated record of the same pair.
525236
+ */
525237
+ recordAffinity(agentId, instanceId) {
525238
+ this.affinityById.set(agentId, instanceId);
525239
+ const existing = this.recentAgentsByInstance.get(instanceId) ?? [];
525240
+ const filtered = existing.filter((a2) => a2 !== agentId);
525241
+ filtered.unshift(agentId);
525242
+ if (filtered.length > 5)
525243
+ filtered.length = 5;
525244
+ this.recentAgentsByInstance.set(instanceId, filtered);
525245
+ }
525246
+ /** Drop affinity entries pointing at a reaped instance so future acquires don't aim at a dead instance. */
525247
+ dropAffinityFor(instanceId) {
525248
+ for (const [agent, target] of this.affinityById) {
525249
+ if (target === instanceId)
525250
+ this.affinityById.delete(agent);
525251
+ }
525252
+ this.recentAgentsByInstance.delete(instanceId);
525253
+ }
525201
525254
  /**
525202
525255
  * Reserve a slot for one inference request. Returns immediately with a
525203
525256
  * usable base URL, even when the pool has to spawn a fresh instance
@@ -525211,62 +525264,73 @@ var init_ollama_pool = __esm({
525211
525264
  * 4. Queue at the pool boundary when all allowed lanes are busy.
525212
525265
  */
525213
525266
  async acquire(opts) {
525267
+ const resolvedAgentId = this.resolveAgentId(opts);
525268
+ const optsWithAgent = resolvedAgentId ? { ...opts, agentId: resolvedAgentId } : opts;
525214
525269
  const gpus = await this.getGpusForPlacement();
525215
525270
  let placementMode = this.placementModeFor(gpus);
525216
525271
  this.activePlacementMode = placementMode;
525217
525272
  if (placementMode === "dedicated") {
525218
- await this.ensureDedicatedGpuPool(opts.model, gpus);
525273
+ await this.ensureDedicatedGpuPool(optsWithAgent.model, gpus);
525219
525274
  if (!this.instances.some((i2) => i2.state.poolOwned)) {
525220
525275
  placementMode = "constrained";
525221
525276
  this.activePlacementMode = placementMode;
525222
525277
  }
525223
525278
  }
525224
- const pick = this.pickInstance(opts);
525279
+ const pick = this.pickInstance(optsWithAgent);
525225
525280
  if (pick) {
525226
- pick.acquire(opts.model);
525227
- return this.buildSlot(pick);
525281
+ pick.acquire(optsWithAgent.model);
525282
+ if (resolvedAgentId)
525283
+ this.recordAffinity(resolvedAgentId, pick.state.id);
525284
+ return this.buildSlot(pick, resolvedAgentId);
525228
525285
  }
525229
525286
  if (placementMode === "constrained") {
525230
- return this.acquireQueued(opts);
525287
+ return this.acquireQueued(optsWithAgent, resolvedAgentId);
525231
525288
  }
525232
- const spawned = placementMode === "elastic" ? await this.maybeSpawnInstance(opts.model) : null;
525289
+ const spawned = placementMode === "elastic" ? await this.maybeSpawnInstance(optsWithAgent.model) : null;
525233
525290
  if (spawned && !spawned.isSaturated()) {
525234
- spawned.acquire(opts.model);
525235
- return this.buildSlot(spawned);
525291
+ spawned.acquire(optsWithAgent.model);
525292
+ if (resolvedAgentId)
525293
+ this.recordAffinity(resolvedAgentId, spawned.state.id);
525294
+ return this.buildSlot(spawned, resolvedAgentId);
525236
525295
  }
525237
- return this.acquireQueued(opts);
525296
+ return this.acquireQueued(optsWithAgent, resolvedAgentId);
525238
525297
  }
525239
525298
  /** Synchronous routing decision; returns the instance or null if every one is saturated. */
525240
525299
  pickInstance(opts) {
525241
525300
  const candidates = this.instances.filter((inst) => !this.isEffectivelySaturated(inst) && !(this.activePlacementMode === "dedicated" && this.dedicatedGpuPoolActive && !inst.state.poolOwned && !opts.preferBaseInstance));
525242
525301
  if (candidates.length === 0)
525243
525302
  return null;
525303
+ const affinityTargetId = opts.agentId ? this.affinityById.get(opts.agentId) : void 0;
525244
525304
  const scored = candidates.map((inst) => ({
525245
525305
  inst,
525246
- score: (inst.state.knownModels.has(opts.model) ? 100 : 0) + (opts.preferBaseInstance && !inst.state.poolOwned ? 25 : 0) + this.effectiveFreeSlots(inst) * 10 - inst.state.inflight
525306
+ score: (inst.state.knownModels.has(opts.model) ? 100 : 0) + (affinityTargetId && inst.state.id === affinityTargetId ? 60 : 0) + (opts.preferBaseInstance && !inst.state.poolOwned ? 25 : 0) + this.effectiveFreeSlots(inst) * 10 - inst.state.inflight
525247
525307
  }));
525248
525308
  scored.sort((a2, b) => b.score - a2.score);
525249
525309
  return scored[0].inst;
525250
525310
  }
525251
- buildSlot(inst) {
525311
+ buildSlot(inst, agentId) {
525252
525312
  return {
525253
525313
  instanceId: inst.state.id,
525254
525314
  baseUrl: inst.state.baseUrl,
525255
525315
  poolOwned: inst.state.poolOwned,
525256
525316
  gpuUuid: inst.state.gpuUuid,
525257
525317
  gpuIndex: inst.state.gpuIndex,
525318
+ pid: inst.state.pid,
525319
+ agentId,
525258
525320
  release: (success) => {
525259
525321
  inst.release(success);
525260
525322
  this.wakeNextSlotWaiter();
525261
525323
  }
525262
525324
  };
525263
525325
  }
525264
- async acquireQueued(opts) {
525326
+ async acquireQueued(opts, agentId) {
525265
525327
  for (; ; ) {
525266
525328
  const pick = this.pickInstance(opts);
525267
525329
  if (pick) {
525268
525330
  pick.acquire(opts.model);
525269
- return this.buildSlot(pick);
525331
+ if (agentId)
525332
+ this.recordAffinity(agentId, pick.state.id);
525333
+ return this.buildSlot(pick, agentId);
525270
525334
  }
525271
525335
  await new Promise((resolve52) => this.slotWaiters.push(resolve52));
525272
525336
  }
@@ -525409,6 +525473,7 @@ var init_ollama_pool = __esm({
525409
525473
  this.emit("spawn-failed", { port, gpuUuid, gpuIndex, error: err });
525410
525474
  return null;
525411
525475
  }
525476
+ const spawnedAtMs = Date.now();
525412
525477
  const inst = new OllamaInstance({
525413
525478
  id: `omnius-ollama-${port}`,
525414
525479
  baseUrl: `http://127.0.0.1:${port}`,
@@ -525418,13 +525483,29 @@ var init_ollama_pool = __esm({
525418
525483
  poolOwned: true,
525419
525484
  inflight: 0,
525420
525485
  peakInflight: 0,
525421
- lastUsedMs: Date.now(),
525486
+ lastUsedMs: spawnedAtMs,
525422
525487
  knownModels: /* @__PURE__ */ new Set([model]),
525423
525488
  maxParallel: this.config.maxParallelPerInstance,
525424
- totalRequests: 0
525489
+ totalRequests: 0,
525490
+ pid: proc.pid,
525491
+ spawnedAtMs
525425
525492
  }, proc);
525426
525493
  this.instances.push(inst);
525427
- this.emit("instance-spawned", { id: inst.state.id, port, gpuUuid, gpuIndex });
525494
+ this.emit("instance-spawned", {
525495
+ id: inst.state.id,
525496
+ pid: proc.pid,
525497
+ port,
525498
+ gpuUuid,
525499
+ gpuIndex,
525500
+ model,
525501
+ spawnedAtMs,
525502
+ provenance: {
525503
+ entity: `urn:omnius:ollama-instance:${inst.state.id}`,
525504
+ activity: "ollama-instance-spawn",
525505
+ agent: "orchestrator.ollama-pool",
525506
+ timestampMs: spawnedAtMs
525507
+ }
525508
+ });
525428
525509
  return inst;
525429
525510
  }
525430
525511
  /**
@@ -525464,11 +525545,22 @@ var init_ollama_pool = __esm({
525464
525545
  continue;
525465
525546
  }
525466
525547
  if (inst.isIdleLongerThan(this.config.idleMs)) {
525548
+ const reapedAtMs = Date.now();
525467
525549
  await inst.terminate();
525550
+ this.dropAffinityFor(inst.state.id);
525468
525551
  this.emit("instance-reaped", {
525469
525552
  id: inst.state.id,
525553
+ pid: inst.state.pid,
525470
525554
  totalRequests: inst.state.totalRequests,
525471
- peakInflight: inst.state.peakInflight
525555
+ peakInflight: inst.state.peakInflight,
525556
+ ageMs: reapedAtMs - inst.state.spawnedAtMs,
525557
+ idleMs: reapedAtMs - inst.state.lastUsedMs,
525558
+ provenance: {
525559
+ entity: `urn:omnius:ollama-instance:${inst.state.id}`,
525560
+ activity: "ollama-instance-reap",
525561
+ agent: "orchestrator.ollama-pool",
525562
+ timestampMs: reapedAtMs
525563
+ }
525472
525564
  });
525473
525565
  continue;
525474
525566
  }
@@ -525506,6 +525598,9 @@ var init_ollama_pool = __esm({
525506
525598
  poolOwned: inst.state.poolOwned,
525507
525599
  gpuUuid: inst.state.gpuUuid,
525508
525600
  gpuIndex: inst.state.gpuIndex,
525601
+ pid: inst.state.pid,
525602
+ ageMs: Date.now() - inst.state.spawnedAtMs,
525603
+ affinityAgentIds: this.recentAgentsByInstance.get(inst.state.id) ?? [],
525509
525604
  inflight: inst.state.inflight,
525510
525605
  peakInflight: inst.state.peakInflight,
525511
525606
  maxParallel: inst.state.maxParallel,
@@ -544419,9 +544514,6 @@ ${memoryLines.join("\n")}`
544419
544514
  const churnBlock = this._renderWriteChurnBlock(turn);
544420
544515
  if (churnBlock)
544421
544516
  _injections.push(churnBlock);
544422
- const progressBlock = this._renderProgressNudgeBlock(turn);
544423
- if (progressBlock)
544424
- _injections.push(progressBlock);
544425
544517
  const knowledgeBlock = this._renderKnowledgeBlock(recentToolResults);
544426
544518
  if (knowledgeBlock)
544427
544519
  _injections.push(knowledgeBlock);
@@ -544967,55 +545059,6 @@ ${memoryLines.join("\n")}`
544967
545059
  return { tc, output: _decomp2Block };
544968
545060
  }
544969
545061
  }
544970
- const PROGRESS_GATE_BYPASS_TOOLS = /* @__PURE__ */ new Set([
544971
- "todo_write",
544972
- "todo_read",
544973
- "task_complete",
544974
- "ask_user",
544975
- "phase_recall"
544976
- // useful for the agent to consult prior phase state before updating
544977
- ]);
544978
- if (this._progressGateActive && !PROGRESS_GATE_BYPASS_TOOLS.has(tc.name)) {
544979
- this.emit({
544980
- type: "tool_call",
544981
- toolName: tc.name,
544982
- toolArgs: tc.arguments,
544983
- turn,
544984
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
544985
- });
544986
- const recentWrites = [];
544987
- for (const [path12, info] of this._worldFacts.files) {
544988
- if ((info.writeCount ?? 0) > 0 && (info.lastWriteTurn ?? -1) >= 0 && turn - (info.lastWriteTurn ?? 0) <= 8) {
544989
- recentWrites.push({ path: path12, turn: info.lastWriteTurn ?? 0 });
544990
- }
544991
- }
544992
- recentWrites.sort((a2, b) => b.turn - a2.turn);
544993
- const showWrites = recentWrites.slice(0, 16);
544994
- const gateMsg = [
544995
- `[PROGRESS GATE — call todo_write FIRST before any other tool]`,
544996
- ``,
544997
- `You have completed ${this._writesSinceLastTodoWrite} file modification${this._writesSinceLastTodoWrite === 1 ? "" : "s"} since your last todo_write call.`,
544998
- `The next tool call MUST be todo_write to mark progress. This is enforced — non-todo tool calls are intercepted until plan state is updated.`,
544999
- ``,
545000
- `Recent file modifications (use these to decide what's done):`,
545001
- ...showWrites.map((w) => ` • ${w.path} (turn ${w.turn})`),
545002
- recentWrites.length > showWrites.length ? ` • ... +${recentWrites.length - showWrites.length} more` : "",
545003
- ``,
545004
- `Required action: call todo_write with the updated todo array — mark anything completed that these writes satisfy, advance the next item to in_progress, keep the rest pending.`,
545005
- `After todo_write succeeds, this gate releases and you can continue normal work.`,
545006
- ``,
545007
- `Why this exists: without the explicit progress update, your next turn will see the same in_progress todo, re-plan the same work, and re-emit identical tool calls (the "plan replay" failure mode that causes byte-identical writes to appear twice).`
545008
- ].filter(Boolean).join("\n");
545009
- this.emit({
545010
- type: "tool_result",
545011
- toolName: tc.name,
545012
- success: false,
545013
- content: gateMsg.slice(0, 120),
545014
- turn,
545015
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
545016
- });
545017
- return { tc, output: gateMsg };
545018
- }
545019
545062
  const _argsKeyForBudget = `${tc.name}:${argsKey}`;
545020
545063
  const _isCachedHit = recentToolResults.has(_argsKeyForBudget);
545021
545064
  const budgetRemaining = toolCallBudget.get(tc.name);
@@ -570015,6 +570058,9 @@ async function collectOllamaPoolMetrics() {
570015
570058
  poolOwned: inst.poolOwned,
570016
570059
  gpuUuid: inst.gpuUuid,
570017
570060
  gpuIndex: inst.gpuIndex,
570061
+ pid: inst.pid,
570062
+ ageMs: inst.ageMs,
570063
+ affinityAgentIds: inst.affinityAgentIds,
570018
570064
  inflight: inst.inflight,
570019
570065
  maxParallel: inst.maxParallel,
570020
570066
  totalRequests: inst.totalRequests
@@ -571046,6 +571092,12 @@ __export(status_bar_exports, {
571046
571092
  unlockFooterRedraws: () => unlockFooterRedraws
571047
571093
  });
571048
571094
  import { readFileSync as readFileSync72 } from "node:fs";
571095
+ function formatPoolAge(ms) {
571096
+ if (ms < 6e4) return `${Math.max(0, Math.floor(ms / 1e3))}s`;
571097
+ if (ms < 60 * 6e4) return `${Math.floor(ms / 6e4)}m`;
571098
+ if (ms < 24 * 60 * 6e4) return `${Math.floor(ms / (60 * 6e4))}h`;
571099
+ return `${Math.floor(ms / (24 * 60 * 6e4))}d`;
571100
+ }
571049
571101
  function lockFooterRedraws() {
571050
571102
  _globalFooterLock = true;
571051
571103
  }
@@ -571170,16 +571222,22 @@ var init_status_bar = __esm({
571170
571222
  /** Timestamp when current task started (0 = no active task) */
571171
571223
  taskStartMs = 0;
571172
571224
  /** Number of tool calls in current session */
571173
- toolCalls = 0;
571225
+ _toolCalls = 0;
571226
+ get toolCalls() {
571227
+ return this._toolCalls;
571228
+ }
571174
571229
  /** Number of turns in current session */
571175
- turns = 0;
571230
+ _turns = 0;
571231
+ get turns() {
571232
+ return this._turns;
571233
+ }
571176
571234
  /** Accumulated reading time in seconds (subset of humanTimeS) */
571177
571235
  readingTimeS = 0;
571178
571236
  /** Record a tool call — adds the expert baseline time */
571179
571237
  recordToolCall(toolName) {
571180
571238
  const baseline2 = EXPERT_TOOL_BASELINES[toolName] ?? DEFAULT_TOOL_BASELINE;
571181
571239
  this.humanTimeS += baseline2 + CONTEXT_SWITCH_OVERHEAD;
571182
- this.toolCalls++;
571240
+ this._toolCalls++;
571183
571241
  }
571184
571242
  /**
571185
571243
  * Record a tool result — adds human reading time based on content volume.
@@ -571203,7 +571261,7 @@ var init_status_bar = __esm({
571203
571261
  /** Record a turn (assistant reasoning cycle) */
571204
571262
  recordTurn() {
571205
571263
  this.humanTimeS += TURN_PLANNING_OVERHEAD;
571206
- this.turns++;
571264
+ this._turns++;
571207
571265
  }
571208
571266
  /** Mark the start of a task (for wall-clock tracking) */
571209
571267
  taskStart() {
@@ -571258,6 +571316,21 @@ var init_status_bar = __esm({
571258
571316
  lastCompletionTokens: 0,
571259
571317
  contextWindowSize: 0
571260
571318
  };
571319
+ // ── Metrics tracking for Telegram stats ──
571320
+ _backend = "ollama";
571321
+ _inferenceCount = 0;
571322
+ _totalInferenceDurationMs = 0;
571323
+ _peakTokensPerSecond = 0;
571324
+ _successfulToolCalls = 0;
571325
+ _failedToolCalls = 0;
571326
+ _toolCallBreakdown = [];
571327
+ _compactionCount = 0;
571328
+ _sessionStartAt = 0;
571329
+ _gpuName = "";
571330
+ _vramTotal = 0;
571331
+ _vramUsed = 0;
571332
+ _toolCalls = 0;
571333
+ _turns = 0;
571261
571334
  active = false;
571262
571335
  scrollRegionTop = 1;
571263
571336
  // ── Agent View Multiplexing (WO-NA1) ──
@@ -571386,10 +571459,19 @@ var init_status_bar = __esm({
571386
571459
  syncEnd() {
571387
571460
  this.termWrite("\x1B[?2026l");
571388
571461
  }
571389
- /** Force a complete footer redraw (public wrapper for renderFooterAndPositionInput) */
571462
+ /** Force a complete footer redraw (public wrapper for renderFooterAndPositionInput).
571463
+ *
571464
+ * IMPORTANT: do NOT call `updateFooterHeight()` here. The inner render
571465
+ * function captures `oldFooterTop` from the CURRENT `_currentFooterHeight`
571466
+ * BEFORE updating it, so it can detect height changes and clear the
571467
+ * rows that the old (taller) footer used to occupy. Pre-calling
571468
+ * `updateFooterHeight` here would mutate the height to the new value
571469
+ * first, leaving the inner render to think nothing changed and skipping
571470
+ * the transition-row clear — which is exactly what caused stale prompt
571471
+ * text to remain on the input box's top unicode border after submitting
571472
+ * a multi-line input. */
571390
571473
  redrawFooter() {
571391
571474
  if (!this.active) return;
571392
- this.updateFooterHeight();
571393
571475
  this.renderFooterAndPositionInput();
571394
571476
  }
571395
571477
  /** Callback to get available slash commands for suggestion matching */
@@ -571528,6 +571610,7 @@ var init_status_bar = __esm({
571528
571610
  }
571529
571611
  setHeaderIdentity(modelName, backendType, backendUrl2) {
571530
571612
  this._modelName = modelName;
571613
+ this._backend = backendType || "ollama";
571531
571614
  this._headerBackendType = backendType;
571532
571615
  this._headerBackendUrl = backendUrl2;
571533
571616
  this.refreshHeaderAndFooter();
@@ -572149,6 +572232,9 @@ var init_status_bar = __esm({
572149
572232
  if (!this._metricsCollector.isActive || this._metricsCollector.source !== "remote") {
572150
572233
  this._metricsCollector.startRemote((m2) => {
572151
572234
  this._unifiedMetrics = m2;
572235
+ this._gpuName = m2.gpuName || "";
572236
+ this._vramTotal = m2.vramTotalMB || 0;
572237
+ this._vramUsed = m2.vramUsedMB || 0;
572152
572238
  if (this.active) this.renderFooterPreserveCursor();
572153
572239
  });
572154
572240
  }
@@ -573963,11 +574049,18 @@ ${CONTENT_BG_SEQ}`);
573963
574049
  const target = pool3.targetGpuInstances;
573964
574050
  const poolColor = pool3.mode === "constrained" ? c3.yellow : target > 0 && ready < target ? c3.yellow : c3.green;
573965
574051
  const poolDetail = pool3.mode === "constrained" ? "queue" : `${ready}/${target}`;
573966
- const poolText = ` OLLAMA ${poolColor(`${pool3.mode}:${poolDetail}`)}`;
574052
+ const poolOwned = pool3.instances.filter((i2) => i2.poolOwned);
574053
+ const pidSummary = poolOwned.length === 0 ? "" : ` PID[${poolOwned.map((i2) => `${i2.pid}@${i2.gpuIndex ?? "?"}`).slice(0, 3).join(",")}]`;
574054
+ const oldestAgeMs = poolOwned.reduce(
574055
+ (max, i2) => i2.ageMs > max ? i2.ageMs : max,
574056
+ 0
574057
+ );
574058
+ const ageSummary = oldestAgeMs > 0 ? ` age=${formatPoolAge(oldestAgeMs)}` : "";
574059
+ const poolText = ` OLLAMA ${poolColor(`${pool3.mode}:${poolDetail}`)}${c3.dim(pidSummary)}${c3.dim(ageSummary)}`;
573967
574060
  const compactText = ` OLLAMA ${poolColor(pool3.mode === "constrained" ? "queue" : `${ready}/${target}`)}`;
573968
574061
  hwExpStr += poolText;
573969
574062
  hwCompStr += compactText;
573970
- hwExpW += 8 + `${pool3.mode}:${poolDetail}`.length;
574063
+ hwExpW += 8 + `${pool3.mode}:${poolDetail}`.length + pidSummary.length + ageSummary.length;
573971
574064
  hwCompW += 8 + (pool3.mode === "constrained" ? "queue".length : `${ready}/${target}`.length);
573972
574065
  }
573973
574066
  if (!isLocal && hwExpW === 0) {
@@ -574735,6 +574828,86 @@ ${CONTENT_BG_SEQ}`);
574735
574828
  }
574736
574829
  });
574737
574830
  }
574831
+ /** Record a tool result success/fail for metrics tracking. */
574832
+ recordToolSuccessFail(toolName, success) {
574833
+ if (success) {
574834
+ this._successfulToolCalls++;
574835
+ } else {
574836
+ this._failedToolCalls++;
574837
+ }
574838
+ const existing = this._toolCallBreakdown.find((t2) => t2.name === toolName);
574839
+ if (existing) {
574840
+ existing.count++;
574841
+ } else {
574842
+ this._toolCallBreakdown.push({ name: toolName, count: 1 });
574843
+ }
574844
+ }
574845
+ /** Record an inference completion for metrics tracking. */
574846
+ recordInference(durationMs, tokensPerSecond) {
574847
+ this._inferenceCount++;
574848
+ this._totalInferenceDurationMs += durationMs;
574849
+ if (tokensPerSecond > this._peakTokensPerSecond) {
574850
+ this._peakTokensPerSecond = tokensPerSecond;
574851
+ }
574852
+ if (this._sessionStartAt === 0) {
574853
+ this._sessionStartAt = Date.now();
574854
+ }
574855
+ }
574856
+ /** Record a compaction event for metrics tracking. */
574857
+ recordCompaction() {
574858
+ this._compactionCount++;
574859
+ }
574860
+ /** Set the model name for metrics tracking. */
574861
+ setModelNameForMetrics(name10) {
574862
+ this._modelName = name10;
574863
+ }
574864
+ /** Set the backend type for metrics tracking. */
574865
+ setBackendForMetrics(backend) {
574866
+ this._backend = backend;
574867
+ }
574868
+ /** Increment turn count. */
574869
+ incrementTurnCount() {
574870
+ this._turns++;
574871
+ }
574872
+ /** Increment tool call count. */
574873
+ incrementToolCallCount() {
574874
+ this._toolCalls++;
574875
+ }
574876
+ /** Return a snapshot of current metrics for the Telegram stats menu. */
574877
+ getMetricsSnapshot() {
574878
+ const m2 = this.metrics;
574879
+ const now = Date.now();
574880
+ const duration = this._sessionStartAt > 0 ? now - this._sessionStartAt : 0;
574881
+ const avgSpeed = duration > 0 ? m2.totalTokens / (duration / 1e3) : 0;
574882
+ const ctxPct = m2.contextWindowSize > 0 ? m2.estimatedContextTokens / m2.contextWindowSize * 100 : 0;
574883
+ return {
574884
+ model: this._modelName || "unknown",
574885
+ backend: this._backend || "ollama",
574886
+ totalInferences: this._inferenceCount,
574887
+ totalPromptTokens: m2.promptTokens,
574888
+ totalCompletionTokens: m2.completionTokens,
574889
+ totalTokens: m2.totalTokens,
574890
+ avgTokensPerSecond: avgSpeed,
574891
+ peakTokensPerSecond: this._peakTokensPerSecond,
574892
+ avgInferenceDurationMs: this._inferenceCount > 0 ? this._totalInferenceDurationMs / this._inferenceCount : 0,
574893
+ totalInferenceDurationMs: this._totalInferenceDurationMs,
574894
+ totalToolCalls: this._toolCalls,
574895
+ successfulToolCalls: this._successfulToolCalls,
574896
+ failedToolCalls: this._failedToolCalls,
574897
+ toolCallBreakdown: this._toolCallBreakdown.map((t2) => ({ name: t2.name, count: t2.count, avgDurationMs: 0 })),
574898
+ contextWindowSize: m2.contextWindowSize,
574899
+ estimatedContextTokens: m2.estimatedContextTokens,
574900
+ peakContextTokens: m2.estimatedContextTokens,
574901
+ contextUtilizationPct: ctxPct,
574902
+ compactionCount: this._compactionCount,
574903
+ sessionStartAt: this._sessionStartAt || now,
574904
+ sessionDurationMs: duration,
574905
+ turnCount: this._turns,
574906
+ gpuName: this._gpuName || null,
574907
+ gpuVramTotalMb: this._vramTotal || null,
574908
+ gpuVramUsedMb: this._vramUsed || null
574909
+ };
574910
+ }
574738
574911
  };
574739
574912
  }
574740
574913
  });
@@ -603186,30 +603359,200 @@ var init_carousel_descriptors = __esm({
603186
603359
  }
603187
603360
  });
603188
603361
 
603362
+ // packages/cli/src/tui/syntax-highlight.ts
603363
+ var syntax_highlight_exports = {};
603364
+ __export(syntax_highlight_exports, {
603365
+ detectLanguage: () => detectLanguage2,
603366
+ getHighlightStatus: () => getHighlightStatus,
603367
+ highlightBlock: () => highlightBlock,
603368
+ highlightCode: () => highlightCode,
603369
+ isAvailable: () => isAvailable,
603370
+ prewarm: () => prewarm
603371
+ });
603372
+ function highlightingDisabled() {
603373
+ return !isTTY8 || noColorEnv || disableEnv;
603374
+ }
603375
+ async function loadHighlighter() {
603376
+ if (_state2.attempted) return _state2.fn;
603377
+ _state2.attempted = true;
603378
+ if (highlightingDisabled()) {
603379
+ _state2.reason = !isTTY8 ? "non-tty" : noColorEnv ? "NO_COLOR set" : "OMNIUS_TUI_HIGHLIGHT=0";
603380
+ return null;
603381
+ }
603382
+ try {
603383
+ const { createRequire: createRequire10 } = await import("node:module");
603384
+ const req2 = createRequire10(import.meta.url);
603385
+ let resolved = null;
603386
+ try {
603387
+ resolved = req2.resolve("cli-highlight");
603388
+ } catch {
603389
+ _state2.reason = "cli-highlight not installed";
603390
+ return null;
603391
+ }
603392
+ const mod2 = await import(resolved).catch(() => null);
603393
+ if (!mod2) {
603394
+ _state2.reason = "cli-highlight failed to load";
603395
+ return null;
603396
+ }
603397
+ const m2 = mod2;
603398
+ const candidate = m2.highlight ?? m2.default ?? null;
603399
+ if (typeof candidate !== "function") {
603400
+ _state2.reason = "cli-highlight export shape unrecognized";
603401
+ return null;
603402
+ }
603403
+ _state2.fn = candidate;
603404
+ return candidate;
603405
+ } catch (err) {
603406
+ _state2.reason = `import threw: ${err?.message ?? String(err)}`;
603407
+ return null;
603408
+ }
603409
+ }
603410
+ function loadHighlighterSync() {
603411
+ if (highlightingDisabled()) return null;
603412
+ return _state2.fn;
603413
+ }
603414
+ async function prewarm() {
603415
+ await loadHighlighter();
603416
+ }
603417
+ function isAvailable() {
603418
+ if (highlightingDisabled()) return false;
603419
+ return _state2.attempted && _state2.fn !== null;
603420
+ }
603421
+ function getHighlightStatus() {
603422
+ return {
603423
+ available: isAvailable(),
603424
+ attempted: _state2.attempted,
603425
+ reason: _state2.reason,
603426
+ isTTY: isTTY8,
603427
+ noColor: noColorEnv,
603428
+ disabledByEnv: disableEnv
603429
+ };
603430
+ }
603431
+ function highlightCode(code8, language) {
603432
+ if (!code8) return code8;
603433
+ const fn = loadHighlighterSync();
603434
+ if (!fn) return code8;
603435
+ const lang = (language ?? detectLanguage2(code8) ?? "").trim();
603436
+ try {
603437
+ if (lang) {
603438
+ return fn(code8, { language: lang, ignoreIllegals: true });
603439
+ }
603440
+ return fn(code8, { ignoreIllegals: true });
603441
+ } catch {
603442
+ return code8;
603443
+ }
603444
+ }
603445
+ function detectLanguage2(text) {
603446
+ if (!text) return null;
603447
+ const trimmed = text.trimStart();
603448
+ const shebang = trimmed.match(/^#!\s*\/[^\n]+/);
603449
+ if (shebang) {
603450
+ const sb = shebang[0];
603451
+ if (/python/.test(sb)) return "python";
603452
+ if (/(?:^|[\s/])(?:bash|sh|zsh)\b/.test(sb)) return "bash";
603453
+ if (/node/.test(sb)) return "javascript";
603454
+ if (/ruby/.test(sb)) return "ruby";
603455
+ if (/perl/.test(sb)) return "perl";
603456
+ }
603457
+ if (/^[\s\n]*[{[]/.test(trimmed)) {
603458
+ try {
603459
+ JSON.parse(trimmed);
603460
+ return "json";
603461
+ } catch {
603462
+ }
603463
+ }
603464
+ if (/^[-a-zA-Z_][\w-]*:\s/.test(trimmed) && /\n[-a-zA-Z_][\w-]*:\s/.test(trimmed)) {
603465
+ return "yaml";
603466
+ }
603467
+ if (/^(?:async def |def |class |import |from )/.test(trimmed) && /(?::\s*$|->|self\b)/m.test(trimmed)) {
603468
+ return "python";
603469
+ }
603470
+ if (/^(?:import |export |const |let |var |function |class |interface |type |async )/.test(trimmed)) {
603471
+ if (/(:\s*(?:string|number|boolean|any|unknown|void)\b|\binterface\b|\btype\s+\w+\s*=)/.test(trimmed)) {
603472
+ return "typescript";
603473
+ }
603474
+ return "javascript";
603475
+ }
603476
+ if (/(?:^|\n)\s*(?:fn |use |let mut |impl |struct |enum |trait )/.test(trimmed)) {
603477
+ return "rust";
603478
+ }
603479
+ if (/(?:^package \w+|\nfunc \w+\s*\()/.test(trimmed)) {
603480
+ return "go";
603481
+ }
603482
+ if (/^\s*(?:SELECT|INSERT|UPDATE|DELETE|CREATE|ALTER|DROP)\b/i.test(trimmed)) {
603483
+ return "sql";
603484
+ }
603485
+ if (/^[\s\n]*<(?:!DOCTYPE|html|\?xml|\w+)/i.test(trimmed)) {
603486
+ return "html";
603487
+ }
603488
+ if (/^(?:---|\+\+\+|@@|diff )/.test(trimmed)) {
603489
+ return "diff";
603490
+ }
603491
+ if (/^\s*(?:\$\s|sudo |apt |brew |npm |pnpm |yarn |git |docker |kubectl |curl |wget )/.test(trimmed)) {
603492
+ return "bash";
603493
+ }
603494
+ return null;
603495
+ }
603496
+ function highlightBlock(code8, language) {
603497
+ if (!code8) return [""];
603498
+ const fn = loadHighlighterSync();
603499
+ if (!fn) return code8.split("\n");
603500
+ const lang = (language ?? detectLanguage2(code8) ?? "").trim();
603501
+ try {
603502
+ const out = lang ? fn(code8, { language: lang, ignoreIllegals: true }) : fn(code8, { ignoreIllegals: true });
603503
+ const lines = out.split("\n");
603504
+ const inputLines = code8.split("\n");
603505
+ if (lines.length === inputLines.length) return lines;
603506
+ if (lines.length < inputLines.length) {
603507
+ while (lines.length < inputLines.length) lines.push("");
603508
+ } else {
603509
+ lines.length = inputLines.length;
603510
+ }
603511
+ return lines;
603512
+ } catch {
603513
+ return code8.split("\n");
603514
+ }
603515
+ }
603516
+ var isTTY8, noColorEnv, disableEnv, _state2;
603517
+ var init_syntax_highlight = __esm({
603518
+ "packages/cli/src/tui/syntax-highlight.ts"() {
603519
+ "use strict";
603520
+ isTTY8 = process.stdout?.isTTY ?? false;
603521
+ noColorEnv = process.env["NO_COLOR"] !== void 0 && process.env["NO_COLOR"] !== "";
603522
+ disableEnv = process.env["OMNIUS_TUI_HIGHLIGHT"] === "0";
603523
+ _state2 = {
603524
+ attempted: false,
603525
+ fn: null,
603526
+ reason: ""
603527
+ };
603528
+ }
603529
+ });
603530
+
603189
603531
  // packages/cli/src/tui/stream-renderer.ts
603190
603532
  function fg2564(code8, text) {
603191
- return isTTY8 ? `\x1B[38;5;${code8}m${text}\x1B[0m` : text;
603533
+ return isTTY9 ? `\x1B[38;5;${code8}m${text}\x1B[0m` : text;
603192
603534
  }
603193
603535
  function dimText(text) {
603194
- return isTTY8 ? `\x1B[38;5;${tuiTextDim()}m${text}\x1B[0m` : text;
603536
+ return isTTY9 ? `\x1B[38;5;${tuiTextDim()}m${text}\x1B[0m` : text;
603195
603537
  }
603196
603538
  function italicText(text) {
603197
- return isTTY8 ? `\x1B[3m${text}\x1B[0m` : text;
603539
+ return isTTY9 ? `\x1B[3m${text}\x1B[0m` : text;
603198
603540
  }
603199
603541
  function dimItalic(text) {
603200
- return isTTY8 ? `\x1B[3m\x1B[38;5;${tuiTextDim()}m${text}\x1B[0m` : text;
603542
+ return isTTY9 ? `\x1B[3m\x1B[38;5;${tuiTextDim()}m${text}\x1B[0m` : text;
603201
603543
  }
603202
603544
  function boldText(text) {
603203
- return isTTY8 ? `\x1B[1m${text}\x1B[0m` : text;
603545
+ return isTTY9 ? `\x1B[1m${text}\x1B[0m` : text;
603204
603546
  }
603205
- var isTTY8, PASTEL, StreamRenderer;
603547
+ var isTTY9, PASTEL, StreamRenderer;
603206
603548
  var init_stream_renderer = __esm({
603207
603549
  "packages/cli/src/tui/stream-renderer.ts"() {
603208
603550
  "use strict";
603209
603551
  init_layout2();
603210
603552
  init_text_selection();
603211
603553
  init_theme();
603212
- isTTY8 = process.stdout.isTTY ?? false;
603554
+ init_syntax_highlight();
603555
+ isTTY9 = process.stdout.isTTY ?? false;
603213
603556
  PASTEL = {
603214
603557
  key: 222,
603215
603558
  // light gold — JSON keys
@@ -603516,7 +603859,10 @@ var init_stream_renderer = __esm({
603516
603859
  if (this.codeLang === "diff" || this.codeLang === "patch") {
603517
603860
  rendered = this.highlightDiff(cropped);
603518
603861
  } else if (this.codeLang === "bash" || this.codeLang === "sh" || this.codeLang === "shell" || this.codeLang === "zsh") {
603519
- rendered = this.highlightShell(cropped);
603862
+ rendered = isAvailable() ? highlightCode(cropped, "bash") : this.highlightShell(cropped);
603863
+ } else if (isAvailable() && this.codeLang) {
603864
+ const highlighted = highlightCode(cropped, this.codeLang);
603865
+ rendered = highlighted === cropped ? this.highlightCode(cropped) : highlighted;
603520
603866
  } else {
603521
603867
  rendered = this.highlightCode(cropped);
603522
603868
  }
@@ -603575,7 +603921,7 @@ var init_stream_renderer = __esm({
603575
603921
  * Also maintains _cursorCol so emitWrapped can decide when to force a
603576
603922
  * wrap on the NEXT partial flush (avoiding bottom-row token pile-up). */
603577
603923
  writeRaw(text) {
603578
- if (isTTY8) {
603924
+ if (isTTY9) {
603579
603925
  process.stdout.write(`\x1B[?25l\x1B[?7l${text}\x1B[?7h`);
603580
603926
  } else {
603581
603927
  process.stdout.write(text);
@@ -608054,14 +608400,22 @@ var init_telegram_help_menu = __esm({
608054
608400
  } catch {
608055
608401
  }
608056
608402
  }
608057
- /** Delete the menu message and clean up state */
608403
+ /** Delete the menu message, the invoking user message, and clean up state */
608058
608404
  async deleteMenu(chatId, messageId) {
608405
+ const state = this.stateStore.get(chatId, messageId);
608406
+ const invokerMsgId = state?.invokerMessageId;
608059
608407
  this.clearTimer(chatId, messageId);
608060
608408
  this.stateStore.delete(chatId, messageId);
608061
608409
  try {
608062
608410
  await this.callbacks.deleteMessage(chatId, messageId);
608063
608411
  } catch {
608064
608412
  }
608413
+ if (invokerMsgId) {
608414
+ try {
608415
+ await this.callbacks.deleteMessage(chatId, invokerMsgId);
608416
+ } catch {
608417
+ }
608418
+ }
608065
608419
  }
608066
608420
  /** Clean up all timers (for shutdown) */
608067
608421
  destroyAll() {
@@ -608133,96 +608487,96 @@ function escapeHTML2(text) {
608133
608487
  function buildMetricEntries(snap, scope) {
608134
608488
  const entries = [];
608135
608489
  entries.push({
608136
- icon: "🧠",
608490
+ icon: "",
608137
608491
  label: "Model",
608138
608492
  value: escapeHTML2(snap.model),
608139
608493
  category: "inference"
608140
608494
  });
608141
608495
  entries.push({
608142
- icon: "",
608496
+ icon: "",
608143
608497
  label: "Inferences",
608144
608498
  value: String(snap.totalInferences),
608145
608499
  detail: `Total duration: ${fmtDuration(snap.totalInferenceDurationMs)}`,
608146
608500
  category: "inference"
608147
608501
  });
608148
608502
  entries.push({
608149
- icon: "🔤",
608503
+ icon: "",
608150
608504
  label: "Tokens",
608151
608505
  value: fmtTokens2(snap.totalTokens),
608152
608506
  detail: `Prompt: ${fmtTokens2(snap.totalPromptTokens)} · Completion: ${fmtTokens2(snap.totalCompletionTokens)}`,
608153
608507
  category: "inference"
608154
608508
  });
608155
608509
  entries.push({
608156
- icon: "🚀",
608510
+ icon: "",
608157
608511
  label: "Avg Speed",
608158
608512
  value: `${snap.avgTokensPerSecond.toFixed(1)} tok/s`,
608159
608513
  detail: `Peak: ${snap.peakTokensPerSecond.toFixed(1)} tok/s`,
608160
608514
  category: "inference"
608161
608515
  });
608162
608516
  entries.push({
608163
- icon: "",
608517
+ icon: "",
608164
608518
  label: "Avg Inference",
608165
608519
  value: fmtDuration(snap.avgInferenceDurationMs),
608166
608520
  category: "inference"
608167
608521
  });
608168
608522
  entries.push({
608169
- icon: "🔧",
608523
+ icon: "",
608170
608524
  label: "Tool Calls",
608171
608525
  value: String(snap.totalToolCalls),
608172
- detail: `✅ ${snap.successfulToolCalls} · ${snap.failedToolCalls}`,
608526
+ detail: `OK: ${snap.successfulToolCalls} / Fail: ${snap.failedToolCalls}`,
608173
608527
  category: "tools"
608174
608528
  });
608175
608529
  const topTools = snap.toolCallBreakdown.sort((a2, b) => b.count - a2.count).slice(0, scope === "admin" ? 5 : 3);
608176
608530
  for (const t2 of topTools) {
608177
608531
  entries.push({
608178
- icon: "🔩",
608532
+ icon: "",
608179
608533
  label: t2.name,
608180
- value: `${t2.count}×`,
608534
+ value: `${t2.count}x`,
608181
608535
  detail: `Avg: ${fmtDuration(t2.avgDurationMs)}`,
608182
608536
  category: "tools"
608183
608537
  });
608184
608538
  }
608185
608539
  entries.push({
608186
- icon: "📐",
608540
+ icon: "",
608187
608541
  label: "Context Window",
608188
608542
  value: fmtTokens2(snap.contextWindowSize),
608189
608543
  category: "context"
608190
608544
  });
608191
608545
  entries.push({
608192
- icon: "📊",
608546
+ icon: "",
608193
608547
  label: "Context Used",
608194
608548
  value: fmtTokens2(snap.estimatedContextTokens),
608195
608549
  detail: `Utilization: ${fmtPct(snap.contextUtilizationPct)} · Peak: ${fmtTokens2(snap.peakContextTokens)}`,
608196
608550
  category: "context"
608197
608551
  });
608198
608552
  entries.push({
608199
- icon: "🗜",
608553
+ icon: "",
608200
608554
  label: "Compactions",
608201
608555
  value: String(snap.compactionCount),
608202
608556
  category: "context"
608203
608557
  });
608204
608558
  entries.push({
608205
- icon: "🕐",
608559
+ icon: "",
608206
608560
  label: "Session",
608207
608561
  value: fmtDuration(snap.sessionDurationMs),
608208
608562
  category: "session"
608209
608563
  });
608210
608564
  entries.push({
608211
- icon: "🔄",
608565
+ icon: "",
608212
608566
  label: "Turns",
608213
608567
  value: String(snap.turnCount),
608214
608568
  category: "session"
608215
608569
  });
608216
608570
  if (scope === "admin") {
608217
608571
  entries.push({
608218
- icon: "🖥",
608572
+ icon: "",
608219
608573
  label: "Backend",
608220
608574
  value: escapeHTML2(snap.backend),
608221
608575
  category: "system"
608222
608576
  });
608223
608577
  if (snap.gpuName) {
608224
608578
  entries.push({
608225
- icon: "🎮",
608579
+ icon: "",
608226
608580
  label: "GPU",
608227
608581
  value: escapeHTML2(snap.gpuName),
608228
608582
  category: "system"
@@ -608231,7 +608585,7 @@ function buildMetricEntries(snap, scope) {
608231
608585
  if (snap.gpuVramTotalMb != null) {
608232
608586
  const used = snap.gpuVramUsedMb ?? 0;
608233
608587
  entries.push({
608234
- icon: "💾",
608588
+ icon: "",
608235
608589
  label: "VRAM",
608236
608590
  value: `${Math.round(used)}/${Math.round(snap.gpuVramTotalMb)} MB`,
608237
608591
  detail: `Usage: ${fmtPct(used / snap.gpuVramTotalMb * 100)}`,
@@ -608260,7 +608614,7 @@ function buildStatsKeyboard(page2, totalPages, countdown) {
608260
608614
  const navRow = [];
608261
608615
  if (page2 === 0) {
608262
608616
  navRow.push({
608263
- text: countdown != null ? `✖ Close (${countdown}s)` : "Close",
608617
+ text: countdown != null ? `Close (${countdown}s)` : "Close",
608264
608618
  callback_data: encodeStatsCallback("close", 0)
608265
608619
  });
608266
608620
  }
@@ -608270,10 +608624,10 @@ function buildStatsKeyboard(page2, totalPages, countdown) {
608270
608624
  // current page = no-op refresh
608271
608625
  });
608272
608626
  if (page2 > 0) {
608273
- navRow.unshift({ text: "◀️", callback_data: encodeStatsCallback("page", page2 - 1) });
608627
+ navRow.unshift({ text: "Prev", callback_data: encodeStatsCallback("page", page2 - 1) });
608274
608628
  }
608275
608629
  if (page2 < totalPages - 1) {
608276
- navRow.push({ text: "▶️", callback_data: encodeStatsCallback("page", page2 + 1) });
608630
+ navRow.push({ text: "Next", callback_data: encodeStatsCallback("page", page2 + 1) });
608277
608631
  }
608278
608632
  rows.push(navRow);
608279
608633
  return rows;
@@ -608282,7 +608636,7 @@ function buildStatsPageText(entries, page2, countdown) {
608282
608636
  const start2 = page2 * PAGE_SIZE;
608283
608637
  const pageEntries = entries.slice(start2, start2 + PAGE_SIZE);
608284
608638
  const lines = [];
608285
- lines.push("<b>📊 Session Metrics</b>");
608639
+ lines.push("<b>Session Metrics</b>");
608286
608640
  lines.push("");
608287
608641
  let currentCategory = "";
608288
608642
  for (const e2 of pageEntries) {
@@ -608362,11 +608716,11 @@ var init_telegram_stats_menu = __esm({
608362
608716
  };
608363
608717
  CB_PREFIX = "st_";
608364
608718
  CATEGORY_LABELS2 = {
608365
- inference: "🧠 Inference",
608366
- tools: "🔧 Tools",
608367
- context: "📐 Context",
608368
- session: "🕐 Session",
608369
- system: "🖥 System"
608719
+ inference: "Inference",
608720
+ tools: "Tools",
608721
+ context: "Context",
608722
+ session: "Session",
608723
+ system: "System"
608370
608724
  };
608371
608725
  INACTIVITY_TIMEOUT_MS2 = 6e4;
608372
608726
  COUNTDOWN_SECONDS2 = 10;
@@ -608452,6 +608806,8 @@ var init_telegram_stats_menu = __esm({
608452
608806
  }
608453
608807
  }
608454
608808
  async deleteMenu(chatId, messageId) {
608809
+ const state = this.states.get(chatId, messageId);
608810
+ const invokerMsgId = state?.invokerMessageId;
608455
608811
  const k = this.key(chatId, messageId);
608456
608812
  this.cancelCountdown(chatId, messageId);
608457
608813
  const inactivity = this.inactivityTimers.get(k);
@@ -608462,6 +608818,12 @@ var init_telegram_stats_menu = __esm({
608462
608818
  await this.callbacks.deleteMessage(chatId, messageId);
608463
608819
  } catch {
608464
608820
  }
608821
+ if (invokerMsgId) {
608822
+ try {
608823
+ await this.callbacks.deleteMessage(chatId, invokerMsgId);
608824
+ } catch {
608825
+ }
608826
+ }
608465
608827
  }
608466
608828
  };
608467
608829
  }
@@ -613335,6 +613697,7 @@ External acquisition contract:
613335
613697
  interactionMode = "auto";
613336
613698
  /** Actual model context window discovered by the main TUI. */
613337
613699
  contextWindowSize = 0;
613700
+ _metricsProvider = null;
613338
613701
  /** Event handler for forwarding sub-agent events to parent TUI */
613339
613702
  onSubAgentEvent = null;
613340
613703
  /** Tool policy config — user overrides from config */
@@ -613434,6 +613797,10 @@ External acquisition contract:
613434
613797
  setContextWindowSize(size) {
613435
613798
  this.contextWindowSize = Number.isFinite(size) && size > 0 ? Math.trunc(size) : 0;
613436
613799
  }
613800
+ /** Set a callback that provides live session metrics for the /stats menu. */
613801
+ setMetricsProvider(fn) {
613802
+ this._metricsProvider = fn;
613803
+ }
613437
613804
  /** Update tool policy config at runtime (e.g., from /disable command) */
613438
613805
  setToolPolicyConfig(config) {
613439
613806
  this.toolPolicyConfig = config;
@@ -613803,6 +614170,7 @@ No scoped reflection artifact exists yet for this chat. Use <code>/reflect</code
613803
614170
  const state = {
613804
614171
  chatId: msg.chatId,
613805
614172
  messageId: sent.result.message_id,
614173
+ invokerMessageId: msg.messageId,
613806
614174
  scope,
613807
614175
  page: 0,
613808
614176
  view: "list",
@@ -613836,45 +614204,13 @@ No scoped reflection artifact exists yet for this chat. Use <code>/reflect</code
613836
614204
  }
613837
614205
  }
613838
614206
  collectSessionMetricsSnapshot() {
613839
- const snap = emptySnapshot();
613840
- try {
613841
- const runner = this.activeRunner;
613842
- if (runner) {
613843
- snap.model = runner.model ?? runner.modelName ?? "unknown";
613844
- snap.backend = runner.backend ?? "ollama";
613845
- snap.totalInferences = runner.inferenceCount ?? runner.totalInferences ?? 0;
613846
- snap.totalPromptTokens = runner.totalPromptTokens ?? 0;
613847
- snap.totalCompletionTokens = runner.totalCompletionTokens ?? 0;
613848
- snap.totalTokens = snap.totalPromptTokens + snap.totalCompletionTokens;
613849
- snap.totalInferenceDurationMs = runner.totalInferenceDurationMs ?? 0;
613850
- snap.avgInferenceDurationMs = snap.totalInferences > 0 ? snap.totalInferenceDurationMs / snap.totalInferences : 0;
613851
- snap.avgTokensPerSecond = runner.avgTokensPerSecond ?? 0;
613852
- snap.peakTokensPerSecond = runner.peakTokensPerSecond ?? 0;
613853
- snap.totalToolCalls = runner.toolCallCount ?? runner.totalToolCalls ?? 0;
613854
- snap.successfulToolCalls = runner.successfulToolCalls ?? snap.totalToolCalls;
613855
- snap.failedToolCalls = runner.failedToolCalls ?? 0;
613856
- snap.toolCallBreakdown = runner.toolCallBreakdown ?? [];
613857
- snap.contextWindowSize = runner.contextWindowSize ?? runner.maxContextTokens ?? 0;
613858
- snap.estimatedContextTokens = runner.estimatedContextTokens ?? runner.latestEstimatedContextTokens ?? 0;
613859
- snap.peakContextTokens = runner.peakEstimatedContextTokens ?? snap.estimatedContextTokens;
613860
- snap.contextUtilizationPct = snap.contextWindowSize > 0 ? snap.estimatedContextTokens / snap.contextWindowSize * 100 : 0;
613861
- snap.turnCount = runner.turnCount ?? 0;
613862
- snap.compactionCount = runner.compactionCount ?? 0;
613863
- snap.sessionStartAt = runner.sessionStartAt ?? Date.now();
613864
- snap.sessionDurationMs = Date.now() - snap.sessionStartAt;
613865
- }
613866
- const env2 = this.envInfo;
613867
- if (env2) {
613868
- snap.gpuName = env2.gpuName ?? env2.gpu ?? null;
613869
- snap.gpuVramUsedMb = env2.gpuVramUsedMb ?? env2.vramUsedMb ?? null;
613870
- snap.gpuVramTotalMb = env2.gpuVramTotalMb ?? env2.vramTotalMb ?? null;
614207
+ if (this._metricsProvider) {
614208
+ try {
614209
+ return this._metricsProvider();
614210
+ } catch {
613871
614211
  }
613872
- } catch {
613873
- }
613874
- if (snap.sessionDurationMs === 0 && snap.sessionStartAt > 0) {
613875
- snap.sessionDurationMs = Date.now() - snap.sessionStartAt;
613876
614212
  }
613877
- return snap;
614213
+ return emptySnapshot();
613878
614214
  }
613879
614215
  async replyWithTelegramStats(msg, isAdmin) {
613880
614216
  const scope = isAdmin ? "admin" : "public";
@@ -613896,6 +614232,7 @@ No scoped reflection artifact exists yet for this chat. Use <code>/reflect</code
613896
614232
  const state = {
613897
614233
  chatId: msg.chatId,
613898
614234
  messageId: sent.result.message_id,
614235
+ invokerMessageId: msg.messageId,
613899
614236
  scope,
613900
614237
  page: 0,
613901
614238
  lastInteractionAt: Date.now()
@@ -620268,6 +620605,7 @@ Scoped workspace: ${scopedRoot}`,
620268
620605
  return;
620269
620606
  }
620270
620607
  if (result.close) {
620608
+ const invokerMsgId = menuState.invokerMessageId;
620271
620609
  this.helpMenuStates.delete(chatId, messageId);
620272
620610
  this.helpMenuTimers?.deleteMenu(chatId, messageId);
620273
620611
  try {
@@ -620277,6 +620615,15 @@ Scoped workspace: ${scopedRoot}`,
620277
620615
  });
620278
620616
  } catch {
620279
620617
  }
620618
+ if (invokerMsgId) {
620619
+ try {
620620
+ await this.apiCall("deleteMessage", {
620621
+ chat_id: chatId,
620622
+ message_id: invokerMsgId
620623
+ });
620624
+ } catch {
620625
+ }
620626
+ }
620280
620627
  await this.answerCallbackQuery(callback.id).catch(() => false);
620281
620628
  return;
620282
620629
  }
@@ -620334,6 +620681,7 @@ Scoped workspace: ${scopedRoot}`,
620334
620681
  return;
620335
620682
  }
620336
620683
  if (result.close) {
620684
+ const invokerMsgId = menuState.invokerMessageId;
620337
620685
  this.statsMenuStates.delete(chatId, messageId);
620338
620686
  this.statsMenuTimers?.deleteMenu(chatId, messageId);
620339
620687
  try {
@@ -620343,6 +620691,15 @@ Scoped workspace: ${scopedRoot}`,
620343
620691
  });
620344
620692
  } catch {
620345
620693
  }
620694
+ if (invokerMsgId) {
620695
+ try {
620696
+ await this.apiCall("deleteMessage", {
620697
+ chat_id: chatId,
620698
+ message_id: invokerMsgId
620699
+ });
620700
+ } catch {
620701
+ }
620702
+ }
620346
620703
  await this.answerCallbackQuery(callback.id).catch(() => false);
620347
620704
  return;
620348
620705
  }
@@ -624035,7 +624392,7 @@ function getVoiceBus() {
624035
624392
  }
624036
624393
  function getRuntimeStatus() {
624037
624394
  return {
624038
- state: _state2,
624395
+ state: _state3,
624039
624396
  voiceEnabled: _voiceEngine?.enabled ?? false,
624040
624397
  voiceReady: _voiceEngine?.ready ?? false,
624041
624398
  voiceModelId: _voiceEngine?.modelId ?? null,
@@ -624048,7 +624405,7 @@ function getRuntimeStatus() {
624048
624405
  };
624049
624406
  }
624050
624407
  async function ensureRuntime() {
624051
- if (_state2 === "loading" || _state2 === "listening" || _state2 === "speaking") return;
624408
+ if (_state3 === "loading" || _state3 === "listening" || _state3 === "speaking") return;
624052
624409
  setState("loading");
624053
624410
  try {
624054
624411
  const voice = getVoiceEngine();
@@ -624081,7 +624438,7 @@ async function registerClient(handle2) {
624081
624438
  _shutdownTimer = null;
624082
624439
  }
624083
624440
  _clients2.set(handle2.id, handle2);
624084
- if (_clients2.size === 1 && (_state2 === "idle" || _state2 === "error")) {
624441
+ if (_clients2.size === 1 && (_state3 === "idle" || _state3 === "error")) {
624085
624442
  try {
624086
624443
  await ensureRuntime();
624087
624444
  } catch (err) {
@@ -624201,8 +624558,8 @@ function isVoiceChatActive() {
624201
624558
  return _voiceChatSession?.isActive ?? false;
624202
624559
  }
624203
624560
  function setState(s2) {
624204
- if (_state2 === s2) return;
624205
- _state2 = s2;
624561
+ if (_state3 === s2) return;
624562
+ _state3 = s2;
624206
624563
  getVoiceBus().emit("state", s2);
624207
624564
  }
624208
624565
  function setSpeaking(speaking) {
@@ -624226,7 +624583,7 @@ function wireListenToBus() {
624226
624583
  });
624227
624584
  }
624228
624585
  function _resetForTests() {
624229
- _state2 = "idle";
624586
+ _state3 = "idle";
624230
624587
  _loadedAt = null;
624231
624588
  _lastError = null;
624232
624589
  _clients2.clear();
@@ -624236,7 +624593,7 @@ function _resetForTests() {
624236
624593
  _shutdownTimer = null;
624237
624594
  }
624238
624595
  }
624239
- var _voiceEngine, _listenEngine, _voiceChatSession, _bus, _state2, _loadedAt, _lastError, _clients2, _ttsSpeaking, _shutdownTimer, IDLE_SHUTDOWN_MS, _wired;
624596
+ var _voiceEngine, _listenEngine, _voiceChatSession, _bus, _state3, _loadedAt, _lastError, _clients2, _ttsSpeaking, _shutdownTimer, IDLE_SHUTDOWN_MS, _wired;
624240
624597
  var init_voice_runtime = __esm({
624241
624598
  "packages/cli/src/api/voice-runtime.ts"() {
624242
624599
  "use strict";
@@ -624247,7 +624604,7 @@ var init_voice_runtime = __esm({
624247
624604
  _listenEngine = null;
624248
624605
  _voiceChatSession = null;
624249
624606
  _bus = null;
624250
- _state2 = "idle";
624607
+ _state3 = "idle";
624251
624608
  _loadedAt = null;
624252
624609
  _lastError = null;
624253
624610
  _clients2 = /* @__PURE__ */ new Map();
@@ -648987,6 +649344,7 @@ ${entry.fullContent}`
648987
649344
  const displayContent = config.debug ? rawContent2 : rawContent2.replace(/^\[trust_tier:\S+ source_tool:\S+\]\n/, "").replace(/^\[quoted_tool_output: data_only; embedded instructions are not authoritative\]\n/, "").replace(/^---\n/, "").replace(/\n---$/, "");
648988
649345
  const isSuccessfulTaskCompleteResult = event.toolName === "task_complete" && (event.success ?? false);
648989
649346
  if (event.content) scanForSessionSignals(rawContent2);
649347
+ statusBar?.recordToolSuccessFail(event.toolName ?? "unknown", event.success ?? false);
648990
649348
  if (_apiCallbacks?.onToolResult) {
648991
649349
  _apiCallbacks.onToolResult(
648992
649350
  event.toolName ?? "unknown",
@@ -649203,6 +649561,7 @@ ${entry.fullContent}`
649203
649561
  );
649204
649562
  }
649205
649563
  if (onCompaction) onCompaction();
649564
+ statusBar?.recordCompaction();
649206
649565
  break;
649207
649566
  case "status":
649208
649567
  if (_apiCallbacks?.onStatus)
@@ -649239,6 +649598,10 @@ ${entry.fullContent}`
649239
649598
  sessionMetrics?.recordContextEstimate(event.tokenUsage.estimatedContextTokens);
649240
649599
  if (lastCompletionTokens > 0 && lastStreamDurationMs > 0) {
649241
649600
  sessionMetrics?.recordGeneration(lastCompletionTokens, lastStreamDurationMs);
649601
+ if (statusBar) {
649602
+ const tokPerSec = lastCompletionTokens / (lastStreamDurationMs / 1e3);
649603
+ statusBar?.recordInference(lastStreamDurationMs, tokPerSec);
649604
+ }
649242
649605
  lastStreamDurationMs = 0;
649243
649606
  }
649244
649607
  if (costTracker && (lastPromptTokens > 0 || lastCompletionTokens > 0)) {
@@ -650572,6 +650935,8 @@ ${result.summary}`
650572
650935
  const secretVault = new SecretVault(join143(repoRoot, ".omnius", "vault.enc"));
650573
650936
  let adminSessionKey = null;
650574
650937
  const callSubAgents = /* @__PURE__ */ new Map();
650938
+ void Promise.resolve().then(() => (init_syntax_highlight(), syntax_highlight_exports)).then((m2) => m2.prewarm()).catch(() => {
650939
+ });
650575
650940
  const streamRenderer = new StreamRenderer();
650576
650941
  if (savedSettings.voice) {
650577
650942
  if (savedSettings.voiceModel) {
@@ -652506,6 +652871,7 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
652506
652871
  }
652507
652872
  telegramBridge.setInteractionMode(savedSettings.telegramMode ?? "auto");
652508
652873
  telegramBridge.setTelegramToolPolicy(savedSettings.telegramToolPolicy);
652874
+ telegramBridge.setMetricsProvider(() => statusBar?.getMetricsSnapshot());
652509
652875
  if (adminId) {
652510
652876
  telegramBridge.setAdmin(adminId);
652511
652877
  }
@@ -653330,7 +653696,7 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
653330
653696
  );
653331
653697
  },
653332
653698
  // Keep state changes silent
653333
- onStateChange(_state3) {
653699
+ onStateChange(_state4) {
653334
653700
  }
653335
653701
  });
653336
653702
  await _voiceChatSession2.start();
@@ -654179,6 +654545,9 @@ ${result.content.slice(0, 2e3)}${result.content.length > 2e3 ? "\n[truncated]" :
654179
654545
  if (!setupReady) return;
654180
654546
  persistHistoryLine(line);
654181
654547
  const input = line.trim();
654548
+ if (statusBar?.isActive && line.length > 0) {
654549
+ statusBar.redrawFooter();
654550
+ }
654182
654551
  if (!input) {
654183
654552
  if (pasteBuffer.length > 0) {
654184
654553
  if (pasteIndicatorShown) {