omnius 1.0.651 → 1.0.652

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
@@ -352821,6 +352821,48 @@ function attachWorkboardEvidence(workingDir, input) {
352821
352821
  payload: { cardId: card.id, evidence: uniqueEvidence }
352822
352822
  }]);
352823
352823
  }
352824
+ function attachTrustedWorkboardCommandEvidence(workingDir, input) {
352825
+ const runId = normalizeWorkboardRunId(input.runId);
352826
+ const snapshot = requireWorkboard(workingDir, runId);
352827
+ const card = snapshot.cards.find((existing) => existing.id === input.cardId);
352828
+ if (!card) {
352829
+ recordWorkboardDiagnostic(workingDir, runId, makeDiagnostic("card_not_found", "error", `card '${input.cardId}' does not exist`, {
352830
+ cardId: input.cardId,
352831
+ actor: input.actor
352832
+ }), input.actor);
352833
+ throw new WorkboardError("card_not_found", `card '${input.cardId}' does not exist`);
352834
+ }
352835
+ const receipt2 = normalizeTrustedCommandReceipt(input.receipt);
352836
+ const evidence = normalizeEvidence(input.evidence, input.actor);
352837
+ if (evidence.kind !== "tool_result") {
352838
+ throw new WorkboardError("invalid_command_evidence", "trusted command evidence must use kind 'tool_result'");
352839
+ }
352840
+ const evidenceCommand = normalizeCommandText(evidence.command ?? "");
352841
+ if (evidenceCommand && evidenceCommand !== receipt2.canonicalCommand) {
352842
+ throw new WorkboardError("invalid_command_evidence", "evidence command does not match the trusted execution receipt", {
352843
+ evidenceCommand,
352844
+ receiptCommand: receipt2.canonicalCommand
352845
+ });
352846
+ }
352847
+ const trustedEvidence = {
352848
+ ...evidence,
352849
+ command: receipt2.command,
352850
+ commandReceipt: receipt2
352851
+ };
352852
+ const uniqueEvidence = filterNewEvidence(card.evidence, [trustedEvidence]);
352853
+ if (uniqueEvidence.length === 0)
352854
+ return snapshot;
352855
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
352856
+ return appendWorkboardEvents(workingDir, runId, [{
352857
+ schemaVersion: WORKBOARD_SCHEMA_VERSION,
352858
+ id: newEventId("evidence"),
352859
+ runId,
352860
+ type: "evidence.attached",
352861
+ createdAt: now2,
352862
+ actor: cleanString(input.actor),
352863
+ payload: { cardId: card.id, evidence: uniqueEvidence }
352864
+ }]);
352865
+ }
352824
352866
  function completeWorkboardCard(workingDir, input) {
352825
352867
  const runId = normalizeWorkboardRunId(input.runId);
352826
352868
  let snapshot = requireWorkboard(workingDir, runId);
@@ -353036,7 +353078,7 @@ function verifyDeclaredVerifierEvidence(card) {
353036
353078
  const verifier = normalizeCommandText(card.verifierCommand ?? "");
353037
353079
  if (!verifier)
353038
353080
  return { valid: true, missing: [] };
353039
- const hasExactLiveCommand = card.evidence.some((evidence) => evidence.kind === "tool_result" && normalizeCommandText(evidence.command ?? "") === verifier);
353081
+ const hasExactLiveCommand = card.evidence.some((evidence) => isSuccessfulFreshVerifierReceipt(evidence, verifier, card.startedAt));
353040
353082
  return hasExactLiveCommand ? { valid: true, missing: [] } : { valid: false, missing: [`live verifier output: ${card.verifierCommand}`] };
353041
353083
  }
353042
353084
  function verifyCompileCardEvidence(card) {
@@ -353051,9 +353093,7 @@ function verifyCompileCardEvidence(card) {
353051
353093
  };
353052
353094
  }
353053
353095
  const liveEvidence = card.evidence.some((evidence) => {
353054
- if (evidence.kind !== "tool_result")
353055
- return false;
353056
- if (normalizeCommandText(evidence.command ?? "") !== verifier)
353096
+ if (!isSuccessfulFreshVerifierReceipt(evidence, verifier, card.startedAt))
353057
353097
  return false;
353058
353098
  const values = [
353059
353099
  evidence.summary,
@@ -353618,7 +353658,8 @@ function cloneCard(card) {
353618
353658
  function cloneEvidence(evidence) {
353619
353659
  return {
353620
353660
  ...evidence,
353621
- verifies: evidence.verifies ? [...evidence.verifies] : void 0
353661
+ verifies: evidence.verifies ? [...evidence.verifies] : void 0,
353662
+ commandReceipt: evidence.commandReceipt ? cloneCommandEvidenceReceipt(evidence.commandReceipt) : void 0
353622
353663
  };
353623
353664
  }
353624
353665
  function appendUniqueEvidence(existing, incoming) {
@@ -353657,6 +353698,15 @@ function evidenceFingerprint(evidence) {
353657
353698
  url: evidence.url,
353658
353699
  message: evidence.message,
353659
353700
  verifies: [...evidence.verifies ?? []].sort(),
353701
+ commandReceipt: evidence.commandReceipt ? {
353702
+ schema: evidence.commandReceipt.schema,
353703
+ canonicalCommand: evidence.commandReceipt.canonicalCommand,
353704
+ cwd: evidence.commandReceipt.cwd,
353705
+ exitCode: evidence.commandReceipt.exitCode,
353706
+ timedOut: evidence.commandReceipt.timedOut,
353707
+ startedAtIso: evidence.commandReceipt.startedAtIso,
353708
+ finishedAtIso: evidence.commandReceipt.finishedAtIso
353709
+ } : void 0,
353660
353710
  contentDigest
353661
353711
  };
353662
353712
  return createHash29("sha256").update(JSON.stringify(key)).digest("hex");
@@ -353667,6 +353717,63 @@ function cleanString(value2) {
353667
353717
  function normalizeCommandText(value2) {
353668
353718
  return value2.replace(/\s+/g, " ").trim();
353669
353719
  }
353720
+ function normalizeTrustedCommandReceipt(receipt2) {
353721
+ if (receipt2?.schema !== "omnius.command-evidence-receipt.v1") {
353722
+ throw new WorkboardError("invalid_command_receipt", "unsupported command evidence receipt schema");
353723
+ }
353724
+ const command = cleanString(receipt2.command);
353725
+ const canonicalCommand3 = normalizeCommandText(receipt2.canonicalCommand ?? "");
353726
+ const cwd4 = cleanString(receipt2.cwd);
353727
+ const startedAt2 = Date.parse(receipt2.startedAtIso);
353728
+ const finishedAt = Date.parse(receipt2.finishedAtIso);
353729
+ if (!command || !canonicalCommand3 || !cwd4 || !Number.isFinite(startedAt2) || !Number.isFinite(finishedAt)) {
353730
+ throw new WorkboardError("invalid_command_receipt", "command evidence receipt is missing command, cwd, or valid timestamps");
353731
+ }
353732
+ if (normalizeCommandText(command) !== canonicalCommand3) {
353733
+ throw new WorkboardError("invalid_command_receipt", "command receipt canonical form does not match its command");
353734
+ }
353735
+ if (finishedAt < startedAt2) {
353736
+ throw new WorkboardError("invalid_command_receipt", "command receipt finished before it started");
353737
+ }
353738
+ if (typeof receipt2.pipefail !== "boolean" || typeof receipt2.timedOut !== "boolean") {
353739
+ throw new WorkboardError("invalid_command_receipt", "command evidence receipt is missing process flags");
353740
+ }
353741
+ return cloneCommandEvidenceReceipt({
353742
+ ...receipt2,
353743
+ command,
353744
+ canonicalCommand: canonicalCommand3,
353745
+ cwd: cwd4
353746
+ });
353747
+ }
353748
+ function cloneCommandEvidenceReceipt(receipt2) {
353749
+ return {
353750
+ ...receipt2,
353751
+ observedPaths: receipt2.observedPaths ? [...receipt2.observedPaths] : void 0,
353752
+ artifactHashes: receipt2.artifactHashes ? receipt2.artifactHashes.map((artifact) => ({ ...artifact })) : void 0,
353753
+ declaredVerification: receipt2.declaredVerification ? { ...receipt2.declaredVerification } : void 0
353754
+ };
353755
+ }
353756
+ function isSuccessfulFreshVerifierReceipt(evidence, verifier, notBeforeIso) {
353757
+ if (evidence.kind !== "tool_result")
353758
+ return false;
353759
+ const receipt2 = evidence.commandReceipt;
353760
+ if (!receipt2 || receipt2.schema !== "omnius.command-evidence-receipt.v1")
353761
+ return false;
353762
+ if (normalizeCommandText(receipt2.canonicalCommand ?? "") !== verifier)
353763
+ return false;
353764
+ if (receipt2.timedOut || receipt2.exitCode !== 0)
353765
+ return false;
353766
+ const startedAt2 = Date.parse(receipt2.startedAtIso);
353767
+ const finishedAt = Date.parse(receipt2.finishedAtIso);
353768
+ if (!Number.isFinite(startedAt2) || !Number.isFinite(finishedAt) || finishedAt < startedAt2)
353769
+ return false;
353770
+ if (notBeforeIso) {
353771
+ const notBefore = Date.parse(notBeforeIso);
353772
+ if (Number.isFinite(notBefore) && finishedAt < notBefore)
353773
+ return false;
353774
+ }
353775
+ return true;
353776
+ }
353670
353777
  function requiredClean(value2, name10) {
353671
353778
  const cleaned = cleanString(value2);
353672
353779
  if (!cleaned)
@@ -618272,6 +618379,7 @@ __export(dist_exports2, {
618272
618379
  artifactManifestFromBytes: () => artifactManifestFromBytes,
618273
618380
  assertAsrEngineAvailable: () => assertAsrEngineAvailable,
618274
618381
  associationAccuracy: () => associationAccuracy,
618382
+ attachTrustedWorkboardCommandEvidence: () => attachTrustedWorkboardCommandEvidence,
618275
618383
  attachWorkboardEvidence: () => attachWorkboardEvidence,
618276
618384
  audioGenerationDir: () => audioGenerationDir,
618277
618385
  audioGenerationModelPresets: () => audioGenerationModelPresets,
@@ -622816,7 +622924,12 @@ var init_operational_world = __esm({
622816
622924
  "supersedes",
622817
622925
  "mentions",
622818
622926
  "governs",
622819
- "blocks"
622927
+ "blocks",
622928
+ "assigned_to",
622929
+ "attempts",
622930
+ "invalidates",
622931
+ "resolves",
622932
+ "synthesizes"
622820
622933
  ]);
622821
622934
  WorkGraphEdgeSchema = z16.object({
622822
622935
  schemaVersion: OperationalWorldSchemaVersionSchema,
@@ -626708,6 +626821,53 @@ function decompositionCycleIssues(graph) {
626708
626821
  }
626709
626822
  return issues;
626710
626823
  }
626824
+ function dependencyCycleIssues(graph) {
626825
+ const adjacency = /* @__PURE__ */ new Map();
626826
+ const add3 = (source, target) => {
626827
+ const values = adjacency.get(source) ?? [];
626828
+ values.push(target);
626829
+ adjacency.set(source, values);
626830
+ };
626831
+ for (const edge of graph.edges) {
626832
+ if (!edge.directed)
626833
+ continue;
626834
+ if (edge.kind === "depends_on" || edge.kind === "blocked_by") {
626835
+ add3(edge.sourceNodeId, edge.targetNodeId);
626836
+ } else if (edge.kind === "blocks") {
626837
+ add3(edge.targetNodeId, edge.sourceNodeId);
626838
+ }
626839
+ }
626840
+ for (const values of adjacency.values())
626841
+ values.sort();
626842
+ const cycle = directedCycle(graph.nodes.map((node) => node.nodeId), adjacency);
626843
+ return cycle ? [
626844
+ issue("dependency_cycle", `Dependency/blocking cycle detected: ${cycle.join(" -> ")}`, ["edges"])
626845
+ ] : [];
626846
+ }
626847
+ function relationEndpointIssues(graph) {
626848
+ const nodes = new Map(graph.nodes.map((node) => [node.nodeId, node]));
626849
+ const issues = [];
626850
+ const expectedTargets = {
626851
+ assigned_to: /* @__PURE__ */ new Set(["entity"]),
626852
+ evidenced_by: /* @__PURE__ */ new Set(["evidence"]),
626853
+ verified_by: /* @__PURE__ */ new Set(["verification"]),
626854
+ satisfies: /* @__PURE__ */ new Set(["requirement", "acceptance"])
626855
+ };
626856
+ graph.edges.forEach((edge, index) => {
626857
+ const source = nodes.get(edge.sourceNodeId);
626858
+ const target = nodes.get(edge.targetNodeId);
626859
+ if (!source || !target)
626860
+ return;
626861
+ const allowedTargets = expectedTargets[edge.kind];
626862
+ if (allowedTargets && !allowedTargets.has(target.kind)) {
626863
+ issues.push(issue("invalid_relation_endpoint", `${edge.kind} edge ${edge.edgeId} cannot target ${target.kind}; expected ${[...allowedTargets].join(" or ")}.`, ["edges", index, "targetNodeId"]));
626864
+ }
626865
+ if (edge.kind === "decomposes_to" && ["objective", "claim", "evidence", "conflict", "validation"].includes(target.kind)) {
626866
+ issues.push(issue("invalid_relation_endpoint", `decomposes_to edge ${edge.edgeId} cannot target ${target.kind}.`, ["edges", index, "targetNodeId"]));
626867
+ }
626868
+ });
626869
+ return issues;
626870
+ }
626711
626871
  function graphTemporalIssues(graph) {
626712
626872
  const issues = [];
626713
626873
  if (Date.parse(graph.updatedAt) < Date.parse(graph.createdAt)) {
@@ -626720,6 +626880,8 @@ function semanticGraphIssues(graph) {
626720
626880
  ...duplicateSemanticIssues(graph),
626721
626881
  ...requiredEvidenceIssues(graph),
626722
626882
  ...decompositionCycleIssues(graph),
626883
+ ...dependencyCycleIssues(graph),
626884
+ ...relationEndpointIssues(graph),
626723
626885
  ...graphTemporalIssues(graph)
626724
626886
  ];
626725
626887
  }
@@ -626755,6 +626917,16 @@ function validatePatchInternal(world, input) {
626755
626917
  if (patch.graphId !== world.graph.graphId) {
626756
626918
  issues.push(issue("graph_id_mismatch", `Patch graph ${patch.graphId} does not match world graph ${world.graph.graphId}.`, ["graphId"]));
626757
626919
  }
626920
+ if (patch.createdBy.startsWith("model:")) {
626921
+ patch.operations.forEach((operation, index) => {
626922
+ if (operation.op === "upsert_node" && operation.node.authority !== "inferred") {
626923
+ issues.push(issue("model_authority_escalation", `Model patch cannot assign ${operation.node.authority} authority to node ${operation.node.nodeId}.`, ["operations", index, "node", "authority"], { nodeId: operation.node.nodeId }));
626924
+ }
626925
+ if (operation.op === "upsert_claim" && operation.claim.authority !== void 0 && operation.claim.authority !== "inferred") {
626926
+ issues.push(issue("model_authority_escalation", `Model patch cannot assign ${operation.claim.authority} authority to claim ${operation.claim.claimId}.`, ["operations", index, "claim", "authority"]));
626927
+ }
626928
+ });
626929
+ }
626758
626930
  if (patch.baseRevision !== world.graph.revision) {
626759
626931
  issues.push(issue("base_revision_mismatch", `Patch base revision ${patch.baseRevision} does not match current revision ${world.graph.revision}.`, ["baseRevision"]));
626760
626932
  }
@@ -627061,15 +627233,25 @@ function buildReasoningSlice(input) {
627061
627233
  throw new Error("A reasoning slice requires at least one existing focus node.");
627062
627234
  }
627063
627235
  const selected = new Set(requestedFocus);
627236
+ const adjacency = /* @__PURE__ */ new Map();
627237
+ const connect3 = (left, right) => {
627238
+ const values = adjacency.get(left) ?? [];
627239
+ values.push(right);
627240
+ adjacency.set(left, values);
627241
+ };
627242
+ for (const edge of input.graph.edges) {
627243
+ connect3(edge.sourceNodeId, edge.targetNodeId);
627244
+ connect3(edge.targetNodeId, edge.sourceNodeId);
627245
+ }
627246
+ for (const values of adjacency.values())
627247
+ values.sort();
627064
627248
  let frontier = [...requestedFocus];
627065
627249
  for (let hop = 0; hop < graphHops && frontier.length > 0; hop++) {
627066
627250
  const next = /* @__PURE__ */ new Set();
627067
- for (const edge of input.graph.edges) {
627068
- if (frontier.includes(edge.sourceNodeId) && !selected.has(edge.targetNodeId)) {
627069
- next.add(edge.targetNodeId);
627070
- }
627071
- if (frontier.includes(edge.targetNodeId) && !selected.has(edge.sourceNodeId)) {
627072
- next.add(edge.sourceNodeId);
627251
+ for (const nodeId2 of frontier) {
627252
+ for (const neighborId of adjacency.get(nodeId2) ?? []) {
627253
+ if (!selected.has(neighborId))
627254
+ next.add(neighborId);
627073
627255
  }
627074
627256
  }
627075
627257
  for (const id2 of [...next].sort())
@@ -627264,25 +627446,77 @@ function buildReasoningSlice(input) {
627264
627446
  };
627265
627447
  return ReasoningSliceSchema.parse(result);
627266
627448
  }
627267
- function renderReasoningSlice(slice2) {
627268
- const lines = [
627269
- "[OPERATIONAL WORK GRAPH]",
627270
- `Revision: ${slice2.revision}`,
627271
- `Objective: ${slice2.objective}`,
627272
- `Focus: ${slice2.focusNodeIds.join(", ")}`,
627273
- "Nodes:",
627274
- ...slice2.nodes.slice(0, 80).map((node) => `- ${node.nodeId} [${node.kind}/${node.status}/${node.authority}] ${node.label}`),
627275
- "Relations:",
627276
- ...slice2.edges.slice(0, 120).map((edge) => `- ${edge.sourceNodeId} --${edge.kind}--> ${edge.targetNodeId}`),
627277
- "Claims:",
627278
- ...slice2.claims.length > 0 ? slice2.claims.slice(0, 40).map((claim) => `- ${claim.claimId} [${claim.status}/${claim.authority ?? "inferred"}] ${claim.statement}`) : ["- none"],
627279
- "Conflicts:",
627280
- ...slice2.conflicts.length > 0 ? slice2.conflicts.slice(0, 20).map((conflict) => `- ${conflict.conflictId} [${conflict.status}] ${conflict.summary}`) : ["- none"],
627281
- `Coverage: ${slice2.selection.truncated ? "partial" : "complete"}; omissions=${slice2.selection.omissions.length}`,
627282
- "Treat explicit nodes as required work. Treat inferred nodes as advisory until adoption.",
627283
- "[/OPERATIONAL WORK GRAPH]"
627284
- ];
627285
- return lines.join("\n");
627449
+ function renderReasoningSlice(slice2, maxCharacters = slice2.selection.maxCharacters) {
627450
+ const budget = Math.max(512, maxCharacters);
627451
+ const closing = "[/OPERATIONAL WORK GRAPH]";
627452
+ const lines = [];
627453
+ let used = 0;
627454
+ let renderedOmissions = 0;
627455
+ const append = (line, required = false) => {
627456
+ const size = line.length + (lines.length > 0 ? 1 : 0);
627457
+ const reserve = closing.length + 1;
627458
+ if (!required && used + size + reserve > budget) {
627459
+ renderedOmissions++;
627460
+ return false;
627461
+ }
627462
+ const value2 = required && used + size + reserve > budget ? line.slice(0, Math.max(0, budget - used - reserve - 4)) + "..." : line;
627463
+ lines.push(value2);
627464
+ used += value2.length + (lines.length > 1 ? 1 : 0);
627465
+ return true;
627466
+ };
627467
+ append("[OPERATIONAL WORK GRAPH]", true);
627468
+ append(`Revision: ${slice2.revision}`, true);
627469
+ append(`Objective: ${slice2.objective}`, true);
627470
+ append(`Focus: ${slice2.focusNodeIds.join(", ")}`, true);
627471
+ append("Nodes:", true);
627472
+ const nodePriority = (node) => {
627473
+ if (slice2.focusNodeIds.includes(node.nodeId))
627474
+ return 0;
627475
+ if (node.kind === "blocker" || node.status === "blocked")
627476
+ return 1;
627477
+ if (node.kind === "acceptance" || node.kind === "requirement")
627478
+ return 2;
627479
+ if (node.kind === "verification" || node.kind === "evidence")
627480
+ return 3;
627481
+ if (node.status === "active")
627482
+ return 4;
627483
+ return 5;
627484
+ };
627485
+ const renderedNodeIds = /* @__PURE__ */ new Set();
627486
+ for (const node of [...slice2.nodes].sort((left, right) => nodePriority(left) - nodePriority(right) || left.nodeId.localeCompare(right.nodeId))) {
627487
+ if (append(`- ${node.nodeId} [${node.kind}/${node.status}/${node.authority}] ${node.label}`)) {
627488
+ renderedNodeIds.add(node.nodeId);
627489
+ }
627490
+ }
627491
+ append("Relations:");
627492
+ for (const edge of slice2.edges) {
627493
+ if (!renderedNodeIds.has(edge.sourceNodeId) || !renderedNodeIds.has(edge.targetNodeId)) {
627494
+ renderedOmissions++;
627495
+ continue;
627496
+ }
627497
+ append(`- ${edge.sourceNodeId} --${edge.kind}--> ${edge.targetNodeId}`);
627498
+ }
627499
+ append("Claims:");
627500
+ if (slice2.claims.length === 0)
627501
+ append("- none");
627502
+ for (const claim of slice2.claims) {
627503
+ append(`- ${claim.claimId} [${claim.status}/${claim.authority ?? "inferred"}] ${claim.statement}`);
627504
+ }
627505
+ append("Conflicts:");
627506
+ if (slice2.conflicts.length === 0)
627507
+ append("- none");
627508
+ for (const conflict of slice2.conflicts) {
627509
+ append(`- ${conflict.conflictId} [${conflict.status}] ${conflict.summary}`);
627510
+ }
627511
+ const totalOmissions = slice2.selection.omissions.length + renderedOmissions;
627512
+ append(`Coverage: ${slice2.selection.truncated || renderedOmissions > 0 ? "partial" : "complete"}; omissions=${totalOmissions}`);
627513
+ append("Treat explicit nodes as required work. Treat inferred nodes as advisory until adoption.");
627514
+ while (used + closing.length + 1 > budget && lines.length > 1) {
627515
+ const removed = lines.pop();
627516
+ used -= removed.length + (lines.length > 0 ? 1 : 0);
627517
+ }
627518
+ lines.push(closing);
627519
+ return lines.join("\n").slice(0, budget);
627286
627520
  }
627287
627521
  function iso(value2, fallback) {
627288
627522
  return value2 && Number.isFinite(Date.parse(value2)) ? value2 : fallback;
@@ -627419,6 +627653,48 @@ function freshTodoVerificationEvidence(todo, ledger) {
627419
627653
  return Number.isFinite(finishedAt) && finishedAt >= lastMutation;
627420
627654
  });
627421
627655
  }
627656
+ function todoTargetPaths(todo) {
627657
+ return [
627658
+ ...todo.declaredArtifacts ?? [],
627659
+ ...(todo.targetRefs ?? []).filter((target) => target.kind === "path").map((target) => target.ref)
627660
+ ];
627661
+ }
627662
+ function todoHasExplicitProofObligation(todo) {
627663
+ return Boolean(todo.verifyCommand || (todo.acceptanceIds?.length ?? 0) > 0 || (todo.declaredArtifacts?.length ?? 0) > 0);
627664
+ }
627665
+ function freshTodoCompletionEvidence(todo, ledger) {
627666
+ if (!ledger)
627667
+ return [];
627668
+ if (todo.verifyCommand)
627669
+ return freshTodoVerificationEvidence(todo, ledger);
627670
+ const targets = todoTargetPaths(todo);
627671
+ const relevantMutations = ledger.evidence.filter((entry) => entry.kind === "file_change" || entry.role === "mutation").filter((entry) => targets.length === 0 || (entry.targetPaths ?? []).some((path16) => targets.some((target) => pathsOverlap(path16, target))));
627672
+ const lastMutation = Math.max(Number.NEGATIVE_INFINITY, ...relevantMutations.map((entry) => Date.parse(entry.observedAtIso)).filter(Number.isFinite));
627673
+ return ledger.evidence.filter((entry) => {
627674
+ if (entry.success !== true)
627675
+ return false;
627676
+ if (entry.role !== "verification" && entry.role !== "delivery")
627677
+ return false;
627678
+ if (entry.receipt && (entry.receipt.exitCode !== 0 || entry.receipt.timedOut)) {
627679
+ return false;
627680
+ }
627681
+ if (!entry.receipt && entry.kind !== "delivery_result" && entry.kind !== "human_input") {
627682
+ return false;
627683
+ }
627684
+ if (targets.length > 0 && !(entry.targetPaths ?? []).some((path16) => targets.some((target) => pathsOverlap(path16, target)))) {
627685
+ return false;
627686
+ }
627687
+ const observedAt = Date.parse(entry.observedAtIso);
627688
+ return Number.isFinite(observedAt) && observedAt >= lastMutation;
627689
+ });
627690
+ }
627691
+ function todoCompletionIsProven(todo, ledger) {
627692
+ if (todo.status !== "completed")
627693
+ return false;
627694
+ if (!todoHasExplicitProofObligation(todo))
627695
+ return true;
627696
+ return freshTodoCompletionEvidence(todo, ledger).length > 0;
627697
+ }
627422
627698
  function deepEqual2(left, right) {
627423
627699
  return JSON.stringify(left) === JSON.stringify(right);
627424
627700
  }
@@ -627434,6 +627710,10 @@ function buildWorkGraphProjectionPatch(input) {
627434
627710
  const sourceBindings = /* @__PURE__ */ new Map();
627435
627711
  const evidenceRefs = /* @__PURE__ */ new Map();
627436
627712
  const claims = /* @__PURE__ */ new Map();
627713
+ const todoProofEvidence = /* @__PURE__ */ new Map();
627714
+ const todoVerificationNodeIds = /* @__PURE__ */ new Map();
627715
+ const todoArtifactNodeIds = /* @__PURE__ */ new Map();
627716
+ const todoMutationNodeIds = /* @__PURE__ */ new Map();
627437
627717
  const addNode = (node) => {
627438
627718
  nodes.set(node.nodeId, node);
627439
627719
  };
@@ -627451,7 +627731,7 @@ function buildWorkGraphProjectionPatch(input) {
627451
627731
  attributes: projectedAttributes("adopted")
627452
627732
  });
627453
627733
  };
627454
- const allTodosVerified = input.todos.length > 0 && input.todos.every((todo) => todo.status === "completed" && (!todo.verifyCommand || freshTodoVerificationEvidence(todo, input.completionLedger).length > 0));
627734
+ const allTodosVerified = input.todos.length > 0 && input.todos.every((todo) => todoCompletionIsProven(todo, input.completionLedger));
627455
627735
  addNode({
627456
627736
  schemaVersion: input.graph.schemaVersion,
627457
627737
  nodeId: objectiveId,
@@ -627474,15 +627754,17 @@ function buildWorkGraphProjectionPatch(input) {
627474
627754
  const nodeId2 = todoNodeIds.get(todo.id);
627475
627755
  const parentNodeId = todo.parentId ? todoNodeIds.get(todo.parentId) ?? objectiveId : objectiveId;
627476
627756
  const authority = todoAuthority(todo);
627477
- const verifierEvidence = freshTodoVerificationEvidence(todo, input.completionLedger);
627478
- const completionVerified = todo.status === "completed" && (!todo.verifyCommand || verifierEvidence.length > 0);
627757
+ const verifierEvidence = freshTodoCompletionEvidence(todo, input.completionLedger);
627758
+ todoProofEvidence.set(todo.id, verifierEvidence);
627759
+ const completionVerified = todoCompletionIsProven(todo, input.completionLedger);
627760
+ const projectedTodoStatus = completionVerified ? "complete" : todo.status === "completed" ? "active" : todoStatus(todo.status);
627479
627761
  addNode({
627480
627762
  schemaVersion: input.graph.schemaVersion,
627481
627763
  nodeId: nodeId2,
627482
627764
  kind: todoKind(todo),
627483
627765
  label: todo.content.slice(0, 1e3),
627484
627766
  description: todo.blocker?.slice(0, 4e3),
627485
- status: todoStatus(todo.status),
627767
+ status: projectedTodoStatus,
627486
627768
  authority,
627487
627769
  parentNodeId,
627488
627770
  bindingIds: [],
@@ -627551,7 +627833,7 @@ function buildWorkGraphProjectionPatch(input) {
627551
627833
  nodeId: actionNodeId,
627552
627834
  kind: "action",
627553
627835
  label: `Action: ${todo.content}`.slice(0, 1e3),
627554
- status: completionVerified ? "complete" : todoStatus(todo.status),
627836
+ status: completionVerified ? "complete" : projectedTodoStatus,
627555
627837
  authority,
627556
627838
  parentNodeId: nodeId2,
627557
627839
  bindingIds: [],
@@ -627560,14 +627842,16 @@ function buildWorkGraphProjectionPatch(input) {
627560
627842
  attributes: projectedAttributes(authority, { todoId: todo.id })
627561
627843
  });
627562
627844
  addEdge(nodeId2, actionNodeId, "decomposes_to");
627845
+ const mutationNodeIds = [];
627563
627846
  for (const mutation of workActionContract.mutations) {
627564
627847
  const mutationNodeId = compactId("mutation-node", mutation.mutationId);
627848
+ mutationNodeIds.push(mutationNodeId);
627565
627849
  addNode({
627566
627850
  schemaVersion: input.graph.schemaVersion,
627567
627851
  nodeId: mutationNodeId,
627568
627852
  kind: "mutation",
627569
627853
  label: mutation.description.slice(0, 1e3),
627570
- status: completionVerified ? "complete" : todoStatus(todo.status),
627854
+ status: completionVerified ? "complete" : projectedTodoStatus,
627571
627855
  authority,
627572
627856
  parentNodeId: actionNodeId,
627573
627857
  bindingIds: [],
@@ -627583,6 +627867,37 @@ function buildWorkGraphProjectionPatch(input) {
627583
627867
  });
627584
627868
  addEdge(actionNodeId, mutationNodeId, "decomposes_to");
627585
627869
  }
627870
+ todoMutationNodeIds.set(todo.id, mutationNodeIds);
627871
+ }
627872
+ let verificationNodeId;
627873
+ if (verifierEvidence.length > 0) {
627874
+ verificationNodeId = compactId("verification", todo.id);
627875
+ todoVerificationNodeIds.set(todo.id, verificationNodeId);
627876
+ const proofEvidenceRefIds = verifierEvidence.map((entry) => compactId("evidence", entry.id));
627877
+ addNode({
627878
+ schemaVersion: input.graph.schemaVersion,
627879
+ nodeId: verificationNodeId,
627880
+ kind: "verification",
627881
+ label: `Verification: ${todo.content}`.slice(0, 1e3),
627882
+ description: todo.verifyCommand?.slice(0, 4e3),
627883
+ status: completionVerified ? "complete" : "active",
627884
+ authority,
627885
+ parentNodeId: nodeId2,
627886
+ bindingIds: [],
627887
+ evidenceRefIds: proofEvidenceRefIds,
627888
+ attributes: projectedAttributes(authority, {
627889
+ todoId: todo.id,
627890
+ verifyCommand: todo.verifyCommand,
627891
+ receiptBacked: verifierEvidence.every((entry) => Boolean(entry.receipt) || entry.kind === "delivery_result" || entry.kind === "human_input")
627892
+ })
627893
+ });
627894
+ addEdge(nodeId2, verificationNodeId, "verified_by", 1, proofEvidenceRefIds);
627895
+ for (const evidence of verifierEvidence) {
627896
+ addEdge(verificationNodeId, compactId("evidence-node", evidence.id), "evidenced_by", 1, [compactId("evidence", evidence.id)]);
627897
+ }
627898
+ for (const mutationNodeId of todoMutationNodeIds.get(todo.id) ?? []) {
627899
+ addEdge(mutationNodeId, verificationNodeId, "verified_by", 1, proofEvidenceRefIds);
627900
+ }
627586
627901
  }
627587
627902
  if (todo.blocker?.trim()) {
627588
627903
  const blockerNodeId = compactId("blocker", `${todo.id}:${todo.blocker}`);
@@ -627641,7 +627956,7 @@ function buildWorkGraphProjectionPatch(input) {
627641
627956
  kind: "acceptance",
627642
627957
  label: acceptanceId.startsWith("verify:") ? `Verify ${todo.content}` : acceptanceId,
627643
627958
  description: acceptanceId.startsWith("verify:") ? todo.verifyCommand : void 0,
627644
- status: acceptanceId.startsWith("verify:") && todo.verifyCommand ? verifierEvidence.length > 0 ? "complete" : "active" : todo.status === "completed" ? "complete" : "proposed",
627959
+ status: completionVerified ? "complete" : todo.status === "completed" ? "active" : "proposed",
627645
627960
  authority,
627646
627961
  parentNodeId: nodeId2,
627647
627962
  bindingIds: [],
@@ -627654,20 +627969,25 @@ function buildWorkGraphProjectionPatch(input) {
627654
627969
  }
627655
627970
  const isVerifierAcceptance = acceptanceId.startsWith("verify:") && Boolean(todo.verifyCommand);
627656
627971
  const verifierEvidenceIds = verifierEvidence.map((entry) => compactId("evidence", entry.id));
627657
- addEdge(nodeId2, acceptanceNodeId, isVerifierAcceptance ? verifierEvidenceIds.length > 0 ? "verified_by" : "depends_on" : "satisfies", 1, isVerifierAcceptance ? verifierEvidenceIds : []);
627972
+ addEdge(nodeId2, acceptanceNodeId, isVerifierAcceptance && verifierEvidenceIds.length === 0 ? "depends_on" : "satisfies", 1, isVerifierAcceptance ? verifierEvidenceIds : []);
627973
+ if (verificationNodeId) {
627974
+ addEdge(acceptanceNodeId, verificationNodeId, "verified_by", 1, verifierEvidenceIds);
627975
+ }
627658
627976
  }
627977
+ const artifactNodeIds = [];
627659
627978
  for (const artifact of todo.declaredArtifacts ?? []) {
627660
627979
  const artifactNodeId = compactId("artifact", artifact);
627980
+ artifactNodeIds.push(artifactNodeId);
627661
627981
  if (!nodes.has(artifactNodeId)) {
627662
627982
  addNode({
627663
627983
  schemaVersion: input.graph.schemaVersion,
627664
627984
  nodeId: artifactNodeId,
627665
627985
  kind: "artifact",
627666
627986
  label: artifact,
627667
- status: todo.status === "completed" ? "complete" : "proposed",
627987
+ status: completionVerified ? "complete" : todo.status === "completed" ? "active" : "proposed",
627668
627988
  authority,
627669
627989
  bindingIds: [],
627670
- evidenceRefIds: [],
627990
+ evidenceRefIds: verifierEvidence.map((entry) => compactId("evidence", entry.id)),
627671
627991
  attributes: projectedAttributes(authority, {
627672
627992
  targetType: "path",
627673
627993
  targetRef: artifact
@@ -627675,7 +627995,11 @@ function buildWorkGraphProjectionPatch(input) {
627675
627995
  });
627676
627996
  }
627677
627997
  addEdge(nodeId2, artifactNodeId, "produces");
627998
+ if (verificationNodeId) {
627999
+ addEdge(artifactNodeId, verificationNodeId, "verified_by", 1, verifierEvidence.map((entry) => compactId("evidence", entry.id)));
628000
+ }
627678
628001
  }
628002
+ todoArtifactNodeIds.set(todo.id, artifactNodeIds);
627679
628003
  for (const target of todo.targetRefs ?? []) {
627680
628004
  const targetNodeId = compactId(target.kind, target.ref);
627681
628005
  if (!nodes.has(targetNodeId)) {
@@ -627697,6 +628021,222 @@ function buildWorkGraphProjectionPatch(input) {
627697
628021
  addEdge(nodeId2, targetNodeId, target.kind === "path" ? "modifies" : "implements");
627698
628022
  }
627699
628023
  }
628024
+ const workboard = input.workboard;
628025
+ if (workboard) {
628026
+ const cardNodeIds = new Map(workboard.cards.map((card) => [card.id, compactId("workboard-card", `${workboard.runId}:${card.id}`)]));
628027
+ const cardStatus = (status) => {
628028
+ if (status === "verified")
628029
+ return "complete";
628030
+ if (status === "blocked" || status === "needs_changes")
628031
+ return "blocked";
628032
+ if (status === "in_progress" || status === "completed")
628033
+ return "active";
628034
+ return "proposed";
628035
+ };
628036
+ for (const card of workboard.cards) {
628037
+ const cardNodeId = cardNodeIds.get(card.id);
628038
+ const sourceTodoNodeIds = (card.sourceTodoIds ?? []).map((id2) => todoNodeIds.get(id2)).filter((id2) => Boolean(id2));
628039
+ addNode({
628040
+ schemaVersion: input.graph.schemaVersion,
628041
+ nodeId: cardNodeId,
628042
+ kind: "task",
628043
+ label: card.title.slice(0, 1e3),
628044
+ description: card.description.slice(0, 4e3),
628045
+ status: cardStatus(card.status),
628046
+ authority: "adopted",
628047
+ parentNodeId: sourceTodoNodeIds[0] ?? objectiveId,
628048
+ bindingIds: [],
628049
+ evidenceRefIds: [],
628050
+ attributes: projectedAttributes("adopted", {
628051
+ workboardRunId: workboard.runId,
628052
+ workboardCardId: card.id,
628053
+ canonicalKey: card.canonicalKey,
628054
+ lane: card.lane,
628055
+ role: card.role,
628056
+ assignee: card.assignee,
628057
+ sourceTodoIds: card.sourceTodoIds,
628058
+ evidenceRequired: card.evidenceRequired,
628059
+ evidenceRequirements: card.evidenceRequirements,
628060
+ verifierCommand: card.verifierCommand,
628061
+ createdAt: card.createdAt,
628062
+ updatedAt: card.updatedAt,
628063
+ verifiedAt: card.verifiedAt
628064
+ })
628065
+ });
628066
+ if (sourceTodoNodeIds.length > 0) {
628067
+ for (const todoNodeId of sourceTodoNodeIds) {
628068
+ addEdge(todoNodeId, cardNodeId, "implements", 1);
628069
+ }
628070
+ } else {
628071
+ addEdge(objectiveId, cardNodeId, "decomposes_to", 0.9);
628072
+ }
628073
+ if (card.assignee) {
628074
+ const assigneeNodeId = compactId("actor", card.assignee);
628075
+ if (!nodes.has(assigneeNodeId)) {
628076
+ addNode({
628077
+ schemaVersion: input.graph.schemaVersion,
628078
+ nodeId: assigneeNodeId,
628079
+ kind: "entity",
628080
+ label: card.assignee.slice(0, 1e3),
628081
+ status: "active",
628082
+ authority: "adopted",
628083
+ bindingIds: [],
628084
+ evidenceRefIds: [],
628085
+ attributes: projectedAttributes("adopted", {
628086
+ entityType: "workboard_actor"
628087
+ })
628088
+ });
628089
+ }
628090
+ addEdge(cardNodeId, assigneeNodeId, "assigned_to", 1);
628091
+ }
628092
+ const workboardEvidenceRefIds = [];
628093
+ for (const evidence of card.evidence) {
628094
+ const bindingId = compactId("workboard-binding", `${workboard.runId}:${card.id}:${evidence.id}`);
628095
+ const evidenceId = compactId("workboard-evidence", `${workboard.runId}:${card.id}:${evidence.id}`);
628096
+ const evidenceNodeId = compactId("workboard-evidence-node", `${workboard.runId}:${card.id}:${evidence.id}`);
628097
+ const sourceUri = evidence.path || evidence.url || evidence.ref || `omnius://workboard/${encodeURIComponent(workboard.runId)}/card/${encodeURIComponent(card.id)}/evidence/${encodeURIComponent(evidence.id)}`;
628098
+ const role = card.status === "blocked" ? "blocker" : card.status === "verified" && Boolean(evidence.verifies?.includes(card.id) || card.verifierCommand && evidence.command === card.verifierCommand) ? "verification" : "context";
628099
+ sourceBindings.set(bindingId, {
628100
+ schemaVersion: input.graph.schemaVersion,
628101
+ bindingId,
628102
+ sourceId: compactId("workboard-source", workboard.runId),
628103
+ sourceUri,
628104
+ sourceRevision: `event-count:${workboard.eventCount}`,
628105
+ locator: { type: "uri_fragment", fragment: evidence.id },
628106
+ authority: "primary",
628107
+ freshness: "fresh",
628108
+ readPolicy: "snapshot",
628109
+ observedAt: iso2(evidence.createdAt, now2),
628110
+ confidence: 1,
628111
+ notes: evidence.summary.slice(0, 2e3)
628112
+ });
628113
+ evidenceRefs.set(evidenceId, {
628114
+ schemaVersion: input.graph.schemaVersion,
628115
+ evidenceId,
628116
+ bindingId,
628117
+ role,
628118
+ excerpt: evidence.summary.slice(0, 4e3),
628119
+ observedAt: iso2(evidence.createdAt, now2),
628120
+ confidence: 1,
628121
+ tags: [WORK_GRAPH_PROJECTION_ID, "workboard", evidence.kind]
628122
+ });
628123
+ addNode({
628124
+ schemaVersion: input.graph.schemaVersion,
628125
+ nodeId: evidenceNodeId,
628126
+ kind: "evidence",
628127
+ label: evidence.summary.slice(0, 1e3) || evidence.id,
628128
+ status: "complete",
628129
+ authority: "adopted",
628130
+ bindingIds: [bindingId],
628131
+ evidenceRefIds: [evidenceId],
628132
+ reference: { type: "evidence", evidenceId },
628133
+ attributes: projectedAttributes("adopted", {
628134
+ workboardRunId: workboard.runId,
628135
+ workboardCardId: card.id,
628136
+ workboardEvidenceId: evidence.id
628137
+ })
628138
+ });
628139
+ addEdge(cardNodeId, evidenceNodeId, "evidenced_by", 1, [evidenceId]);
628140
+ workboardEvidenceRefIds.push(evidenceId);
628141
+ }
628142
+ if (card.status === "verified" && workboardEvidenceRefIds.length > 0) {
628143
+ const verificationNodeId = compactId("workboard-verification", `${workboard.runId}:${card.id}`);
628144
+ addNode({
628145
+ schemaVersion: input.graph.schemaVersion,
628146
+ nodeId: verificationNodeId,
628147
+ kind: "verification",
628148
+ label: `Workboard verification: ${card.title}`.slice(0, 1e3),
628149
+ description: card.verificationNotes?.slice(0, 4e3),
628150
+ status: "complete",
628151
+ authority: "adopted",
628152
+ parentNodeId: cardNodeId,
628153
+ bindingIds: [],
628154
+ evidenceRefIds: workboardEvidenceRefIds,
628155
+ attributes: projectedAttributes("adopted", {
628156
+ workboardRunId: workboard.runId,
628157
+ workboardCardId: card.id,
628158
+ verifierCommand: card.verifierCommand,
628159
+ advisory: true
628160
+ })
628161
+ });
628162
+ addEdge(cardNodeId, verificationNodeId, "verified_by", 0.8, workboardEvidenceRefIds);
628163
+ }
628164
+ for (const decision2 of card.decisions) {
628165
+ const decisionNodeId = compactId("workboard-decision", `${workboard.runId}:${decision2.id}`);
628166
+ addNode({
628167
+ schemaVersion: input.graph.schemaVersion,
628168
+ nodeId: decisionNodeId,
628169
+ kind: "decision",
628170
+ label: decision2.reason.slice(0, 1e3),
628171
+ status: decision2.kind === "blocked" || decision2.kind === "needs_changes" ? "blocked" : "complete",
628172
+ authority: "adopted",
628173
+ parentNodeId: cardNodeId,
628174
+ bindingIds: [],
628175
+ evidenceRefIds: [],
628176
+ attributes: projectedAttributes("adopted", {
628177
+ workboardRunId: workboard.runId,
628178
+ workboardCardId: card.id,
628179
+ workboardDecisionId: decision2.id,
628180
+ decisionKind: decision2.kind,
628181
+ actor: decision2.actor,
628182
+ createdAt: decision2.createdAt
628183
+ })
628184
+ });
628185
+ addEdge(cardNodeId, decisionNodeId, "contains", 1);
628186
+ }
628187
+ }
628188
+ for (const card of workboard.cards) {
628189
+ const cardNodeId = cardNodeIds.get(card.id);
628190
+ for (const dependencyId of card.dependencies) {
628191
+ const dependencyNodeId = cardNodeIds.get(dependencyId);
628192
+ if (dependencyNodeId)
628193
+ addEdge(cardNodeId, dependencyNodeId, "depends_on", 1);
628194
+ }
628195
+ if (card.blocker) {
628196
+ const blockerNodeId = compactId("workboard-blocker", `${workboard.runId}:${card.id}:${card.blocker}`);
628197
+ addNode({
628198
+ schemaVersion: input.graph.schemaVersion,
628199
+ nodeId: blockerNodeId,
628200
+ kind: "blocker",
628201
+ label: card.blocker.slice(0, 1e3),
628202
+ status: card.status === "blocked" ? "blocked" : "superseded",
628203
+ authority: "adopted",
628204
+ parentNodeId: cardNodeId,
628205
+ bindingIds: [],
628206
+ evidenceRefIds: [],
628207
+ attributes: projectedAttributes("adopted", {
628208
+ workboardRunId: workboard.runId,
628209
+ workboardCardId: card.id
628210
+ })
628211
+ });
628212
+ addEdge(cardNodeId, blockerNodeId, "blocked_by", 1);
628213
+ }
628214
+ }
628215
+ for (const diagnostic of workboard.diagnostics) {
628216
+ const diagnosticNodeId = compactId("workboard-diagnostic", `${workboard.runId}:${diagnostic.id}`);
628217
+ const cardNodeId = diagnostic.cardId ? cardNodeIds.get(diagnostic.cardId) : void 0;
628218
+ addNode({
628219
+ schemaVersion: input.graph.schemaVersion,
628220
+ nodeId: diagnosticNodeId,
628221
+ kind: diagnostic.severity === "error" ? "blocker" : "event",
628222
+ label: diagnostic.message.slice(0, 1e3),
628223
+ status: diagnostic.severity === "error" ? "blocked" : "active",
628224
+ authority: "adopted",
628225
+ parentNodeId: cardNodeId ?? objectiveId,
628226
+ bindingIds: [],
628227
+ evidenceRefIds: [],
628228
+ attributes: projectedAttributes("adopted", {
628229
+ workboardRunId: workboard.runId,
628230
+ diagnosticId: diagnostic.id,
628231
+ diagnosticCode: diagnostic.code,
628232
+ severity: diagnostic.severity,
628233
+ actor: diagnostic.actor,
628234
+ createdAt: diagnostic.createdAt
628235
+ })
628236
+ });
628237
+ addEdge(cardNodeId ?? objectiveId, diagnosticNodeId, diagnostic.severity === "error" ? "blocked_by" : "contains", 1);
628238
+ }
628239
+ }
627700
628240
  const ledger = input.completionLedger;
627701
628241
  if (ledger) {
627702
628242
  for (const evidence of ledger.evidence) {
@@ -627753,6 +628293,11 @@ function buildWorkGraphProjectionPatch(input) {
627753
628293
  for (const completionClaim of ledger.proposedClaims) {
627754
628294
  const claimId = compactId("claim", completionClaim.id);
627755
628295
  const evidenceIds = completionClaim.evidenceIds.map((id2) => compactId("evidence", id2)).filter((id2) => evidenceRefs.has(id2));
628296
+ const matchedTodo = input.todos.find((todo) => {
628297
+ const proofIds = new Set((todoProofEvidence.get(todo.id) ?? []).map((entry) => entry.id));
628298
+ return completionClaim.evidenceIds.some((id2) => proofIds.has(id2));
628299
+ });
628300
+ const subjectNodeId = matchedTodo ? todoNodeIds.get(matchedTodo.id) ?? objectiveId : objectiveId;
627756
628301
  const claim = {
627757
628302
  schemaVersion: input.graph.schemaVersion,
627758
628303
  claimId,
@@ -627764,7 +628309,7 @@ function buildWorkGraphProjectionPatch(input) {
627764
628309
  evidenceRefIds: evidenceIds,
627765
628310
  dependsOnClaimIds: [],
627766
628311
  tags: [WORK_GRAPH_PROJECTION_ID, completionClaim.riskCategory],
627767
- subjectNodeId: objectiveId,
628312
+ subjectNodeId,
627768
628313
  predicate: "completion_outcome",
627769
628314
  objectValue: completionClaim.attainedState,
627770
628315
  authority: claimAuthority(completionClaim)
@@ -627778,7 +628323,7 @@ function buildWorkGraphProjectionPatch(input) {
627778
628323
  label: completionClaim.text.slice(0, 1e3),
627779
628324
  status: completionClaim.status === "supported" ? "complete" : completionClaim.status === "contradicted" ? "invalid" : completionClaim.status === "blocked" ? "blocked" : "proposed",
627780
628325
  authority: claimAuthority(completionClaim),
627781
- parentNodeId: objectiveId,
628326
+ parentNodeId: subjectNodeId,
627782
628327
  bindingIds: [],
627783
628328
  evidenceRefIds: evidenceIds,
627784
628329
  reference: { type: "claim", claimId },
@@ -627788,7 +628333,22 @@ function buildWorkGraphProjectionPatch(input) {
627788
628333
  requiresIndependentEvidence: completionClaim.requiresIndependentEvidence
627789
628334
  })
627790
628335
  });
627791
- addEdge(objectiveId, claimNodeId, "contains");
628336
+ addEdge(subjectNodeId, claimNodeId, "contains");
628337
+ if (matchedTodo) {
628338
+ const proofNodes = [
628339
+ ...todoMutationNodeIds.get(matchedTodo.id) ?? [],
628340
+ ...todoArtifactNodeIds.get(matchedTodo.id) ?? []
628341
+ ];
628342
+ for (const proofNodeId of proofNodes) {
628343
+ if (evidenceIds.length > 0) {
628344
+ addEdge(proofNodeId, claimNodeId, "supports", completionClaim.status === "supported" ? 1 : 0.5, evidenceIds);
628345
+ }
628346
+ }
628347
+ const verificationNodeId = todoVerificationNodeIds.get(matchedTodo.id);
628348
+ if (verificationNodeId) {
628349
+ addEdge(claimNodeId, verificationNodeId, "verified_by", completionClaim.status === "supported" ? 1 : 0.5, evidenceIds);
628350
+ }
628351
+ }
627792
628352
  for (const evidenceId of evidenceIds) {
627793
628353
  addEdge(claimNodeId, compactId("evidence-node", ledger.evidence.find((entry) => compactId("evidence", entry.id) === evidenceId)?.id ?? evidenceId), "evidenced_by", 1, [evidenceId]);
627794
628354
  }
@@ -627892,6 +628452,7 @@ var OperationalWorkGraphController;
627892
628452
  var init_controller = __esm({
627893
628453
  "packages/orchestrator/dist/operational-world/controller.js"() {
627894
628454
  "use strict";
628455
+ init_dist7();
627895
628456
  init_reducer();
627896
628457
  init_persistence();
627897
628458
  init_reasoningSlice();
@@ -627923,6 +628484,24 @@ var init_controller = __esm({
627923
628484
  graph() {
627924
628485
  return structuredClone(this.state.graph);
627925
628486
  }
628487
+ /**
628488
+ * Validate and commit a model-proposed patch without rewriting its revision.
628489
+ * Model-created nodes and claims remain inferred until code or a user adopts them.
628490
+ */
628491
+ applyModelPatch(input, producer = "unknown") {
628492
+ const parsed = WorldPatchSchema.safeParse(input);
628493
+ const patch = parsed.success ? {
628494
+ ...parsed.data,
628495
+ createdBy: `model:${producer.trim() || "unknown"}`
628496
+ } : input;
628497
+ const result = applyWorldPatch(this.state, patch);
628498
+ if (result.applied) {
628499
+ this.state = result.world;
628500
+ if (this.persist)
628501
+ this.save();
628502
+ }
628503
+ return result;
628504
+ }
627926
628505
  refresh(input) {
627927
628506
  if (input.objective?.trim())
627928
628507
  this.objective = input.objective.trim();
@@ -627931,6 +628510,7 @@ var init_controller = __esm({
627931
628510
  objective: this.objective,
627932
628511
  objectiveAuthority: "explicit",
627933
628512
  todos: input.todos,
628513
+ workboard: input.workboard,
627934
628514
  completionLedger: input.completionLedger,
627935
628515
  now: input.now
627936
628516
  });
@@ -653779,7 +654359,16 @@ ${parts.join("\n")}
653779
654359
  if (targetCard.assignee && targetCard.assignee !== actor && targetCard.assignee !== "any") {
653780
654360
  return;
653781
654361
  }
653782
- const evidenced = attachWorkboardEvidence(dir, {
654362
+ const evidenced = result?.executionReceipt ? attachTrustedWorkboardCommandEvidence(dir, {
654363
+ runId: this.currentArtifactRunId(),
654364
+ cardId: targetCard.id,
654365
+ actor,
654366
+ evidence: {
654367
+ ...evidence,
654368
+ command: result.executionReceipt.command
654369
+ },
654370
+ receipt: result.executionReceipt
654371
+ }) : attachWorkboardEvidence(dir, {
653783
654372
  runId: this.currentArtifactRunId(),
653784
654373
  cardId: targetCard.id,
653785
654374
  actor,
@@ -656524,6 +657113,7 @@ Pick the SMALLEST concrete deliverable from the spec — typically the project e
656524
657113
  const result = this._operationalWorkGraph.refresh({
656525
657114
  objective,
656526
657115
  todos: this.readSessionTodos() ?? [],
657116
+ workboard: this._currentWorkboardSnapshotForFrame(),
656527
657117
  completionLedger: this._completionLedger
656528
657118
  });
656529
657119
  if (result.issues.length > 0) {
@@ -678018,7 +678608,15 @@ Example: ${tool.name}(${JSON.stringify(meta.examples[0].args ?? {})})` : "";
678018
678608
  "memory_read",
678019
678609
  "memory_write",
678020
678610
  "working_notes",
678021
- "batch_edit"
678611
+ "batch_edit",
678612
+ // Public media acquisition has a deterministic follow-on contract.
678613
+ // Keep its semantic validators and delivery boundary callable without a
678614
+ // tool_search round trip, even when the original request is as vague as
678615
+ // "get one from NASA" and therefore cannot relevance-score them.
678616
+ "telegram_image_analyze",
678617
+ "audio_analyze",
678618
+ "transcribe_file",
678619
+ "telegram_send_file"
678022
678620
  ]);
678023
678621
  const taskText = (this._taskState.originalGoal || this._taskState.goal || "").toLowerCase();
678024
678622
  const wants3dModelGeneration = /\b(?:make|create|generate|build|produce|render|give)\b/.test(taskText) && /\b(?:3d|three[-\s]?d|mesh|glb|obj|stl|ply|cad|scad|step|printable|model)\b/.test(taskText) && !/\b(?:image|picture|photo|rendering|screenshot)\b/.test(taskText);
@@ -686959,11 +687557,22 @@ function treePath(stateDir, rootId) {
686959
687557
  return path13.join(stateDir, "feature-tree", `${rootId}.json`);
686960
687558
  }
686961
687559
  function saveFeatureTree(stateDir, rootId, nodes) {
687560
+ let temporaryFile;
686962
687561
  try {
686963
687562
  const file = treePath(stateDir, rootId);
686964
687563
  fs11.mkdirSync(path13.dirname(file), { recursive: true });
686965
- fs11.writeFileSync(file, JSON.stringify(nodes, null, 2), "utf8");
687564
+ temporaryFile = `${file}.${process.pid}.${Date.now()}.tmp`;
687565
+ fs11.writeFileSync(temporaryFile, JSON.stringify(nodes, null, 2), "utf8");
687566
+ fs11.renameSync(temporaryFile, file);
687567
+ return true;
686966
687568
  } catch {
687569
+ if (temporaryFile) {
687570
+ try {
687571
+ fs11.unlinkSync(temporaryFile);
687572
+ } catch {
687573
+ }
687574
+ }
687575
+ return false;
686967
687576
  }
686968
687577
  }
686969
687578
  function loadFeatureTree(stateDir, rootId) {
@@ -687008,7 +687617,7 @@ async function execUnit(unit, node, ctx3, contract, maxRegenerate) {
687008
687617
  let result = await ctx3.executeUnit(unit, node);
687009
687618
  if (!result.ok)
687010
687619
  return false;
687011
- const verifyResult = () => ctx3.verifyUnit ? ctx3.verifyUnit(unit, result) : Promise.resolve(true);
687620
+ const verifyResult = () => ctx3.verifyUnit ? ctx3.verifyUnit(unit, result) : Promise.resolve(false);
687012
687621
  const expected = unit.expectedSymbols ?? [];
687013
687622
  if (expected.length === 0) {
687014
687623
  return verifyResult();
@@ -687033,6 +687642,7 @@ async function execUnit(unit, node, ctx3, contract, maxRegenerate) {
687033
687642
  }
687034
687643
  async function runFeatureNode(node, ctx3, options2, tree2 = {}) {
687035
687644
  const maxRegenerate = options2.maxRegenerate ?? 2;
687645
+ node.attempt = Math.max(0, Number(node.attempt) || 0) + 1;
687036
687646
  tree2[node.id] = node;
687037
687647
  try {
687038
687648
  const artefact = await planFeature(options2.survey, {
@@ -687083,7 +687693,11 @@ async function runFeatureNode(node, ctx3, options2, tree2 = {}) {
687083
687693
  kind: artefact.kind,
687084
687694
  status: "planned",
687085
687695
  scopeToken: node.scopeToken,
687086
- children: []
687696
+ children: [],
687697
+ graphId: node.graphId,
687698
+ requirementIds: node.requirementIds,
687699
+ acceptanceIds: node.acceptanceIds,
687700
+ attempt: 0
687087
687701
  };
687088
687702
  node.children.push(childId);
687089
687703
  const childSurvey = {
@@ -687136,7 +687750,8 @@ function createRootFeatureNode(request) {
687136
687750
  request,
687137
687751
  kind: "new-module",
687138
687752
  status: "planned",
687139
- children: []
687753
+ children: [],
687754
+ attempt: 0
687140
687755
  };
687141
687756
  }
687142
687757
  var runClaimNode, createClaimRootNode;
@@ -692994,7 +693609,12 @@ function startManagedAsrDownload(input) {
692994
693609
  else finish(current, "error", void 0, message2.error || "Managed ASR download failed");
692995
693610
  });
692996
693611
  worker2.on("error", (cause) => {
692997
- finish(readStatus(engineId, modelId) ?? status, "error", void 0, cause.message);
693612
+ finish(
693613
+ readStatus(engineId, modelId) ?? status,
693614
+ "error",
693615
+ void 0,
693616
+ cause instanceof Error ? cause.message : String(cause)
693617
+ );
692998
693618
  });
692999
693619
  worker2.on("exit", (code8) => {
693000
693620
  const active = activeDownloads.get(downloadKey);
@@ -774335,7 +774955,7 @@ var init_telegram_public_media = __esm({
774335
774955
  registerArtifact;
774336
774956
  deps;
774337
774957
  name = "telegram_media_acquire";
774338
- description = "Safely discover or acquire public HTTPS image/audio media for this Telegram chat. Use action='discover' on a static public page to extract image/audio candidate URLs, then action='acquire' on an exact candidate URL. Only HTTPS port 443 is allowed. Downloads are DNS-pinned, size-limited, type-checked by MIME and magic bytes, and saved under a server-generated name in this chat's scoped creative workspace. After acquisition, inspect an image with telegram_image_analyze detail='full', or inspect/transcribe audio with audio_analyze or transcribe_file, before claiming it matches the request or sending it.";
774958
+ description = "Safely discover or acquire public HTTPS image/audio media for this Telegram chat. Use action='discover' on a static public page to extract image/audio candidate URLs, then action='acquire' on an exact candidate URL. Only HTTPS port 443 is allowed. Downloads are DNS-pinned, size-limited, type-checked by MIME and magic bytes, and saved under a server-generated name in this chat's scoped creative workspace. After acquisition, inspect an image with telegram_image_analyze detail='full', or inspect/transcribe audio with audio_analyze or transcribe_file. Successful semantic validation queues the media for verified delivery before task_complete can succeed.";
774339
774959
  parameters = {
774340
774960
  type: "object",
774341
774961
  properties: {
@@ -774488,7 +775108,7 @@ ${output2}`,
774488
775108
  source_url: redactDisplayedUrl(new URL(url)),
774489
775109
  final_url: redactDisplayedUrl(response.finalUrl),
774490
775110
  validation: kind === "image" ? "MIME and raster magic agree; dimensions and single-frame policy passed. Semantic image validation is still required." : "MIME and container magic agree; ffprobe confirmed bounded audio-only media. Semantic/transcript validation is still required.",
774491
- next: kind === "image" ? `Call telegram_image_analyze with image=${JSON.stringify(logicalName)} and detail='full' before sending or describing it.` : `Call audio_analyze or transcribe_file with path=${JSON.stringify(logicalName)} before sending or describing it.`
775111
+ next: kind === "image" ? `Call telegram_image_analyze with image=${JSON.stringify(logicalName)} and detail='full'. A successful check queues this image for verified Telegram delivery; task_complete is blocked until then.` : `Call audio_analyze or transcribe_file with path=${JSON.stringify(logicalName)}. A successful check queues this audio for verified Telegram delivery; task_complete is blocked until then.`
774492
775112
  };
774493
775113
  const output2 = JSON.stringify(receipt2, null, 2);
774494
775114
  return {
@@ -774614,14 +775234,14 @@ function collectGeneratedArtifactPathsFromText(text3, root) {
774614
775234
  }
774615
775235
  return [...paths];
774616
775236
  }
774617
- function buildTelegramCreativeTools(repoRoot, chatId, backendUrl2, imageDefaults = {}, audioDefaults = {}, videoDefaults = {}, modelDefaults = {}) {
775237
+ function buildTelegramCreativeTools(repoRoot, chatId, backendUrl2, imageDefaults = {}, audioDefaults = {}, videoDefaults = {}, modelDefaults = {}, onAcquiredArtifact) {
774618
775238
  const root = telegramCreativeWorkspaceRoot(repoRoot, chatId);
774619
775239
  ensureManifest(root);
774620
775240
  return [
774621
- new TelegramPublicMediaAcquireTool(
774622
- root,
774623
- (path16) => rememberCreated(root, path16)
774624
- ),
775241
+ new TelegramPublicMediaAcquireTool(root, (path16) => {
775242
+ rememberCreated(root, path16);
775243
+ onAcquiredArtifact?.(path16);
775244
+ }),
774625
775245
  scopedTool(new FileWriteTool(root), root, "create"),
774626
775246
  scopedTool(new FileEditTool(root), root, "edit"),
774627
775247
  scopedTool(new StructuredFileTool(root), root, "create"),
@@ -777712,6 +778332,9 @@ var init_telegram_social_state_types = __esm({
777712
778332
  DEFAULT_TELEGRAM_REPLY_MODE = "reply_then_notes";
777713
778333
  TELEGRAM_SOCIAL_LIMITS = {
777714
778334
  relationships: 240,
778335
+ relationshipObservations: 80,
778336
+ turns: 600,
778337
+ claims: 240,
777715
778338
  salience: 180,
777716
778339
  outcomes: 160,
777717
778340
  daydreamOpportunities: 120,
@@ -777726,12 +778349,20 @@ function formatTelegramSocialStateContext(state5, input) {
777726
778349
  const senderKey3 = telegramSocialActorKey(input);
777727
778350
  const selfKey = input.self ? telegramSocialActorKey(input.self) : void 0;
777728
778351
  const replyKey = input.replySender ? telegramSocialActorKey(input.replySender) : void 0;
778352
+ const mentionedKeys = (input.mentionedActors ?? []).map(telegramSocialActorKey);
777729
778353
  const thread = state5.threads[telegramSocialThreadKey(input)];
777730
778354
  const participant = state5.participants[senderKey3];
777731
778355
  const senderIdentity = selfKey && senderKey3 === selfKey ? "agent_self" : "participant";
777732
778356
  const replyIdentity = replyKey ? selfKey && replyKey === selfKey ? "agent_self" : "participant" : "none";
777733
- const relevantKeys = new Set([senderKey3, selfKey, replyKey].filter(Boolean));
778357
+ const relevantKeys = new Set([senderKey3, selfKey, replyKey, ...mentionedKeys].filter(Boolean));
778358
+ const firstHop = state5.relationships.filter((edge) => relevantKeys.has(edge.fromKey) || relevantKeys.has(edge.toKey));
778359
+ for (const edge of firstHop) {
778360
+ relevantKeys.add(edge.fromKey);
778361
+ relevantKeys.add(edge.toKey);
778362
+ }
777734
778363
  const edges = state5.relationships.filter((edge) => relevantKeys.has(edge.fromKey) || relevantKeys.has(edge.toKey)).sort((a2, b) => b.lastSeenAt - a2.lastSeenAt).slice(0, limit2);
778364
+ const turns = state5.turns.filter((turn) => turn.threadKey === telegramSocialThreadKey(input)).filter((turn) => relevantKeys.has(turn.senderKey) || turn.mentionedActorKeys.some((key) => relevantKeys.has(key))).sort((a2, b) => b.ts - a2.ts).slice(0, limit2);
778365
+ const claims = state5.claims.filter((claim) => claim.validUntil === void 0 && relevantKeys.has(claim.subjectKey)).sort((a2, b) => b.ts - a2.ts).slice(0, Math.min(4, limit2));
777735
778366
  const outcomes = state5.outcomes.filter((outcome) => outcome.senderKey === senderKey3 || outcome.chatId === String(input.chatId)).sort((a2, b) => b.ts - a2.ts).slice(0, limit2);
777736
778367
  const salience = state5.salience.filter((item) => item.senderKey === senderKey3 || item.chatId === String(input.chatId)).sort((a2, b) => b.ts - a2.ts).slice(0, limit2);
777737
778368
  const daydreams = Object.values(state5.daydreamOpportunities).filter((item) => item.lifecycle !== "retired").sort((a2, b) => b.updatedAt - a2.updatedAt).slice(0, Math.min(5, limit2));
@@ -777761,8 +778392,12 @@ ${preferences.join("\n")}` : `Relevant preference vector for ${senderKey3}: no d
777761
778392
  ${scopedPreferences.join("\n")}` : "",
777762
778393
  salience.length ? `Recent salience observations:
777763
778394
  ${salience.map((item) => `- ${iso3(item.ts)} ${item.senderKey}: ${item.signals.join(", ") || "none"}; text=${jsonLine(item.textPreview, 120)}`).join("\n")}` : "",
777764
- edges.length ? `Relevant relationship edges:
777765
- ${edges.map(formatEdge).join("\n")}` : "Relevant relationship edges: none yet",
778395
+ turns.length ? `Scoped conversation-flow turns:
778396
+ ${turns.map((turn) => `- ${iso3(turn.ts)} ${turn.senderKey} msg=${turn.messageId ?? "?"}${turn.replyToMessageId !== void 0 ? ` reply_to=${turn.replyToMessageId}` : ""}${turn.mentionedActorKeys.length ? ` mentions=${turn.mentionedActorKeys.join(",")}` : ""}${turn.previousSenderKey ? ` follows=${turn.previousSenderKey}` : ""}: ${jsonLine(turn.textPreview, 140)}`).join("\n")}` : "",
778397
+ edges.length ? `Relevant relationship edges (observations are factual; activation decays, evidence does not):
778398
+ ${edges.map((edge) => formatEdge(edge, input.ts ?? Date.now())).join("\n")}` : "Relevant relationship edges: none yet",
778399
+ claims.length ? `Advisory social claims (model-derived; never facts):
778400
+ ${claims.map((claim) => `- ${claim.subjectKey} --${claim.predicate}--> ${jsonLine(claim.objectText, 180)} confidence=${claim.confidence.toFixed(2)} evidence=${claim.evidenceMessageIds.join(",") || "none"}`).join("\n")}` : "",
777766
778401
  outcomes.length ? `Prior response outcomes:
777767
778402
  ${outcomes.map(formatOutcome).join("\n")}` : "Prior response outcomes: none yet",
777768
778403
  daydreams.length ? `Active daydream opportunity lifecycle:
@@ -777791,10 +778426,12 @@ function preferenceLines(vector, prefix) {
777791
778426
  return `- ${prefix ? `${prefix}.` : ""}${key}: value=${item.value.toFixed(2)} confidence=${item.confidence.toFixed(2)} evidence=${item.evidenceMessageIds.join(",") || "none"}${item.note ? ` note=${jsonLine(item.note, 120)}` : ""}`;
777792
778427
  });
777793
778428
  }
777794
- function formatEdge(edge) {
778429
+ function formatEdge(edge, now2) {
777795
778430
  const evidence = edge.evidenceMessageIds.length ? ` evidence=${edge.evidenceMessageIds.join(",")}` : "";
777796
778431
  const note = edge.note ? ` note=${jsonLine(edge.note, 140)}` : "";
777797
- return `- ${edge.fromKey} --${edge.kind}--> ${edge.toKey} confidence=${edge.confidence.toFixed(2)} weight=${edge.weight.toFixed(2)}${evidence}${note}`;
778432
+ const ageDays = Math.max(0, now2 - edge.lastSeenAt) / 864e5;
778433
+ const activation = edge.evidenceClass === "observed" ? edge.weight : edge.weight * Math.exp(-ageDays / 30);
778434
+ return `- ${edge.fromKey} --${edge.kind}--> ${edge.toKey} class=${edge.evidenceClass} confidence=${edge.confidence.toFixed(2)} activation=${activation.toFixed(2)} observations=${edge.observations.length}${evidence}${note}`;
777798
778435
  }
777799
778436
  function formatOutcome(outcome) {
777800
778437
  const action = outcome.shouldReply ? `replied via ${outcome.route}` : "silent";
@@ -777811,11 +778448,13 @@ var init_telegram_social_state_context = __esm({
777811
778448
  // packages/cli/src/tui/telegram-social-state-normalize.ts
777812
778449
  function createTelegramSocialState(now2 = Date.now()) {
777813
778450
  return {
777814
- version: 1,
778451
+ version: 2,
777815
778452
  createdAt: now2,
777816
778453
  updatedAt: now2,
777817
778454
  participants: {},
777818
778455
  relationships: [],
778456
+ turns: [],
778457
+ claims: [],
777819
778458
  preferences: {},
777820
778459
  deliveryCapabilities: {},
777821
778460
  threads: {},
@@ -777833,6 +778472,8 @@ function normalizeTelegramSocialState(raw) {
777833
778472
  state5.updatedAt = numberOr(value2.updatedAt, state5.updatedAt);
777834
778473
  state5.participants = normalizeRecord(value2.participants, normalizeParticipant);
777835
778474
  state5.relationships = Array.isArray(value2.relationships) ? value2.relationships.map(normalizeRelationship).filter(Boolean).slice(-TELEGRAM_SOCIAL_LIMITS.relationships) : [];
778475
+ state5.turns = Array.isArray(value2.turns) ? value2.turns.map(normalizeTurn).filter(Boolean).slice(-TELEGRAM_SOCIAL_LIMITS.turns) : [];
778476
+ state5.claims = Array.isArray(value2.claims) ? value2.claims.map(normalizeClaim).filter(Boolean).slice(-TELEGRAM_SOCIAL_LIMITS.claims) : [];
777836
778477
  state5.preferences = normalizePreferences(value2.preferences);
777837
778478
  state5.deliveryCapabilities = normalizeRecord(value2.deliveryCapabilities, normalizeDeliveryCapability);
777838
778479
  state5.threads = normalizeRecord(value2.threads, normalizeThread);
@@ -777883,10 +778524,69 @@ function normalizeRelationship(raw) {
777883
778524
  firstSeenAt: numberOr(value2.firstSeenAt, Date.now()),
777884
778525
  lastSeenAt: numberOr(value2.lastSeenAt, Date.now()),
777885
778526
  evidenceMessageIds: Array.isArray(value2.evidenceMessageIds) ? value2.evidenceMessageIds.filter(isNumber).slice(-40) : [],
778527
+ observations: Array.isArray(value2.observations) ? value2.observations.map(normalizeRelationshipObservation).filter(Boolean).slice(-TELEGRAM_SOCIAL_LIMITS.relationshipObservations) : [],
778528
+ evidenceClass: value2.evidenceClass === "reported" || value2.evidenceClass === "inferred" || value2.evidenceClass === "operational" ? value2.evidenceClass : value2.source === "router" ? "inferred" : "observed",
778529
+ validFrom: numberOr(value2.validFrom, numberOr(value2.firstSeenAt, Date.now())),
778530
+ validUntil: typeof value2.validUntil === "number" ? value2.validUntil : void 0,
777886
778531
  note: compactOptional(value2.note, 260),
777887
778532
  source: value2.source === "router" || value2.source === "daydream" ? value2.source : "message"
777888
778533
  };
777889
778534
  }
778535
+ function normalizeRelationshipObservation(raw) {
778536
+ if (!raw || typeof raw !== "object") return null;
778537
+ const value2 = raw;
778538
+ const source = value2.source === "router" || value2.source === "daydream" ? value2.source : "message";
778539
+ const ts = numberOr(value2.ts, Date.now());
778540
+ return {
778541
+ id: String(value2.id || hashTelegramSocialId(["relationship-observation", source, value2.messageId ?? ts, value2.note ?? ""])),
778542
+ ts,
778543
+ confidence: clamp0117(numberOr(value2.confidence, 0)),
778544
+ source,
778545
+ evidenceClass: value2.evidenceClass === "reported" || value2.evidenceClass === "inferred" || value2.evidenceClass === "operational" ? value2.evidenceClass : source === "router" ? "inferred" : "observed",
778546
+ messageId: typeof value2.messageId === "number" ? value2.messageId : void 0,
778547
+ note: compactOptional(value2.note, 260)
778548
+ };
778549
+ }
778550
+ function normalizeTurn(raw) {
778551
+ if (!raw || typeof raw !== "object") return null;
778552
+ const value2 = raw;
778553
+ if (!value2.id || !value2.senderKey || !value2.threadKey) return null;
778554
+ return {
778555
+ id: String(value2.id),
778556
+ ts: numberOr(value2.ts, Date.now()),
778557
+ chatId: String(value2.chatId || ""),
778558
+ threadKey: String(value2.threadKey),
778559
+ messageId: typeof value2.messageId === "number" ? value2.messageId : void 0,
778560
+ senderKey: String(value2.senderKey),
778561
+ replyToMessageId: typeof value2.replyToMessageId === "number" ? value2.replyToMessageId : void 0,
778562
+ replyTargetKey: compactOptional(value2.replyTargetKey, 180),
778563
+ mentionedActorKeys: Array.isArray(value2.mentionedActorKeys) ? value2.mentionedActorKeys.map(String).slice(0, 24) : [],
778564
+ previousMessageId: typeof value2.previousMessageId === "number" ? value2.previousMessageId : void 0,
778565
+ previousSenderKey: compactOptional(value2.previousSenderKey, 180),
778566
+ textPreview: compact5(value2.textPreview || "", 500)
778567
+ };
778568
+ }
778569
+ function normalizeClaim(raw) {
778570
+ if (!raw || typeof raw !== "object") return null;
778571
+ const value2 = raw;
778572
+ if (!value2.id || !value2.subjectKey || !value2.objectText) return null;
778573
+ return {
778574
+ id: String(value2.id),
778575
+ ts: numberOr(value2.ts, Date.now()),
778576
+ subjectKey: String(value2.subjectKey),
778577
+ predicate: value2.predicate === "preference_note" ? "preference_note" : "relationship_note",
778578
+ objectText: compact5(value2.objectText, 500),
778579
+ epistemicStatus: value2.epistemicStatus === "reported" ? "reported" : "inferred",
778580
+ authority: value2.authority === "participant" ? "participant" : "router",
778581
+ confidence: clamp0117(numberOr(value2.confidence, 0)),
778582
+ evidenceMessageIds: Array.isArray(value2.evidenceMessageIds) ? value2.evidenceMessageIds.filter(isNumber).slice(-40) : [],
778583
+ sourceOutcomeId: compactOptional(value2.sourceOutcomeId, 180),
778584
+ supersedesClaimIds: Array.isArray(value2.supersedesClaimIds) ? value2.supersedesClaimIds.map(String).slice(-40) : [],
778585
+ contradictsClaimIds: Array.isArray(value2.contradictsClaimIds) ? value2.contradictsClaimIds.map(String).slice(-40) : [],
778586
+ validFrom: numberOr(value2.validFrom, numberOr(value2.ts, Date.now())),
778587
+ validUntil: typeof value2.validUntil === "number" ? value2.validUntil : void 0
778588
+ };
778589
+ }
777890
778590
  function normalizePreferences(raw) {
777891
778591
  const out = {};
777892
778592
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) return out;
@@ -777969,7 +778669,8 @@ function normalizeThread(raw) {
777969
778669
  messageCount: numberOr(value2.messageCount, 0),
777970
778670
  participantKeys: Array.isArray(value2.participantKeys) ? value2.participantKeys.map(String).slice(-40) : [],
777971
778671
  lastMessageIds: Array.isArray(value2.lastMessageIds) ? value2.lastMessageIds.filter(isNumber).slice(-40) : [],
777972
- lastOutcomeIds: Array.isArray(value2.lastOutcomeIds) ? value2.lastOutcomeIds.map(String).slice(-30) : []
778672
+ lastOutcomeIds: Array.isArray(value2.lastOutcomeIds) ? value2.lastOutcomeIds.map(String).slice(-30) : [],
778673
+ lastSenderKey: compactOptional(value2.lastSenderKey, 180)
777973
778674
  };
777974
778675
  }
777975
778676
  function normalizeSalience(raw) {
@@ -778052,6 +778753,10 @@ var init_telegram_social_state_normalize = __esm({
778052
778753
  function observeTelegramSocialMessage(state5, input) {
778053
778754
  const now2 = input.ts ?? Date.now();
778054
778755
  state5.updatedAt = now2;
778756
+ const threadKey = telegramSocialThreadKey(input);
778757
+ const previousThread = state5.threads[threadKey];
778758
+ const previousSenderKey = previousThread?.lastSenderKey;
778759
+ const previousMessageId = previousThread?.lastMessageIds.at(-1);
778055
778760
  const senderKey3 = upsertParticipant(state5, input, now2, true);
778056
778761
  const thread = upsertThread(state5, input, senderKey3, now2, true);
778057
778762
  const text3 = compact5(input.text || "", 500);
@@ -778065,6 +778770,33 @@ function observeTelegramSocialMessage(state5, input) {
778065
778770
  const targetKey = input.replySender ? upsertParticipant(state5, input.replySender, now2, false) : `message:${input.replyToMessageId}`;
778066
778771
  upsertRelationship(state5, "replied_to", senderKey3, targetKey, input.messageId, 0.86, "message", replyNote(input), now2);
778067
778772
  }
778773
+ const mentionedActorKeys = [];
778774
+ for (const actor of input.mentionedActors ?? []) {
778775
+ const mentionedKey = upsertParticipant(state5, actor, now2, false);
778776
+ if (mentionedKey === "unknown" || mentionedKey === senderKey3) continue;
778777
+ mentionedActorKeys.push(mentionedKey);
778778
+ upsertRelationship(state5, "mentioned", senderKey3, mentionedKey, input.messageId, 0.98, "message", "Telegram mention entity", now2);
778779
+ }
778780
+ upsertRelationship(state5, "participated_in", senderKey3, thread.key, input.messageId, 1, "message", "Telegram message observed in this chat/thread", now2);
778781
+ if (previousSenderKey && previousSenderKey !== senderKey3) {
778782
+ upsertRelationship(state5, "turn_followed", senderKey3, previousSenderKey, input.messageId, 0.62, "message", `message ${input.messageId ?? "?"} followed ${previousMessageId ?? "?"}`, now2);
778783
+ }
778784
+ state5.turns.push({
778785
+ id: hashTelegramSocialId(["turn", input.sessionKey, input.chatId, input.messageId ?? now2]),
778786
+ ts: now2,
778787
+ chatId: String(input.chatId),
778788
+ threadKey: thread.key,
778789
+ messageId: input.messageId,
778790
+ senderKey: senderKey3,
778791
+ replyToMessageId: input.replyToMessageId,
778792
+ replyTargetKey: input.replySender ? telegramSocialActorKey(input.replySender) : void 0,
778793
+ mentionedActorKeys: [...new Set(mentionedActorKeys)],
778794
+ previousMessageId,
778795
+ previousSenderKey,
778796
+ textPreview: text3
778797
+ });
778798
+ state5.turns = state5.turns.slice(-TELEGRAM_SOCIAL_LIMITS.turns);
778799
+ thread.lastSenderKey = senderKey3;
778068
778800
  const selfKey = input.self ? upsertParticipant(state5, { ...input.self, isBot: true }, now2, false) : void 0;
778069
778801
  addMessageDerivedEdges(state5, input, senderKey3, selfKey, text3, now2);
778070
778802
  updatePreferencesFromText(state5, senderKey3, text3, input.messageId, now2, telegramSocialActorKind(input));
@@ -778222,12 +778954,27 @@ function telegramTextDeliveryCapabilityFor(state5, input) {
778222
778954
  function commitDecisionEdges(state5, input, senderKey3, selfKey, now2) {
778223
778955
  if (!selfKey) return;
778224
778956
  if (input.shouldReply) {
778225
- upsertRelationship(state5, "addressed", selfKey, senderKey3, input.messageId, input.confidence, "router", input.reason, now2);
778957
+ upsertRelationship(state5, "addressed", selfKey, senderKey3, input.messageId, input.confidence, "router", input.reason, now2, "operational");
778226
778958
  } else {
778227
- upsertRelationship(state5, "ignored", selfKey, senderKey3, input.messageId, input.confidence, "router", input.silentDisposition || input.reason, now2);
778959
+ upsertRelationship(state5, "ignored", selfKey, senderKey3, input.messageId, input.confidence, "router", input.silentDisposition || input.reason, now2, "operational");
778228
778960
  }
778229
778961
  if (input.relationshipNote) {
778230
- upsertRelationship(state5, "addressed", senderKey3, selfKey, input.messageId, Math.max(0.35, input.confidence), "router", input.relationshipNote, now2);
778962
+ state5.claims.push({
778963
+ id: hashTelegramSocialId(["social-claim", input.sessionKey, input.messageId ?? now2, input.relationshipNote]),
778964
+ ts: now2,
778965
+ subjectKey: senderKey3,
778966
+ predicate: "relationship_note",
778967
+ objectText: compact5(input.relationshipNote, 500),
778968
+ epistemicStatus: "inferred",
778969
+ authority: "router",
778970
+ confidence: clamp0117(input.confidence),
778971
+ evidenceMessageIds: input.messageId === void 0 ? [] : [input.messageId],
778972
+ sourceOutcomeId: state5.outcomes.at(-1)?.id,
778973
+ supersedesClaimIds: [],
778974
+ contradictsClaimIds: [],
778975
+ validFrom: now2
778976
+ });
778977
+ state5.claims = state5.claims.slice(-TELEGRAM_SOCIAL_LIMITS.claims);
778231
778978
  }
778232
778979
  }
778233
778980
  function markDecisionOpportunities(opportunities, shouldReply, messageId, now2) {
@@ -778316,7 +779063,8 @@ function upsertThread(state5, input, senderKey3, now2, countMessage) {
778316
779063
  messageCount: 0,
778317
779064
  participantKeys: [],
778318
779065
  lastMessageIds: [],
778319
- lastOutcomeIds: []
779066
+ lastOutcomeIds: [],
779067
+ lastSenderKey: void 0
778320
779068
  };
778321
779069
  thread.chatType = input.chatType || thread.chatType;
778322
779070
  thread.chatTitle = input.chatTitle || thread.chatTitle;
@@ -778327,7 +779075,7 @@ function upsertThread(state5, input, senderKey3, now2, countMessage) {
778327
779075
  state5.threads[key] = thread;
778328
779076
  return thread;
778329
779077
  }
778330
- function upsertRelationship(state5, kind, fromKey, toKey, messageId, confidence2, source, note, now2) {
779078
+ function upsertRelationship(state5, kind, fromKey, toKey, messageId, confidence2, source, note, now2, evidenceClass = source === "message" ? "observed" : "inferred") {
778331
779079
  const id2 = hashTelegramSocialId(["edge", kind, fromKey, toKey]);
778332
779080
  const edge = state5.relationships.find((item) => item.id === id2) ?? {
778333
779081
  id: id2,
@@ -778339,14 +779087,34 @@ function upsertRelationship(state5, kind, fromKey, toKey, messageId, confidence2
778339
779087
  firstSeenAt: now2,
778340
779088
  lastSeenAt: now2,
778341
779089
  evidenceMessageIds: [],
779090
+ observations: [],
779091
+ evidenceClass,
779092
+ validFrom: now2,
778342
779093
  source
778343
779094
  };
778344
779095
  edge.confidence = Math.max(edge.confidence, clamp0117(confidence2));
778345
779096
  edge.weight = Math.min(1, edge.weight + 0.12 + clamp0117(confidence2) * 0.2);
778346
779097
  edge.lastSeenAt = now2;
778347
779098
  edge.evidenceMessageIds = appendUnique(edge.evidenceMessageIds, messageId, 40);
778348
- edge.note = compactOptional(note, 260) || edge.note;
778349
- edge.source = source;
779099
+ const observation = {
779100
+ id: hashTelegramSocialId(["relationship-observation", id2, messageId ?? now2, source, note ?? ""]),
779101
+ ts: now2,
779102
+ confidence: clamp0117(confidence2),
779103
+ source,
779104
+ evidenceClass,
779105
+ messageId,
779106
+ note: compactOptional(note, 260)
779107
+ };
779108
+ if (!edge.observations.some((item) => item.id === observation.id)) {
779109
+ edge.observations.push(observation);
779110
+ edge.observations = edge.observations.slice(-TELEGRAM_SOCIAL_LIMITS.relationshipObservations);
779111
+ }
779112
+ const authorityRank = (value2) => value2 === "observed" ? 4 : value2 === "reported" ? 3 : value2 === "operational" ? 2 : 1;
779113
+ if (authorityRank(evidenceClass) >= authorityRank(edge.evidenceClass)) {
779114
+ edge.note = observation.note || edge.note;
779115
+ edge.source = source;
779116
+ edge.evidenceClass = evidenceClass;
779117
+ }
778350
779118
  if (!state5.relationships.some((item) => item.id === id2)) state5.relationships.push(edge);
778351
779119
  }
778352
779120
  function addSalience(state5, input, senderKey3, now2, text3) {
@@ -778376,6 +779144,8 @@ function retireStaleDaydreamOpportunities(state5, now2) {
778376
779144
  }
778377
779145
  function trimSocialState(state5, now2) {
778378
779146
  state5.relationships = state5.relationships.sort((a2, b) => b.lastSeenAt - a2.lastSeenAt).slice(0, TELEGRAM_SOCIAL_LIMITS.relationships);
779147
+ state5.turns = state5.turns.slice(-TELEGRAM_SOCIAL_LIMITS.turns);
779148
+ state5.claims = state5.claims.slice(-TELEGRAM_SOCIAL_LIMITS.claims);
778379
779149
  state5.salience = state5.salience.slice(-TELEGRAM_SOCIAL_LIMITS.salience);
778380
779150
  state5.outcomes = state5.outcomes.slice(-TELEGRAM_SOCIAL_LIMITS.outcomes);
778381
779151
  retireStaleDaydreamOpportunities(state5, now2);
@@ -778719,6 +779489,7 @@ import {
778719
779489
  statSync as statSync69,
778720
779490
  readFileSync as readFileSync148,
778721
779491
  writeFileSync as writeFileSync100,
779492
+ renameSync as renameSync29,
778722
779493
  appendFileSync as appendFileSync21
778723
779494
  } from "node:fs";
778724
779495
  import {
@@ -781157,7 +781928,7 @@ function renderTelegramSubAgentError(username, error) {
781157
781928
  `${c3.red("✘")} ${c3.bold(`@${username}`)}: ${c3.dim(linkifyTerminalReferences(preview))}`
781158
781929
  );
781159
781930
  }
781160
- var TELEGRAM_TOOL_ACTION_GROUPS, TELEGRAM_TOOL_ACTION_GROUP, TELEGRAM_TOOL_MUTATING_GROUPS, DEFAULT_TELEGRAM_TOOL_GROUP_POLICY, TELEGRAM_TOOL_BUTTON_LABELS, TELEGRAM_SAFETY_PROMPT, ADMIN_DM_PROMPT, ADMIN_GROUP_PROMPT, TELEGRAM_PUBLIC_SOUL_PROFILE, TELEGRAM_PUBLIC_ORCHESTRATOR_CONTRACT, TELEGRAM_PUBLIC_MEMORY_SCOPE_CONTRACT, TELEGRAM_PUBLIC_VISION_STACK_CONTRACT, TELEGRAM_EVIDENCE_SUFFICIENCY_CONTRACT, GROUP_REPLY_DISCRETION_PROMPT, TELEGRAM_CHAT_MODE_PROMPT, ADMIN_CHAT_PROFILE_PROMPT, TELEGRAM_ACTION_RESPONSE_CONTRACT, TELEGRAM_EXTERNAL_ACQUISITION_CONTRACT, TELEGRAM_LINK_INTEGRITY_CONTRACT, TELEGRAM_INTERACTION_DECISION_RESPONSE_FORMAT, TELEGRAM_INTERACTION_DECISION_MINIMAL_SCHEMA, TELEGRAM_INTERACTION_DECISION_REPAIR_SCHEMA, TELEGRAM_CHAT_REPLY_RESPONSE_FORMAT, TelegramEmptyInferenceStreamError, TELEGRAM_SPACED_URL_RE, TELEGRAM_HTTP_URL_RE, TELEGRAM_STUCK_SELF_TALK_PREFIXES, TELEGRAM_CHAT_HISTORY_LIMIT, TELEGRAM_CONTEXT_RECENT_DEFAULT, TELEGRAM_CONTEXT_LINE_LIMIT, TELEGRAM_CONTEXT_SAMPLE_LIMIT, TELEGRAM_MEMORY_CARD_LIMIT, TELEGRAM_MEMORY_NOTE_LIMIT, TELEGRAM_ASSOCIATIVE_FACT_LIMIT, TELEGRAM_ASSOCIATIVE_USER_FACT_LIMIT, TELEGRAM_ASSOCIATIVE_ACTION_LIMIT, TELEGRAM_ASSOCIATIVE_RELATION_LIMIT, TELEGRAM_MEMORY_STOPWORDS, TELEGRAM_MEMORY_GENERIC_QUERY_TOKENS, TELEGRAM_SUB_AGENT_BOUNDED_OPTIONS, TELEGRAM_PUBLIC_FAST_OPTIONS, TELEGRAM_ADMIN_EVIDENCE_OPTIONS, TELEGRAM_SUB_AGENT_DEFAULT_LIMIT, TELEGRAM_SUB_AGENT_MAX_LIMIT, TELEGRAM_SUB_AGENT_BURST_CONTEXT_LIMIT, TELEGRAM_ADMIN_LIVE_PANEL_PAGES, TELEGRAM_ADMIN_LIVE_MUTATION_TOOLS, TELEGRAM_PUBLIC_HELP_COMMANDS2, TELEGRAM_REMINDER_SLASH_COMMANDS, TELEGRAM_REFLECTION_SLASH_COMMANDS, TELEGRAM_PUBLIC_BOT_COMMAND_NAMES, TELEGRAM_IMAGE_EXTENSIONS, MEDIA_CACHE_TTL_MS, TELEGRAM_CHANNEL_DMN_SWEEP_MS, TELEGRAM_CHANNEL_DMN_IDLE_AFTER_MS, TELEGRAM_CHANNEL_DMN_MIN_INTERVAL_MS, TELEGRAM_CHANNEL_DMN_MIN_MESSAGES, TELEGRAM_ALLOWED_UPDATES, TELEGRAM_DEFAULT_LONG_POLL_TIMEOUT_SECONDS, TelegramStartupError, TELEGRAM_PUBLIC_TOOL_QUOTAS, TelegramBridge;
781931
+ var TELEGRAM_TOOL_ACTION_GROUPS, TELEGRAM_TOOL_ACTION_GROUP, TELEGRAM_TOOL_MUTATING_GROUPS, DEFAULT_TELEGRAM_TOOL_GROUP_POLICY, TELEGRAM_TOOL_BUTTON_LABELS, TELEGRAM_SAFETY_PROMPT, ADMIN_DM_PROMPT, ADMIN_GROUP_PROMPT, TELEGRAM_PUBLIC_SOUL_PROFILE, TELEGRAM_PUBLIC_ORCHESTRATOR_CONTRACT, TELEGRAM_PUBLIC_MEMORY_SCOPE_CONTRACT, TELEGRAM_PUBLIC_VISION_STACK_CONTRACT, TELEGRAM_EVIDENCE_SUFFICIENCY_CONTRACT, GROUP_REPLY_DISCRETION_PROMPT, TELEGRAM_CHAT_MODE_PROMPT, ADMIN_CHAT_PROFILE_PROMPT, TELEGRAM_ACTION_RESPONSE_CONTRACT, TELEGRAM_EXTERNAL_ACQUISITION_CONTRACT, TELEGRAM_LINK_INTEGRITY_CONTRACT, TELEGRAM_INTERACTION_DECISION_RESPONSE_FORMAT, TELEGRAM_INTERACTION_DECISION_MINIMAL_SCHEMA, TELEGRAM_INTERACTION_DECISION_REPAIR_SCHEMA, TELEGRAM_CHAT_REPLY_RESPONSE_FORMAT, TelegramEmptyInferenceStreamError, TELEGRAM_SPACED_URL_RE, TELEGRAM_HTTP_URL_RE, TELEGRAM_STUCK_SELF_TALK_PREFIXES, TELEGRAM_CHAT_HISTORY_LIMIT, TELEGRAM_CONTEXT_RECENT_DEFAULT, TELEGRAM_CONTEXT_LINE_LIMIT, TELEGRAM_CONTEXT_SAMPLE_LIMIT, TELEGRAM_MEMORY_CARD_LIMIT, TELEGRAM_MEMORY_NOTE_LIMIT, TELEGRAM_ASSOCIATIVE_FACT_LIMIT, TELEGRAM_ASSOCIATIVE_USER_FACT_LIMIT, TELEGRAM_ASSOCIATIVE_ACTION_LIMIT, TELEGRAM_ASSOCIATIVE_RELATION_LIMIT, TELEGRAM_MEMORY_STOPWORDS, TELEGRAM_MEMORY_GENERIC_QUERY_TOKENS, TELEGRAM_SUB_AGENT_BOUNDED_OPTIONS, TELEGRAM_PUBLIC_FAST_OPTIONS, TELEGRAM_ADMIN_EVIDENCE_OPTIONS, TELEGRAM_SUB_AGENT_DEFAULT_LIMIT, TELEGRAM_SUB_AGENT_MAX_LIMIT, TELEGRAM_SUB_AGENT_BURST_CONTEXT_LIMIT, TELEGRAM_ADMIN_LIVE_PANEL_PAGES, TELEGRAM_ADMIN_LIVE_MUTATION_TOOLS, TELEGRAM_PUBLIC_HELP_COMMANDS2, TELEGRAM_REMINDER_SLASH_COMMANDS, TELEGRAM_REFLECTION_SLASH_COMMANDS, TELEGRAM_PUBLIC_BOT_COMMAND_NAMES, TELEGRAM_IMAGE_EXTENSIONS, MEDIA_CACHE_TTL_MS, TELEGRAM_ACQUIRED_MEDIA_BASENAME_RE, TELEGRAM_CHANNEL_DMN_SWEEP_MS, TELEGRAM_CHANNEL_DMN_IDLE_AFTER_MS, TELEGRAM_CHANNEL_DMN_MIN_INTERVAL_MS, TELEGRAM_CHANNEL_DMN_MIN_MESSAGES, TELEGRAM_ALLOWED_UPDATES, TELEGRAM_DEFAULT_LONG_POLL_TIMEOUT_SECONDS, TelegramStartupError, TELEGRAM_PUBLIC_TOOL_QUOTAS, TelegramBridge;
781161
781932
  var init_telegram_bridge = __esm({
781162
781933
  "packages/cli/src/tui/telegram-bridge.ts"() {
781163
781934
  init_conversation_context();
@@ -781380,7 +782151,7 @@ Public Telegram vision and media stack
781380
782151
 
781381
782152
  Public Telegram runs have the full scoped media-analysis stack for media posted in this chat:
781382
782153
  - To obtain public web images or audio, use telegram_media_acquire. Use action='discover' on a static HTTPS page when needed, then action='acquire' on one exact candidate. Generic downloads, browsers, crawling, HTTP, non-443 ports, caller-selected paths, headers, and credentials remain unavailable.
781383
- - Acquired media is only structurally validated at ingress. Before claiming that it matches the user's request or sending it, run telegram_image_analyze detail='full' for every acquired image, or audio_analyze/transcribe_file for acquired audio. A failed or inconclusive semantic check is not success.
782154
+ - Acquired media is only structurally validated at ingress. Before claiming that it matches the user's request, run telegram_image_analyze detail='full' for every acquired image, or audio_analyze/transcribe_file for acquired audio. Successful semantic validation queues the acquired artifact for verified delivery. task_complete remains blocked until every acquired artifact is validated and Telegram returns a media message id. A failed or inconclusive semantic check is not success.
781384
782155
  - Use telegram_media_recent to find recent scoped media, then use path/media aliases 'reply' and 'latest' instead of exposing local paths to users.
781385
782156
  - MANDATORY: whenever one or more images are present (posted, replied-to, or recent), run a FULL comprehension pass on EVERY image BEFORE responding — telegram_image_analyze with detail='full' (advanced OCR + Moondream vision) on each; if a burst of images was posted, enumerate them with telegram_media_recent and analyze ALL of them. Base the answer ONLY on the extracted content (objects, scene, any text). NEVER answer from metadata alone (count, size, timestamp, caption), never claim you can't say what's pictured without running vision, and never offer to analyze only "the ones you care about" — full vision on all of them IS the job. Do not stop until every image is fully comprehended.
781386
782157
  - Use ocr for quick image text extraction, ocr_image_advanced when basic OCR shows dense or degraded text, image_read for image metadata + multimodal image payload, and vision for direct Moondream captioning, visual QA, object detection, or pointing.
@@ -781684,6 +782455,7 @@ Telegram link integrity contract:
781684
782455
  ".svg"
781685
782456
  ]);
781686
782457
  MEDIA_CACHE_TTL_MS = 30 * 60 * 1e3;
782458
+ TELEGRAM_ACQUIRED_MEDIA_BASENAME_RE = /^acquired-[a-f0-9]{64}\.(?:jpe?g|png|webp|mp3|wav|flac|ogg|m4a)$/i;
781687
782459
  TELEGRAM_CHANNEL_DMN_SWEEP_MS = 2 * 60 * 1e3;
781688
782460
  TELEGRAM_CHANNEL_DMN_IDLE_AFTER_MS = 10 * 60 * 1e3;
781689
782461
  TELEGRAM_CHANNEL_DMN_MIN_INTERVAL_MS = 20 * 60 * 1e3;
@@ -784902,6 +785674,10 @@ ${mediaContext}` : ""
784902
785674
  messageThreadId: msg.messageThreadId,
784903
785675
  replyToMessageId: msg.replyToMessageId,
784904
785676
  replySender: this.telegramReplySocialActorInput(msg),
785677
+ mentionedActors: (msg.mentionedUsernames ?? []).map((username) => ({
785678
+ username,
785679
+ isBot: username.toLowerCase().endsWith("_bot")
785680
+ })),
784905
785681
  salienceSignals: salienceSignals ?? this.telegramMessageIdentitySalienceSignals(msg),
784906
785682
  self: this.telegramSelfSocialActorInput(),
784907
785683
  ...this.telegramMessageSocialActorInput(msg)
@@ -784933,6 +785709,10 @@ ${mediaContext}` : ""
784933
785709
  ),
784934
785710
  isSelf: Boolean(entry.replyContext.sender.isSelf)
784935
785711
  } : void 0,
785712
+ mentionedActors: (entry.mentionedUsernames ?? []).map((username) => ({
785713
+ username,
785714
+ isBot: username.toLowerCase().endsWith("_bot")
785715
+ })),
784936
785716
  self: this.telegramSelfSocialActorInput(),
784937
785717
  userId: entry.fromUserId,
784938
785718
  username: entry.username,
@@ -785462,11 +786242,22 @@ ${mediaContext}` : ""
785462
786242
  autoFollowup: false
785463
786243
  }
785464
786244
  };
785465
- writeFileSync100(
785466
- this.telegramConversationPath(sessionKey),
785467
- JSON.stringify(payload, null, 2) + "\n",
785468
- "utf8"
785469
- );
786245
+ const targetPath = this.telegramConversationPath(sessionKey);
786246
+ const temporaryPath = `${targetPath}.${process.pid}.${randomBytes29(6).toString("hex")}.tmp`;
786247
+ try {
786248
+ writeFileSync100(
786249
+ temporaryPath,
786250
+ JSON.stringify(payload, null, 2) + "\n",
786251
+ "utf8"
786252
+ );
786253
+ renameSync29(temporaryPath, targetPath);
786254
+ } catch (error) {
786255
+ try {
786256
+ if (existsSync176(temporaryPath)) unlinkSync45(temporaryPath);
786257
+ } catch {
786258
+ }
786259
+ throw error;
786260
+ }
785470
786261
  } catch {
785471
786262
  }
785472
786263
  }
@@ -785990,6 +786781,10 @@ ${mediaContext}` : ""
785990
786781
  messageThreadId: msg.messageThreadId,
785991
786782
  replyToMessageId: msg.replyToMessageId,
785992
786783
  replySender: this.telegramReplySocialActorInput(msg),
786784
+ mentionedActors: (msg.mentionedUsernames ?? []).map((username) => ({
786785
+ username,
786786
+ isBot: username.toLowerCase().endsWith("_bot")
786787
+ })),
785993
786788
  salienceSignals,
785994
786789
  self: this.telegramSelfSocialActorInput(),
785995
786790
  ...this.telegramMessageSocialActorInput(msg)
@@ -786092,6 +786887,7 @@ ${mediaContext}` : ""
786092
786887
  messageThreadId: msg.messageThreadId,
786093
786888
  replyToMessageId: msg.replyToMessageId,
786094
786889
  replyContext: msg.replyContext,
786890
+ mentionedUsernames: msg.mentionedUsernames,
786095
786891
  chatType: msg.chatType,
786096
786892
  chatTitle: msg.chatTitle,
786097
786893
  mediaSummary
@@ -786114,17 +786910,6 @@ ${mediaContext}` : ""
786114
786910
  msg.chatType !== "private" ? `Group participant ${telegramSpeakerLabel(msg)} in ${msg.chatTitle || "unknown group"}` : `Private Telegram chat with ${telegramSpeakerLabel(msg)}`
786115
786911
  ]
786116
786912
  });
786117
- updateScopedPersonality(this.telegramUserPersonalityScope(msg), {
786118
- speaker: telegramSpeakerLabel(msg),
786119
- text: text3,
786120
- role: "user",
786121
- mode,
786122
- ts: entry.ts,
786123
- toneTags: inferTelegramToneTags(text3),
786124
- relationshipHints: [
786125
- `Telegram user identity ${telegramSpeakerLabel(msg)} observed in ${msg.chatType}`
786126
- ]
786127
- });
786128
786913
  updateScopedPersonality(
786129
786914
  this.telegramUserInChatPersonalityScope(sessionKey, msg),
786130
786915
  {
@@ -791620,6 +792405,7 @@ Join: ${newUrl}`
791620
792405
  toolContext
791621
792406
  ),
791622
792407
  generatedArtifacts: [],
792408
+ acquiredMediaArtifacts: /* @__PURE__ */ new Set(),
791623
792409
  validatedMediaArtifacts: /* @__PURE__ */ new Set(),
791624
792410
  deliveredArtifacts: [],
791625
792411
  deliveredFileSends: /* @__PURE__ */ new Set(),
@@ -791979,6 +792765,7 @@ ${errMsg}`
791979
792765
  toolContext
791980
792766
  ),
791981
792767
  generatedArtifacts: [],
792768
+ acquiredMediaArtifacts: /* @__PURE__ */ new Set(),
791982
792769
  validatedMediaArtifacts: /* @__PURE__ */ new Set(),
791983
792770
  deliveredArtifacts: [],
791984
792771
  deliveredFileSends: /* @__PURE__ */ new Set(),
@@ -792913,7 +793700,7 @@ ${currentTelegramPrompt}`;
792913
793700
  TELEGRAM_LINK_INTEGRITY_CONTRACT,
792914
793701
  "If a user explicitly states a durable preference for reply cadence/order, call telegram_preference_set. Do not infer or classify reply-mode preferences from keywords, style, tone, or task type.",
792915
793702
  TELEGRAM_EVIDENCE_SUFFICIENCY_CONTRACT,
792916
- "You have the full scoped Telegram media-analysis stack by default: telegram_media_acquire, telegram_image_analyze, telegram_media_recent, image_read, ocr, ocr_image_advanced, vision, pdf_to_text, ocr_pdf, transcribe_file, video_understand, audio_analyze, and identity_memory. Use telegram_media_acquire to discover/acquire public HTTPS images or audio without exposing generic downloads or browsers. MANDATORY image handling: whenever one or more images are present or acquired (posted, replied-to, or recent), you MUST run a FULL comprehension pass on EVERY image before you respond or send it — call telegram_image_analyze with detail='full' (advanced OCR + Moondream vision) on each, and if multiple images were posted/acquired, analyze ALL of them. Base your answer ONLY on the actual extracted content (objects, scene, and any text). NEVER answer from metadata alone (count, size, timestamp, caption), and do not treat successful acquisition as semantic validation. Do not stop until every image is fully comprehended; if a pass returns nothing, retry with detail='full' or image_read+ocr_image_advanced+vision before concluding.",
793703
+ "You have the full scoped Telegram media-analysis stack by default: telegram_media_acquire, telegram_image_analyze, telegram_media_recent, image_read, ocr, ocr_image_advanced, vision, pdf_to_text, ocr_pdf, transcribe_file, video_understand, audio_analyze, and identity_memory. Use telegram_media_acquire to discover/acquire public HTTPS images or audio without exposing generic downloads or browsers. MANDATORY image handling: whenever one or more images are present or acquired (posted, replied-to, or recent), you MUST run a FULL comprehension pass on EVERY image before you respond or send it — call telegram_image_analyze with detail='full' (advanced OCR + Moondream vision) on each, and if multiple images were posted/acquired, analyze ALL of them. Successful semantic validation queues acquired media for automatic verified delivery; do not claim delivery until the completion tool accepts the Telegram delivery receipt. Base your answer ONLY on the actual extracted content (objects, scene, and any text). NEVER answer from metadata alone (count, size, timestamp, caption), and do not treat successful acquisition as semantic validation. Do not stop until every image is fully comprehended; if a pass returns nothing, retry with detail='full' or image_read+ocr_image_advanced+vision before concluding.",
792917
793704
  formatIdentityMemoryContext(chatLabel || "Telegram private chat"),
792918
793705
  reminderToolContract,
792919
793706
  "If the user asks you to create an image, audio file, video, 3D/CAD model, or document artifact, create it with the scoped creative tools. Freshly generated artifacts are recorded and automatically attached to this Telegram chat when the turn completes, so do not call telegram_send_file for those same artifacts unless the user asked for a specific caption, existing/unrecorded file, or non-default target.",
@@ -793991,9 +794778,10 @@ Scoped workspace: ${scopedRoot}`,
793991
794778
  chatId,
793992
794779
  msg
793993
794780
  );
794781
+ const bridge = this;
793994
794782
  const taskComplete = {
793995
794783
  name: "task_complete",
793996
- description: "Internal completion signal for Telegram runs. Put the actual user-facing reply in assistant text before calling this. Use summary 'no_reply' only to silently skip responding; never write that sentinel as assistant text.",
794784
+ description: "Internal completion signal for Telegram runs. Put the actual user-facing reply in assistant text before calling this. Acquired public media must first pass semantic analysis and receive a Telegram delivery receipt; this tool enforces both conditions. Use summary 'no_reply' only to silently skip responding; never write that sentinel as assistant text.",
793997
794785
  parameters: {
793998
794786
  type: "object",
793999
794787
  properties: {
@@ -794005,6 +794793,38 @@ Scoped workspace: ${scopedRoot}`,
794005
794793
  required: ["summary"]
794006
794794
  },
794007
794795
  async execute(args) {
794796
+ const activeSubAgent = msg ? bridge.subAgents.get(bridge.sessionKeyForMessage(msg)) : void 0;
794797
+ const acquired = [...activeSubAgent?.acquiredMediaArtifacts ?? []];
794798
+ if (activeSubAgent && msg && acquired.length > 0) {
794799
+ const unvalidated = acquired.filter(
794800
+ (path16) => !activeSubAgent.validatedMediaArtifacts?.has(resolve82(path16))
794801
+ );
794802
+ if (unvalidated.length > 0) {
794803
+ return {
794804
+ success: false,
794805
+ output: "",
794806
+ error: "Completion blocked: acquired public media has not passed semantic validation. Call telegram_image_analyze detail='full' for each acquired image, or audio_analyze/transcribe_file for each acquired audio file, then call task_complete again."
794807
+ };
794808
+ }
794809
+ await bridge.sendGeneratedArtifactsFromSubAgent(
794810
+ msg,
794811
+ activeSubAgent,
794812
+ "",
794813
+ true
794814
+ );
794815
+ const undelivered = acquired.filter(
794816
+ (path16) => !activeSubAgent.deliveredArtifacts?.some(
794817
+ (delivered) => resolve82(delivered) === resolve82(path16)
794818
+ )
794819
+ );
794820
+ if (undelivered.length > 0) {
794821
+ return {
794822
+ success: false,
794823
+ output: "",
794824
+ error: "Completion blocked: Telegram did not return a delivery receipt for every validated acquired-media artifact. Retry task_complete to retry the queued upload, or call telegram_send_file with the same logical path."
794825
+ };
794826
+ }
794827
+ }
794008
794828
  return {
794009
794829
  success: true,
794010
794830
  output: args["summary"] || "complete"
@@ -794198,7 +795018,8 @@ Scoped workspace: ${scopedRoot}`,
794198
795018
  imageDefaults,
794199
795019
  audioDefaults,
794200
795020
  videoDefaults,
794201
- modelDefaults
795021
+ modelDefaults,
795022
+ (path16) => this.rememberTelegramAcquiredMediaArtifactForMessage(msg, path16)
794202
795023
  ).map((tool) => adaptTool5(tool, todoSessionId, generativeProgress));
794203
795024
  adaptedTools.push(...creativeTools);
794204
795025
  adaptedTools.push(
@@ -796724,6 +797545,7 @@ ${objectMemory.contextBlock}`
796724
797545
  currentMsg,
796725
797546
  sendFingerprint
796726
797547
  )) {
797548
+ file.cleanup?.();
796727
797549
  return {
796728
797550
  success: true,
796729
797551
  output: `Telegram file already sent in this turn: ${basename44(file.path)} as ${kind} to ${String(target.chatId)}`,
@@ -796873,11 +797695,32 @@ ${knownList}` : "Private-user telegram_send_file target must be this DM or a kno
796873
797695
  if (!subAgent) return;
796874
797696
  this.rememberTelegramDeliveredArtifact(subAgent, path16);
796875
797697
  }
797698
+ rememberTelegramAcquiredMediaArtifactForMessage(msg, path16) {
797699
+ if (!msg || !path16) return;
797700
+ const subAgent = this.subAgents.get(this.sessionKeyForMessage(msg));
797701
+ const root = subAgent?.creativeWorkspaceRoot;
797702
+ if (!subAgent || !root) return;
797703
+ const abs = resolve82(path16);
797704
+ if (!isPathInside2(resolve82(root), abs) || !TELEGRAM_ACQUIRED_MEDIA_BASENAME_RE.test(basename44(abs))) {
797705
+ return;
797706
+ }
797707
+ subAgent.acquiredMediaArtifacts ??= /* @__PURE__ */ new Set();
797708
+ subAgent.acquiredMediaArtifacts.add(abs);
797709
+ }
796876
797710
  rememberTelegramValidatedMediaArtifactForMessage(msg, path16) {
796877
797711
  if (!msg || !path16) return;
796878
797712
  const subAgent = this.subAgents.get(this.sessionKeyForMessage(msg));
796879
797713
  if (!subAgent) return;
796880
- subAgent.validatedMediaArtifacts.add(resolve82(path16));
797714
+ const abs = resolve82(path16);
797715
+ subAgent.validatedMediaArtifacts ??= /* @__PURE__ */ new Set();
797716
+ subAgent.validatedMediaArtifacts.add(abs);
797717
+ const root = subAgent.creativeWorkspaceRoot;
797718
+ if (root && isPathInside2(resolve82(root), abs) && TELEGRAM_ACQUIRED_MEDIA_BASENAME_RE.test(basename44(abs))) {
797719
+ subAgent.generatedArtifacts ??= [];
797720
+ if (!subAgent.generatedArtifacts.some((item) => resolve82(item) === abs)) {
797721
+ subAgent.generatedArtifacts.push(abs);
797722
+ }
797723
+ }
796881
797724
  }
796882
797725
  telegramMediaArtifactValidatedForMessage(msg, path16) {
796883
797726
  if (!msg) return false;
@@ -797670,35 +798513,50 @@ Content-Type: ${contentType}\r
797670
798513
  }
797671
798514
  async sendGeneratedArtifactsFromSubAgent(msg, subAgent, finalText, includeMentioned) {
797672
798515
  const root = subAgent.creativeWorkspaceRoot;
797673
- if (!root) return;
798516
+ if (!root) return { attempted: 0, delivered: 0, failures: [] };
797674
798517
  const rootAbs = resolve82(root);
797675
798518
  const paths = new Set(subAgent.generatedArtifacts);
797676
798519
  for (const path16 of collectGeneratedArtifactPathsFromText(finalText, root)) {
797677
798520
  paths.add(path16);
797678
798521
  }
797679
- if (paths.size === 0) return;
798522
+ if (paths.size === 0)
798523
+ return { attempted: 0, delivered: 0, failures: [] };
797680
798524
  const alreadySentByText = new Set(
797681
798525
  extractMediaReferences(finalText).media.filter((media) => media.source === "file").map((media) => resolve82(media.value))
797682
798526
  );
797683
798527
  const alreadyDelivered = new Set(
797684
798528
  (subAgent.deliveredArtifacts ?? []).map((path16) => resolve82(path16))
797685
798529
  );
798530
+ const failures = [];
798531
+ let attempted = 0;
798532
+ let delivered = 0;
797686
798533
  for (const path16 of paths) {
797687
798534
  const abs = resolve82(path16);
797688
798535
  if (!isPathInside2(rootAbs, abs)) continue;
797689
798536
  if (alreadyDelivered.has(abs)) continue;
797690
798537
  if (!includeMentioned && alreadySentByText.has(abs)) continue;
798538
+ if (TELEGRAM_ACQUIRED_MEDIA_BASENAME_RE.test(basename44(abs)) && !subAgent.validatedMediaArtifacts?.has(abs)) {
798539
+ failures.push(
798540
+ `${basename44(abs)} was not sent because semantic validation is missing.`
798541
+ );
798542
+ continue;
798543
+ }
797691
798544
  const materialized = materializeTelegramCreativeArtifactForSend(
797692
798545
  rootAbs,
797693
798546
  abs
797694
798547
  );
797695
- if (!materialized.ok) continue;
798548
+ if (!materialized.ok) {
798549
+ failures.push(materialized.error);
798550
+ continue;
798551
+ }
797696
798552
  if (!existsSync176(materialized.path) || !statSync69(materialized.path).isFile()) {
798553
+ failures.push(`Artifact could not be materialized: ${basename44(abs)}`);
797697
798554
  materialized.cleanup?.();
797698
798555
  continue;
797699
798556
  }
797700
798557
  const kind = classifyMedia(materialized.path) ?? "document";
797701
- await this.sendMediaReference(
798558
+ attempted += 1;
798559
+ await this.sendMediaReferenceStrict(
797702
798560
  msg.chatId,
797703
798561
  {
797704
798562
  original: materialized.path,
@@ -797714,11 +798572,24 @@ Content-Type: ${contentType}\r
797714
798572
  if (messageId !== null) {
797715
798573
  this.rememberTelegramDeliveredArtifact(subAgent, abs);
797716
798574
  alreadyDelivered.add(abs);
798575
+ delivered += 1;
798576
+ } else {
798577
+ failures.push(
798578
+ `Telegram returned no delivery message id for ${basename44(abs)}.`
798579
+ );
797717
798580
  }
797718
- }).catch(() => null).finally(() => {
798581
+ }).catch((err) => {
798582
+ const reason = err instanceof Error ? err.message : String(err);
798583
+ failures.push(`${basename44(abs)}: ${reason}`);
798584
+ this.subAgentViewCallbacks?.onWrite(
798585
+ subAgent.viewId,
798586
+ `attachment delivery failed: ${basename44(abs)}: ${reason}`
798587
+ );
798588
+ }).finally(() => {
797719
798589
  materialized.cleanup?.();
797720
798590
  });
797721
798591
  }
798592
+ return { attempted, delivered, failures };
797722
798593
  }
797723
798594
  async uploadTelegramFiles(method, fields, files) {
797724
798595
  const boundary = `----omnius-upload-${Date.now()}-${Math.random().toString(36).slice(2)}`;
@@ -799948,7 +800819,7 @@ __export(projects_exports, {
799948
800819
  setCurrentProject: () => setCurrentProject,
799949
800820
  unregisterProject: () => unregisterProject
799950
800821
  });
799951
- import { readFileSync as readFileSync149, writeFileSync as writeFileSync101, mkdirSync as mkdirSync112, existsSync as existsSync177, statSync as statSync70, renameSync as renameSync29 } from "node:fs";
800822
+ import { readFileSync as readFileSync149, writeFileSync as writeFileSync101, mkdirSync as mkdirSync112, existsSync as existsSync177, statSync as statSync70, renameSync as renameSync30 } from "node:fs";
799952
800823
  import { homedir as homedir62 } from "node:os";
799953
800824
  import { basename as basename45, join as join187, resolve as resolve83 } from "node:path";
799954
800825
  import { randomUUID as randomUUID33 } from "node:crypto";
@@ -799967,7 +800838,7 @@ function writeAll(file) {
799967
800838
  mkdirSync112(OMNIUS_DIR3, { recursive: true });
799968
800839
  const tmp = `${PROJECTS_FILE}.${randomUUID33().slice(0, 8)}.tmp`;
799969
800840
  writeFileSync101(tmp, JSON.stringify(file, null, 2), "utf8");
799970
- renameSync29(tmp, PROJECTS_FILE);
800841
+ renameSync30(tmp, PROJECTS_FILE);
799971
800842
  }
799972
800843
  function listProjects() {
799973
800844
  const { projects } = readAll2();
@@ -813491,7 +814362,7 @@ var init_access_policy = __esm({
813491
814362
 
813492
814363
  // packages/cli/src/api/project-preferences.ts
813493
814364
  import { createHash as createHash75 } from "node:crypto";
813494
- import { existsSync as existsSync181, mkdirSync as mkdirSync116, readFileSync as readFileSync153, renameSync as renameSync30, writeFileSync as writeFileSync105, unlinkSync as unlinkSync46 } from "node:fs";
814365
+ import { existsSync as existsSync181, mkdirSync as mkdirSync116, readFileSync as readFileSync153, renameSync as renameSync31, writeFileSync as writeFileSync105, unlinkSync as unlinkSync46 } from "node:fs";
813495
814366
  import { homedir as homedir65 } from "node:os";
813496
814367
  import { join as join192, resolve as resolve88 } from "node:path";
813497
814368
  import { randomUUID as randomUUID34 } from "node:crypto";
@@ -813545,7 +814416,7 @@ function writeProjectPreferences(root, partial) {
813545
814416
  const tmp = `${file}.${randomUUID34().slice(0, 8)}.tmp`;
813546
814417
  writeFileSync105(tmp, JSON.stringify(merged, null, 2), "utf8");
813547
814418
  try {
813548
- renameSync30(tmp, file);
814419
+ renameSync31(tmp, file);
813549
814420
  } catch (err) {
813550
814421
  try {
813551
814422
  writeFileSync105(file, JSON.stringify(merged, null, 2), "utf8");
@@ -815507,7 +816378,7 @@ var init_direct_tool_registry = __esm({
815507
816378
  });
815508
816379
 
815509
816380
  // packages/cli/src/api/external-tool-registry.ts
815510
- import { existsSync as existsSync187, mkdirSync as mkdirSync122, readFileSync as readFileSync157, writeFileSync as writeFileSync108, renameSync as renameSync31 } from "node:fs";
816381
+ import { existsSync as existsSync187, mkdirSync as mkdirSync122, readFileSync as readFileSync157, writeFileSync as writeFileSync108, renameSync as renameSync32 } from "node:fs";
815511
816382
  import { join as join197 } from "node:path";
815512
816383
  function externalToolStorePath(workingDir) {
815513
816384
  return join197(workingDir, ".omnius", "external-tools.json");
@@ -815531,7 +816402,7 @@ function persist4(workingDir, tools) {
815531
816402
  const file = { version: STORE_VERSION, tools };
815532
816403
  const tmp = `${path16}.tmp`;
815533
816404
  writeFileSync108(tmp, JSON.stringify(file, null, 2), { mode: 384 });
815534
- renameSync31(tmp, path16);
816405
+ renameSync32(tmp, path16);
815535
816406
  }
815536
816407
  function validateManifest3(input, existing) {
815537
816408
  if (!input || typeof input !== "object") {
@@ -822480,9 +823351,6 @@ function getWebUI() {
822480
823351
  <meta charset="UTF-8">
822481
823352
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
822482
823353
  <title>Omnius</title>
822483
- <link rel="preconnect" href="https://fonts.googleapis.com">
822484
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
822485
- <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
822486
823354
  <style>
822487
823355
  /* ─── Cold Tactical HUD design tokens ──────────────────────────────
822488
823356
  * Source of truth:
@@ -824554,12 +825422,6 @@ textarea,
824554
825422
  font-variant-numeric: tabular-nums !important;
824555
825423
  }
824556
825424
 
824557
- .live,
824558
- .success,
824559
- .ok { color: var(--nclp-ok) !important; }
824560
- .error,
824561
- .bad { color: var(--nclp-bad) !important; }
824562
-
824563
825425
  html { scrollbar-color: var(--nclp-line-strong) var(--nclp-bg-main); }
824564
825426
  *::-webkit-scrollbar { width: 6px; height: 6px; }
824565
825427
  *::-webkit-scrollbar-track { background: var(--nclp-bg-main); }
@@ -824867,6 +825729,1235 @@ html { scrollbar-color: var(--nclp-line-strong) var(--nclp-bg-main); }
824867
825729
  .workspace-picker { position: fixed; top: 54px; right: 12px; left: 12px; width: auto; }
824868
825730
  }
824869
825731
 
825732
+ /* ─── Bounded workspace shell ─────────────────────────────────────
825733
+ * Interaction and composition reference: the user-designated Huggle chat
825734
+ * surface. Omnius keeps its own identity, content, routes, and assets.
825735
+ */
825736
+ :root {
825737
+ color-scheme: dark;
825738
+ --workspace-outer: #11120f;
825739
+ --workspace-canvas: #191a17;
825740
+ --workspace-panel: #20211e;
825741
+ --workspace-panel-raised: #282925;
825742
+ --workspace-panel-hover: #30312d;
825743
+ --workspace-border: rgba(255, 255, 255, 0.075);
825744
+ --workspace-border-strong: rgba(255, 255, 255, 0.13);
825745
+ --workspace-text: #ecece8;
825746
+ --workspace-muted: #a4a49e;
825747
+ --workspace-quiet: #71716c;
825748
+ --workspace-blue: #75c9eb;
825749
+ --workspace-rose: #ee9aa4;
825750
+ --workspace-cyan: #60d6db;
825751
+ --workspace-violet: #b7a2eb;
825752
+ --workspace-mint: #68d7b0;
825753
+ --workspace-gold: #d7af58;
825754
+ --workspace-shadow: 0 18px 55px rgba(0, 0, 0, 0.28);
825755
+ --font-ui: Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
825756
+ --font-mono: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
825757
+ --radius-sm: 8px;
825758
+ --radius-md: 11px;
825759
+ --radius-lg: 15px;
825760
+
825761
+ --color-bg: var(--workspace-outer);
825762
+ --color-bg-elevated: var(--workspace-panel);
825763
+ --color-bg-input: var(--workspace-panel-raised);
825764
+ --color-bg-hover: var(--workspace-panel-hover);
825765
+ --color-fg: var(--workspace-text);
825766
+ --color-fg-muted: var(--workspace-muted);
825767
+ --color-fg-subtle: var(--workspace-muted);
825768
+ --color-fg-faint: var(--workspace-quiet);
825769
+ --color-border: var(--workspace-border);
825770
+ --color-border-strong: var(--workspace-border-strong);
825771
+ --color-accent: var(--workspace-text);
825772
+ --color-accent-hover: #ffffff;
825773
+ --color-accent-fg: var(--workspace-outer);
825774
+ --color-brand: var(--workspace-text);
825775
+ --color-brand-hover: #ffffff;
825776
+ }
825777
+
825778
+ html[data-theme="light"] {
825779
+ color-scheme: dark;
825780
+ --color-bg: var(--workspace-outer);
825781
+ --color-bg-elevated: var(--workspace-panel);
825782
+ --color-bg-input: var(--workspace-panel-raised);
825783
+ --color-bg-hover: var(--workspace-panel-hover);
825784
+ --color-fg: var(--workspace-text);
825785
+ --color-fg-muted: var(--workspace-muted);
825786
+ --color-fg-subtle: var(--workspace-muted);
825787
+ --color-fg-faint: var(--workspace-quiet);
825788
+ --color-border: var(--workspace-border);
825789
+ --color-border-strong: var(--workspace-border-strong);
825790
+ }
825791
+
825792
+ html,
825793
+ body,
825794
+ #omnius-shell,
825795
+ #omnius-main {
825796
+ color: var(--workspace-text) !important;
825797
+ font-family: var(--font-ui) !important;
825798
+ }
825799
+
825800
+ body {
825801
+ background: var(--workspace-outer) !important;
825802
+ font-size: 14px;
825803
+ }
825804
+
825805
+ body::before,
825806
+ body::after,
825807
+ #omnius-sidebar::before,
825808
+ .observability-card::before,
825809
+ .hud-panel::before,
825810
+ .hud-result-card::before,
825811
+ .hud-btn__chrome,
825812
+ .hud-tab__chrome {
825813
+ content: none !important;
825814
+ display: none !important;
825815
+ }
825816
+
825817
+ button,
825818
+ [role="button"],
825819
+ .hud-btn,
825820
+ .hud-tab,
825821
+ .sb-nav {
825822
+ border-radius: var(--radius-sm) !important;
825823
+ font-family: var(--font-ui) !important;
825824
+ font-weight: 500 !important;
825825
+ letter-spacing: 0 !important;
825826
+ text-transform: none !important;
825827
+ }
825828
+
825829
+ button:focus-visible,
825830
+ [role="button"]:focus-visible,
825831
+ input:focus-visible,
825832
+ textarea:focus-visible,
825833
+ select:focus-visible {
825834
+ outline: 2px solid rgba(236, 236, 232, 0.72) !important;
825835
+ outline-offset: 2px;
825836
+ }
825837
+
825838
+ input,
825839
+ textarea,
825840
+ select {
825841
+ border-radius: var(--radius-sm) !important;
825842
+ border-color: var(--workspace-border) !important;
825843
+ background: var(--workspace-panel) !important;
825844
+ color: var(--workspace-text) !important;
825845
+ font-family: var(--font-ui) !important;
825846
+ box-shadow: none !important;
825847
+ }
825848
+
825849
+ input:focus,
825850
+ textarea:focus,
825851
+ select:focus {
825852
+ border-color: var(--workspace-border-strong) !important;
825853
+ box-shadow: none !important;
825854
+ }
825855
+
825856
+ #omnius-shell {
825857
+ height: 100vh !important;
825858
+ padding: 8px;
825859
+ gap: 0;
825860
+ background: var(--workspace-outer) !important;
825861
+ }
825862
+
825863
+ #omnius-sidebar {
825864
+ width: 260px;
825865
+ border: 0 !important;
825866
+ background: var(--workspace-outer) !important;
825867
+ box-shadow: none !important;
825868
+ padding-right: 8px;
825869
+ overflow: visible;
825870
+ }
825871
+
825872
+ #omnius-main {
825873
+ position: relative;
825874
+ border: 1px solid var(--workspace-border) !important;
825875
+ border-radius: 17px;
825876
+ background: var(--workspace-canvas) !important;
825877
+ box-shadow: var(--workspace-shadow);
825878
+ isolation: isolate;
825879
+ }
825880
+
825881
+ #omnius-sidebar[data-collapsed="true"] {
825882
+ width: 52px !important;
825883
+ padding-right: 6px;
825884
+ }
825885
+
825886
+ .sidebar-mode-row {
825887
+ display: flex;
825888
+ align-items: center;
825889
+ gap: 7px;
825890
+ min-height: 46px;
825891
+ padding: 4px;
825892
+ }
825893
+
825894
+ .sidebar-mode-button {
825895
+ display: flex;
825896
+ flex: 1;
825897
+ align-items: center;
825898
+ gap: 8px;
825899
+ min-width: 0;
825900
+ height: 36px;
825901
+ padding: 0 12px;
825902
+ border: 1px solid var(--workspace-border-strong);
825903
+ background: var(--workspace-panel-raised);
825904
+ color: var(--workspace-text);
825905
+ box-shadow: inset 0 1px rgba(255,255,255,0.035);
825906
+ }
825907
+
825908
+ #sidebar-toggle {
825909
+ display: inline-flex !important;
825910
+ width: 32px !important;
825911
+ height: 32px !important;
825912
+ align-items: center;
825913
+ justify-content: center;
825914
+ padding: 0 !important;
825915
+ border: 0 !important;
825916
+ background: transparent !important;
825917
+ color: var(--workspace-quiet) !important;
825918
+ cursor: pointer;
825919
+ }
825920
+
825921
+ .sidebar-workspace-row {
825922
+ display: flex;
825923
+ padding: 5px 5px 8px;
825924
+ }
825925
+
825926
+ .workspace-switcher__trigger {
825927
+ min-height: 32px;
825928
+ border: 0 !important;
825929
+ background: transparent !important;
825930
+ color: var(--workspace-muted) !important;
825931
+ }
825932
+
825933
+ #sidebar-brand-dot {
825934
+ width: 7px !important;
825935
+ height: 7px !important;
825936
+ box-shadow: none !important;
825937
+ }
825938
+
825939
+ #sidebar-brand {
825940
+ color: var(--workspace-muted) !important;
825941
+ font-family: var(--font-ui) !important;
825942
+ font-size: 12px !important;
825943
+ font-weight: 500 !important;
825944
+ letter-spacing: 0 !important;
825945
+ text-transform: none !important;
825946
+ }
825947
+
825948
+ #sidebar-new-chat-row {
825949
+ padding: 0 5px 7px !important;
825950
+ }
825951
+
825952
+ #sidebar-new-chat-row > button {
825953
+ display: flex;
825954
+ width: 100%;
825955
+ align-items: center;
825956
+ gap: 9px;
825957
+ padding: 8px 10px !important;
825958
+ border: 0 !important;
825959
+ background: transparent !important;
825960
+ color: var(--workspace-muted) !important;
825961
+ cursor: pointer;
825962
+ text-align: left;
825963
+ }
825964
+
825965
+ #sidebar-new-chat-row > button:hover,
825966
+ #sidebar-toggle:hover,
825967
+ .workspace-switcher__trigger:hover {
825968
+ background: var(--workspace-panel) !important;
825969
+ color: var(--workspace-text) !important;
825970
+ }
825971
+
825972
+ #sidebar-search-row {
825973
+ padding: 0 5px 5px !important;
825974
+ }
825975
+
825976
+ #sidebar-search {
825977
+ height: 34px;
825978
+ border: 0 !important;
825979
+ background: transparent !important;
825980
+ font-size: 12px !important;
825981
+ }
825982
+
825983
+ #sidebar-search:focus {
825984
+ background: var(--workspace-panel) !important;
825985
+ }
825986
+
825987
+ #sidebar-chats {
825988
+ padding: 2px 2px 8px !important;
825989
+ }
825990
+
825991
+ .sb-chat {
825992
+ min-height: 34px;
825993
+ border-radius: var(--radius-sm) !important;
825994
+ color: var(--workspace-muted) !important;
825995
+ }
825996
+
825997
+ .sb-chat:hover,
825998
+ .sb-chat.active {
825999
+ background: var(--workspace-panel) !important;
826000
+ color: var(--workspace-text) !important;
826001
+ }
826002
+
826003
+ #sidebar-nav {
826004
+ display: grid !important;
826005
+ grid-template-columns: repeat(3, minmax(0, 1fr));
826006
+ gap: 3px !important;
826007
+ margin: 0 2px 7px;
826008
+ padding: 6px !important;
826009
+ border: 1px solid var(--workspace-border) !important;
826010
+ border-radius: var(--radius-md);
826011
+ background: var(--workspace-canvas) !important;
826012
+ }
826013
+
826014
+ .sb-nav {
826015
+ min-width: 0;
826016
+ min-height: 46px;
826017
+ flex-direction: column;
826018
+ justify-content: center;
826019
+ gap: 3px !important;
826020
+ padding: 5px 2px !important;
826021
+ border: 1px solid transparent !important;
826022
+ color: var(--workspace-quiet) !important;
826023
+ font-size: 9px !important;
826024
+ text-align: center;
826025
+ }
826026
+
826027
+ .sb-nav svg {
826028
+ width: 15px;
826029
+ height: 15px;
826030
+ }
826031
+
826032
+ .sb-nav:hover,
826033
+ .sb-nav.active {
826034
+ border-color: var(--workspace-border) !important;
826035
+ background: var(--workspace-panel) !important;
826036
+ color: var(--workspace-text) !important;
826037
+ box-shadow: none !important;
826038
+ }
826039
+
826040
+ #sidebar-footer {
826041
+ min-height: 42px;
826042
+ margin: 0 2px;
826043
+ padding: 7px 8px !important;
826044
+ border: 0 !important;
826045
+ background: transparent !important;
826046
+ color: var(--workspace-quiet) !important;
826047
+ }
826048
+
826049
+ #sidebar-key-btn,
826050
+ #sidebar-update-btn {
826051
+ border: 0 !important;
826052
+ background: var(--workspace-panel) !important;
826053
+ color: var(--workspace-muted) !important;
826054
+ }
826055
+
826056
+ #sidebar-resize {
826057
+ right: 4px !important;
826058
+ width: 3px !important;
826059
+ }
826060
+
826061
+ #omnius-sidebar[data-collapsed="true"] .sidebar-mode-button {
826062
+ flex: 0 0 38px;
826063
+ justify-content: center;
826064
+ padding: 0;
826065
+ }
826066
+
826067
+ #omnius-sidebar[data-collapsed="true"] #sidebar-toggle,
826068
+ #omnius-sidebar[data-collapsed="true"] .sidebar-workspace-row,
826069
+ #omnius-sidebar[data-collapsed="true"] #sidebar-new-chat-row,
826070
+ #omnius-sidebar[data-collapsed="true"] #sidebar-search-row,
826071
+ #omnius-sidebar[data-collapsed="true"] #sidebar-chats,
826072
+ #omnius-sidebar[data-collapsed="true"] #sidebar-footer {
826073
+ display: none !important;
826074
+ }
826075
+
826076
+ #omnius-sidebar[data-collapsed="true"] #sidebar-nav {
826077
+ display: flex !important;
826078
+ flex-direction: column;
826079
+ padding: 3px !important;
826080
+ margin-top: auto;
826081
+ }
826082
+
826083
+ #omnius-sidebar[data-collapsed="true"] .sb-nav {
826084
+ min-height: 38px;
826085
+ padding: 7px !important;
826086
+ }
826087
+
826088
+ #header {
826089
+ min-height: 50px;
826090
+ padding: 7px 14px !important;
826091
+ border: 0 !important;
826092
+ background: transparent !important;
826093
+ box-shadow: none !important;
826094
+ }
826095
+
826096
+ .header-route-copy {
826097
+ display: flex;
826098
+ flex-direction: column;
826099
+ gap: 1px;
826100
+ min-width: 100px;
826101
+ }
826102
+
826103
+ .header-route-kicker {
826104
+ color: var(--workspace-quiet);
826105
+ font-size: 9px;
826106
+ letter-spacing: 0.08em;
826107
+ text-transform: uppercase;
826108
+ }
826109
+
826110
+ #desktop-route-title {
826111
+ color: var(--workspace-text);
826112
+ font-size: 12px;
826113
+ font-weight: 550;
826114
+ }
826115
+
826116
+ #header select,
826117
+ #header .key-btn,
826118
+ #header #update-btn {
826119
+ min-height: 30px;
826120
+ padding: 4px 9px !important;
826121
+ border: 1px solid var(--workspace-border) !important;
826122
+ border-radius: var(--radius-sm) !important;
826123
+ background: var(--workspace-panel) !important;
826124
+ color: var(--workspace-muted) !important;
826125
+ }
826126
+
826127
+ #system-prompt-area {
826128
+ border: 0;
826129
+ background: var(--workspace-canvas) !important;
826130
+ }
826131
+
826132
+ [data-route-page] {
826133
+ min-width: 0;
826134
+ background: var(--workspace-canvas) !important;
826135
+ background-image: none !important;
826136
+ }
826137
+
826138
+ .omnius-route-shell {
826139
+ padding: 18px 22px 22px !important;
826140
+ }
826141
+
826142
+ #chat-container {
826143
+ padding: 0 !important;
826144
+ }
826145
+
826146
+ #chat-container,
826147
+ #agent-panel,
826148
+ #jobs-panel,
826149
+ #config-panel,
826150
+ #activity-panel,
826151
+ #discover-panel,
826152
+ #projects-panel,
826153
+ #voice-panel,
826154
+ #generate-panel {
826155
+ background: var(--workspace-canvas) !important;
826156
+ background-image: none !important;
826157
+ }
826158
+
826159
+ #chat-container > .route-heading {
826160
+ display: none;
826161
+ }
826162
+
826163
+ .route-heading {
826164
+ margin: 0 0 16px;
826165
+ padding: 4px 2px 14px;
826166
+ border-bottom: 1px solid var(--workspace-border);
826167
+ }
826168
+
826169
+ .route-heading__eyebrow {
826170
+ color: var(--workspace-quiet);
826171
+ font-size: 9px;
826172
+ }
826173
+
826174
+ .route-heading__title {
826175
+ color: var(--workspace-text) !important;
826176
+ font-size: 21px !important;
826177
+ font-weight: 500 !important;
826178
+ letter-spacing: -0.02em !important;
826179
+ }
826180
+
826181
+ .route-heading__description {
826182
+ color: var(--workspace-muted);
826183
+ font-size: 12px;
826184
+ }
826185
+
826186
+ .route-heading__signal,
826187
+ .observability-chip {
826188
+ border-color: var(--workspace-border) !important;
826189
+ border-radius: 999px;
826190
+ background: var(--workspace-panel) !important;
826191
+ color: var(--workspace-quiet) !important;
826192
+ font-size: 9px;
826193
+ letter-spacing: 0;
826194
+ text-transform: none;
826195
+ }
826196
+
826197
+ .observability-grid {
826198
+ grid-template-columns: repeat(6, minmax(0, 1fr));
826199
+ gap: 12px;
826200
+ }
826201
+
826202
+ .observability-card,
826203
+ .hud-panel,
826204
+ .hud-result-card,
826205
+ .settings-provider-module,
826206
+ .settings-provider-setup,
826207
+ .omnius-modal-window,
826208
+ #key-modal .modal,
826209
+ #proc-popover,
826210
+ #slash-palette {
826211
+ border: 1px solid var(--workspace-border) !important;
826212
+ border-radius: var(--radius-lg) !important;
826213
+ background: var(--workspace-panel) !important;
826214
+ box-shadow: none !important;
826215
+ clip-path: none !important;
826216
+ backdrop-filter: none !important;
826217
+ }
826218
+
826219
+ .observability-card__header,
826220
+ .hud-panel__header {
826221
+ padding: 13px 14px 10px;
826222
+ border-color: var(--workspace-border) !important;
826223
+ }
826224
+
826225
+ .observability-card__title,
826226
+ .hud-panel__title,
826227
+ .hud-title {
826228
+ color: var(--workspace-text) !important;
826229
+ font-family: var(--font-ui) !important;
826230
+ font-size: 12px !important;
826231
+ font-weight: 550 !important;
826232
+ letter-spacing: 0 !important;
826233
+ text-transform: none !important;
826234
+ }
826235
+
826236
+ .observability-card__meta,
826237
+ .kicker {
826238
+ color: var(--workspace-quiet) !important;
826239
+ font-size: 9px;
826240
+ letter-spacing: 0 !important;
826241
+ text-transform: none !important;
826242
+ }
826243
+
826244
+ .observability-card__body {
826245
+ padding: 13px 14px 15px;
826246
+ }
826247
+
826248
+ .hud-btn,
826249
+ .hud-tab,
826250
+ .omnius-btn-secondary,
826251
+ .observability-actions button,
826252
+ .session-topbar button {
826253
+ border: 1px solid var(--workspace-border) !important;
826254
+ background: var(--workspace-panel-raised) !important;
826255
+ color: var(--workspace-muted) !important;
826256
+ box-shadow: none !important;
826257
+ clip-path: none !important;
826258
+ }
826259
+
826260
+ .hud-btn:hover,
826261
+ .hud-tab:hover,
826262
+ .hud-tab.active,
826263
+ .hud-tab[aria-selected="true"],
826264
+ .omnius-btn-secondary:hover,
826265
+ .observability-actions button:hover {
826266
+ border-color: var(--workspace-border-strong) !important;
826267
+ background: var(--workspace-panel-hover) !important;
826268
+ color: var(--workspace-text) !important;
826269
+ box-shadow: none !important;
826270
+ }
826271
+
826272
+ .route-workspace__main {
826273
+ position: relative;
826274
+ }
826275
+
826276
+ .session-topbar {
826277
+ min-height: 42px;
826278
+ padding: 6px 16px !important;
826279
+ border: 0 !important;
826280
+ background: transparent !important;
826281
+ color: var(--workspace-quiet) !important;
826282
+ }
826283
+
826284
+ .session-topbar select {
826285
+ max-width: 320px;
826286
+ border: 0 !important;
826287
+ background: transparent !important;
826288
+ color: var(--workspace-muted) !important;
826289
+ }
826290
+
826291
+ #conversation {
826292
+ width: min(100%, 820px);
826293
+ margin: 0 auto;
826294
+ padding: 38px 24px 120px !important;
826295
+ gap: 8px !important;
826296
+ }
826297
+
826298
+ .msg {
826299
+ color: var(--workspace-text) !important;
826300
+ font-size: 14px !important;
826301
+ line-height: 1.62;
826302
+ }
826303
+
826304
+ .msg.user > * {
826305
+ max-width: min(78%, 640px);
826306
+ padding: 10px 14px;
826307
+ border: 1px solid var(--workspace-border) !important;
826308
+ border-radius: 16px !important;
826309
+ background: var(--workspace-panel-raised) !important;
826310
+ }
826311
+
826312
+ .msg.assistant {
826313
+ padding-left: 42px;
826314
+ }
826315
+
826316
+ .msg.assistant::before {
826317
+ width: 29px;
826318
+ height: 29px;
826319
+ border: 1px solid var(--workspace-border) !important;
826320
+ border-radius: 9px !important;
826321
+ background: var(--workspace-panel) !important;
826322
+ color: var(--workspace-text) !important;
826323
+ clip-path: none !important;
826324
+ }
826325
+
826326
+ .msg.system > *,
826327
+ .msg .tool-card,
826328
+ .msg pre {
826329
+ border-color: var(--workspace-border) !important;
826330
+ border-radius: var(--radius-sm) !important;
826331
+ background: var(--workspace-panel) !important;
826332
+ color: var(--workspace-muted) !important;
826333
+ }
826334
+
826335
+ .msg-actions button {
826336
+ border-radius: 7px !important;
826337
+ }
826338
+
826339
+ #chat-empty-state {
826340
+ position: absolute;
826341
+ inset: 0;
826342
+ z-index: 2;
826343
+ display: flex;
826344
+ flex-direction: column;
826345
+ align-items: center;
826346
+ padding: min(25vh, 235px) 24px 90px;
826347
+ overflow-y: auto;
826348
+ pointer-events: none;
826349
+ }
826350
+
826351
+ #chat-empty-state[hidden] {
826352
+ display: none !important;
826353
+ }
826354
+
826355
+ #chat-empty-state h1 {
826356
+ margin: 0;
826357
+ color: #c9c9c4 !important;
826358
+ font-family: var(--font-ui) !important;
826359
+ font-size: clamp(24px, 2.2vw, 31px) !important;
826360
+ font-weight: 400 !important;
826361
+ letter-spacing: -0.035em !important;
826362
+ text-transform: none !important;
826363
+ }
826364
+
826365
+ .chat-empty-composer-space {
826366
+ width: 1px;
826367
+ height: 166px;
826368
+ flex: 0 0 166px;
826369
+ }
826370
+
826371
+ .capability-grid {
826372
+ display: grid;
826373
+ grid-template-columns: repeat(6, minmax(0, 1fr));
826374
+ gap: 7px;
826375
+ width: min(680px, 100%);
826376
+ pointer-events: auto;
826377
+ }
826378
+
826379
+ .capability-grid button {
826380
+ display: flex;
826381
+ min-width: 0;
826382
+ min-height: 76px;
826383
+ flex-direction: column;
826384
+ align-items: center;
826385
+ justify-content: center;
826386
+ gap: 8px;
826387
+ border: 1px solid transparent !important;
826388
+ border-radius: 12px !important;
826389
+ background: rgba(255,255,255,0.022) !important;
826390
+ color: var(--workspace-muted) !important;
826391
+ cursor: pointer;
826392
+ font-size: 12px;
826393
+ }
826394
+
826395
+ .capability-grid button:hover {
826396
+ border-color: var(--workspace-border) !important;
826397
+ background: var(--workspace-panel) !important;
826398
+ color: var(--workspace-text) !important;
826399
+ }
826400
+
826401
+ .capability-icon {
826402
+ font-family: var(--font-mono);
826403
+ font-size: 25px;
826404
+ font-weight: 400;
826405
+ line-height: 1;
826406
+ }
826407
+
826408
+ .capability-icon--code { color: var(--workspace-blue); }
826409
+ .capability-icon--image { color: var(--workspace-rose); }
826410
+ .capability-icon--video { color: var(--workspace-cyan); }
826411
+ .capability-icon--document { color: var(--workspace-violet); }
826412
+ .capability-icon--audio { color: var(--workspace-mint); }
826413
+ .capability-icon--explore { color: var(--workspace-gold); }
826414
+
826415
+ .capability-secondary {
826416
+ display: flex;
826417
+ justify-content: center;
826418
+ gap: 24px;
826419
+ padding-top: 12px;
826420
+ pointer-events: auto;
826421
+ }
826422
+
826423
+ .capability-secondary button {
826424
+ border: 0 !important;
826425
+ background: transparent !important;
826426
+ color: var(--workspace-quiet) !important;
826427
+ cursor: pointer;
826428
+ font-size: 11px;
826429
+ }
826430
+
826431
+ .capability-secondary button:hover {
826432
+ color: var(--workspace-text) !important;
826433
+ }
826434
+
826435
+ #footer {
826436
+ z-index: 8;
826437
+ order: 20;
826438
+ width: min(760px, calc(100% - 34px));
826439
+ margin: 0 auto 16px;
826440
+ overflow: visible;
826441
+ border: 1px solid var(--workspace-border) !important;
826442
+ border-radius: 17px;
826443
+ background: var(--workspace-panel) !important;
826444
+ box-shadow: 0 12px 34px rgba(0,0,0,0.19) !important;
826445
+ }
826446
+
826447
+ #processes-pill,
826448
+ #tasks-row,
826449
+ #processes-row {
826450
+ border-color: var(--workspace-border) !important;
826451
+ background: transparent !important;
826452
+ }
826453
+
826454
+ #input-row {
826455
+ order: 10;
826456
+ min-height: 62px;
826457
+ align-items: center;
826458
+ padding: 10px 12px 2px !important;
826459
+ border: 0 !important;
826460
+ background: transparent !important;
826461
+ }
826462
+
826463
+ #input-area {
826464
+ min-height: 42px;
826465
+ max-height: 210px !important;
826466
+ padding: 10px 4px !important;
826467
+ border: 0 !important;
826468
+ background: transparent !important;
826469
+ color: var(--workspace-text) !important;
826470
+ font-size: 15px !important;
826471
+ line-height: 1.45;
826472
+ }
826473
+
826474
+ #input-area::placeholder {
826475
+ color: var(--workspace-quiet);
826476
+ }
826477
+
826478
+ #input-area:focus,
826479
+ #input-area:focus-visible {
826480
+ border: 0 !important;
826481
+ outline: 0 !important;
826482
+ box-shadow: none !important;
826483
+ }
826484
+
826485
+ #input-toolbar {
826486
+ order: 11;
826487
+ min-height: 42px;
826488
+ padding: 2px 12px 10px !important;
826489
+ border: 0 !important;
826490
+ background: transparent !important;
826491
+ }
826492
+
826493
+ #input-toolbar .ibtn,
826494
+ .composer-model-trigger {
826495
+ min-width: 28px;
826496
+ height: 28px;
826497
+ padding: 0 8px;
826498
+ border: 1px solid transparent !important;
826499
+ border-radius: 9px !important;
826500
+ background: var(--workspace-panel-raised) !important;
826501
+ color: var(--workspace-muted) !important;
826502
+ }
826503
+
826504
+ #input-toolbar .ibtn:hover,
826505
+ #input-toolbar .ibtn.active,
826506
+ .composer-model-trigger:hover,
826507
+ .composer-model-trigger[aria-expanded="true"] {
826508
+ border-color: var(--workspace-border) !important;
826509
+ background: var(--workspace-panel-hover) !important;
826510
+ color: var(--workspace-text) !important;
826511
+ }
826512
+
826513
+ #input-model-mini {
826514
+ display: none !important;
826515
+ }
826516
+
826517
+ .composer-model-trigger {
826518
+ display: inline-flex;
826519
+ align-items: center;
826520
+ gap: 7px;
826521
+ cursor: pointer;
826522
+ font-size: 11px;
826523
+ }
826524
+
826525
+ .model-gem {
826526
+ color: #7aa8ff;
826527
+ font-size: 9px;
826528
+ }
826529
+
826530
+ #send-btn,
826531
+ #stop-btn,
826532
+ #checkin-btn,
826533
+ #voice-input-toggle-btn {
826534
+ display: inline-flex;
826535
+ width: 34px;
826536
+ height: 34px;
826537
+ align-items: center;
826538
+ justify-content: center;
826539
+ padding: 0 !important;
826540
+ border: 1px solid var(--workspace-border) !important;
826541
+ border-radius: 50% !important;
826542
+ background: var(--workspace-panel-raised) !important;
826543
+ color: var(--workspace-muted) !important;
826544
+ font-size: 16px !important;
826545
+ }
826546
+
826547
+ #stop-btn,
826548
+ #checkin-btn {
826549
+ width: auto;
826550
+ padding: 0 12px !important;
826551
+ border-radius: 12px !important;
826552
+ font-size: 11px !important;
826553
+ }
826554
+
826555
+ #send-btn:hover,
826556
+ #voice-input-toggle-btn:hover {
826557
+ background: var(--workspace-text) !important;
826558
+ color: var(--workspace-outer) !important;
826559
+ }
826560
+
826561
+ #attach-chips {
826562
+ order: 9;
826563
+ padding: 7px 12px 0 !important;
826564
+ background: transparent !important;
826565
+ }
826566
+
826567
+ #voice-mini-panel {
826568
+ order: 8;
826569
+ border-color: var(--workspace-border) !important;
826570
+ background: var(--workspace-panel) !important;
826571
+ }
826572
+
826573
+ .composer-menu {
826574
+ position: absolute;
826575
+ z-index: 90;
826576
+ bottom: calc(100% + 8px);
826577
+ left: 10px;
826578
+ display: none;
826579
+ width: min(310px, calc(100vw - 34px));
826580
+ padding: 6px;
826581
+ border: 1px solid var(--workspace-border-strong);
826582
+ border-radius: 14px;
826583
+ background: var(--workspace-panel-raised);
826584
+ box-shadow: 0 18px 48px rgba(0,0,0,0.42);
826585
+ }
826586
+
826587
+ .composer-menu.open {
826588
+ display: grid;
826589
+ gap: 2px;
826590
+ }
826591
+
826592
+ .composer-menu--model {
826593
+ right: 10px;
826594
+ left: auto;
826595
+ max-height: min(380px, 55vh);
826596
+ overflow-y: auto;
826597
+ }
826598
+
826599
+ .composer-menu button {
826600
+ display: grid;
826601
+ gap: 3px;
826602
+ width: 100%;
826603
+ padding: 9px 10px;
826604
+ border: 1px solid transparent !important;
826605
+ background: transparent !important;
826606
+ color: var(--workspace-text) !important;
826607
+ cursor: pointer;
826608
+ text-align: left;
826609
+ }
826610
+
826611
+ .composer-menu button:hover,
826612
+ .composer-menu button[aria-checked="true"] {
826613
+ border-color: var(--workspace-border) !important;
826614
+ background: var(--workspace-panel-hover) !important;
826615
+ }
826616
+
826617
+ .composer-menu button small,
826618
+ .composer-menu-empty {
826619
+ color: var(--workspace-quiet);
826620
+ font-size: 10px;
826621
+ }
826622
+
826623
+ .composer-menu-empty {
826624
+ padding: 14px;
826625
+ text-align: center;
826626
+ }
826627
+
826628
+ body[data-chat-empty="true"] #chat-session-topbar {
826629
+ visibility: hidden;
826630
+ }
826631
+
826632
+ body[data-chat-empty="true"] #footer {
826633
+ position: absolute;
826634
+ top: min(34vh, 335px);
826635
+ left: 50%;
826636
+ width: min(680px, calc(100% - 42px));
826637
+ margin: 0;
826638
+ transform: translateX(-50%);
826639
+ }
826640
+
826641
+ body[data-chat-empty="true"] #footer > :is(#processes-pill, #tasks-row, #processes-row) {
826642
+ display: none !important;
826643
+ }
826644
+
826645
+ #workspace-sidebar,
826646
+ #agent-workspace-sidebar {
826647
+ width: min(46%, 640px) !important;
826648
+ padding: 24px !important;
826649
+ border: 0 !important;
826650
+ border-right: 1px solid var(--workspace-border) !important;
826651
+ background: var(--workspace-canvas) !important;
826652
+ }
826653
+
826654
+ .workspace-picker {
826655
+ border-color: var(--workspace-border-strong) !important;
826656
+ border-radius: 14px;
826657
+ background: var(--workspace-panel-raised) !important;
826658
+ box-shadow: 0 20px 56px rgba(0,0,0,0.42);
826659
+ }
826660
+
826661
+ #mobile-topbar,
826662
+ #mobile-bottom-nav,
826663
+ #mobile-more-menu,
826664
+ #mobile-sidebar-backdrop {
826665
+ display: none;
826666
+ }
826667
+
826668
+ @media (max-width: 920px) {
826669
+ .observability-grid {
826670
+ grid-template-columns: repeat(2, minmax(0, 1fr));
826671
+ }
826672
+ .observability-span-2,
826673
+ .observability-span-3 { grid-column: span 1; }
826674
+ .observability-span-4,
826675
+ .observability-span-6 { grid-column: 1 / -1; }
826676
+ .observability-facts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
826677
+ }
826678
+
826679
+ @media (max-width: 767px) {
826680
+ body {
826681
+ padding: 0;
826682
+ }
826683
+
826684
+ #mobile-topbar {
826685
+ position: fixed;
826686
+ z-index: 75;
826687
+ top: 0;
826688
+ right: 0;
826689
+ left: 0;
826690
+ display: grid;
826691
+ grid-template-columns: 42px 1fr 42px;
826692
+ align-items: center;
826693
+ height: 48px;
826694
+ padding: 0 7px;
826695
+ border-bottom: 1px solid var(--workspace-border);
826696
+ background: var(--workspace-canvas);
826697
+ }
826698
+
826699
+ #mobile-route-title {
826700
+ color: var(--workspace-text);
826701
+ font-size: 13px;
826702
+ font-weight: 550;
826703
+ }
826704
+
826705
+ .mobile-icon-btn {
826706
+ display: inline-flex;
826707
+ width: 34px;
826708
+ height: 34px;
826709
+ align-items: center;
826710
+ justify-content: center;
826711
+ padding: 0;
826712
+ border: 0;
826713
+ background: transparent;
826714
+ color: var(--workspace-muted);
826715
+ }
826716
+
826717
+ #mobile-bottom-nav {
826718
+ position: fixed;
826719
+ z-index: 75;
826720
+ right: 0;
826721
+ bottom: 0;
826722
+ left: 0;
826723
+ display: grid;
826724
+ grid-template-columns: 56px 112px 72px;
826725
+ align-items: stretch;
826726
+ height: 58px;
826727
+ padding: 4px 7px;
826728
+ border-top: 1px solid var(--workspace-border);
826729
+ background: var(--workspace-outer);
826730
+ }
826731
+
826732
+ #mobile-bottom-nav button {
826733
+ display: flex;
826734
+ flex-direction: column;
826735
+ align-items: center;
826736
+ justify-content: center;
826737
+ gap: 1px;
826738
+ border: 1px solid transparent !important;
826739
+ background: transparent !important;
826740
+ color: var(--workspace-quiet) !important;
826741
+ }
826742
+
826743
+ #mobile-bottom-nav button.active,
826744
+ #mobile-bottom-nav button[aria-expanded="true"] {
826745
+ border-color: var(--workspace-border) !important;
826746
+ background: var(--workspace-panel) !important;
826747
+ color: var(--workspace-text) !important;
826748
+ }
826749
+
826750
+ #mobile-bottom-nav small {
826751
+ font-size: 9px;
826752
+ }
826753
+
826754
+ #mobile-more-menu {
826755
+ position: fixed;
826756
+ z-index: 84;
826757
+ right: 10px;
826758
+ bottom: 66px;
826759
+ display: none;
826760
+ grid-template-columns: repeat(2, minmax(0, 1fr));
826761
+ gap: 4px;
826762
+ width: min(300px, calc(100vw - 20px));
826763
+ padding: 7px;
826764
+ border: 1px solid var(--workspace-border-strong);
826765
+ border-radius: 14px;
826766
+ background: var(--workspace-panel-raised);
826767
+ box-shadow: 0 20px 50px rgba(0,0,0,0.48);
826768
+ }
826769
+
826770
+ #mobile-more-menu.open { display: grid; }
826771
+ #mobile-more-menu button {
826772
+ min-height: 40px;
826773
+ border: 1px solid transparent !important;
826774
+ background: transparent !important;
826775
+ color: var(--workspace-muted) !important;
826776
+ }
826777
+ #mobile-more-menu button:hover {
826778
+ border-color: var(--workspace-border) !important;
826779
+ background: var(--workspace-panel-hover) !important;
826780
+ color: var(--workspace-text) !important;
826781
+ }
826782
+
826783
+ #mobile-sidebar-backdrop {
826784
+ position: fixed;
826785
+ z-index: 78;
826786
+ inset: 0;
826787
+ background: rgba(0,0,0,0.56);
826788
+ }
826789
+
826790
+ body.mobile-sidebar-open #mobile-sidebar-backdrop { display: block; }
826791
+
826792
+ #omnius-shell {
826793
+ height: 100vh !important;
826794
+ padding: 48px 0 58px;
826795
+ }
826796
+
826797
+ #omnius-main {
826798
+ border: 0 !important;
826799
+ border-radius: 0;
826800
+ box-shadow: none;
826801
+ }
826802
+
826803
+ #header { display: none !important; }
826804
+
826805
+ #omnius-sidebar,
826806
+ #omnius-sidebar[data-collapsed="true"] {
826807
+ position: fixed !important;
826808
+ z-index: 82 !important;
826809
+ top: 0 !important;
826810
+ bottom: 0 !important;
826811
+ left: 0 !important;
826812
+ display: flex !important;
826813
+ width: min(320px, 86vw) !important;
826814
+ padding: 8px !important;
826815
+ transform: translateX(-105%);
826816
+ transition: transform 180ms ease;
826817
+ box-shadow: 18px 0 54px rgba(0,0,0,0.46) !important;
826818
+ }
826819
+
826820
+ body.mobile-sidebar-open #omnius-sidebar,
826821
+ body.mobile-sidebar-open #omnius-sidebar[data-collapsed="true"] {
826822
+ transform: translateX(0);
826823
+ }
826824
+
826825
+ #omnius-sidebar[data-collapsed="true"] .sb-label { display: initial !important; }
826826
+ #omnius-sidebar[data-collapsed="true"] #sidebar-toggle,
826827
+ #omnius-sidebar[data-collapsed="true"] .sidebar-workspace-row,
826828
+ #omnius-sidebar[data-collapsed="true"] #sidebar-new-chat-row,
826829
+ #omnius-sidebar[data-collapsed="true"] #sidebar-search-row,
826830
+ #omnius-sidebar[data-collapsed="true"] #sidebar-chats,
826831
+ #omnius-sidebar[data-collapsed="true"] #sidebar-footer { display: flex !important; }
826832
+ #omnius-sidebar[data-collapsed="true"] #sidebar-search-row,
826833
+ #omnius-sidebar[data-collapsed="true"] #sidebar-chats { display: block !important; }
826834
+ #omnius-sidebar[data-collapsed="true"] #sidebar-nav {
826835
+ display: grid !important;
826836
+ grid-template-columns: repeat(3, minmax(0, 1fr));
826837
+ margin-top: 0;
826838
+ }
826839
+
826840
+ .omnius-route-shell {
826841
+ padding: 14px 12px 20px !important;
826842
+ }
826843
+
826844
+ #chat-container { padding: 0 !important; }
826845
+ .route-heading {
826846
+ grid-template-columns: minmax(0, 1fr);
826847
+ align-items: start;
826848
+ }
826849
+ .route-heading__signals { justify-content: flex-start; }
826850
+ .route-heading__title { font-size: 18px !important; }
826851
+
826852
+ .observability-grid {
826853
+ grid-template-columns: minmax(0, 1fr);
826854
+ gap: 10px;
826855
+ }
826856
+ .observability-span-2,
826857
+ .observability-span-3,
826858
+ .observability-span-4,
826859
+ .observability-span-6 { grid-column: 1; }
826860
+ .observability-facts { grid-template-columns: minmax(0, 1fr); }
826861
+
826862
+ #chat-session-topbar {
826863
+ min-height: 36px;
826864
+ padding: 3px 12px !important;
826865
+ }
826866
+
826867
+ #conversation {
826868
+ width: 100%;
826869
+ padding: 24px 16px 108px !important;
826870
+ }
826871
+
826872
+ #chat-empty-state {
826873
+ padding: 34px 7px 70px;
826874
+ }
826875
+
826876
+ #chat-empty-state h1 {
826877
+ align-self: stretch;
826878
+ padding: 0 62px;
826879
+ font-size: 23px !important;
826880
+ line-height: 1.2;
826881
+ }
826882
+
826883
+ .chat-empty-composer-space {
826884
+ height: 128px;
826885
+ flex-basis: 128px;
826886
+ }
826887
+
826888
+ .capability-grid {
826889
+ grid-template-columns: repeat(3, minmax(0, 1fr));
826890
+ gap: 6px;
826891
+ width: 100%;
826892
+ }
826893
+
826894
+ .capability-grid button {
826895
+ min-height: 64px;
826896
+ font-size: 10px;
826897
+ }
826898
+
826899
+ .capability-icon { font-size: 21px; }
826900
+ .capability-secondary {
826901
+ align-self: stretch;
826902
+ justify-content: space-around;
826903
+ gap: 4px;
826904
+ padding: 10px 10px 0;
826905
+ }
826906
+
826907
+ #footer {
826908
+ width: calc(100% - 14px);
826909
+ margin-bottom: 8px;
826910
+ border-radius: 16px;
826911
+ }
826912
+
826913
+ body[data-chat-empty="true"] #footer {
826914
+ top: 84px;
826915
+ width: calc(100% - 14px);
826916
+ }
826917
+
826918
+ #input-row {
826919
+ min-height: 58px;
826920
+ padding: 7px 9px 0 !important;
826921
+ }
826922
+
826923
+ #input-area {
826924
+ min-height: 44px;
826925
+ font-size: 14px !important;
826926
+ }
826927
+
826928
+ #input-toolbar {
826929
+ padding: 0 9px 8px !important;
826930
+ }
826931
+
826932
+ #sys-btn,
826933
+ #context-btn,
826934
+ #voice-input-toggle-btn {
826935
+ display: none !important;
826936
+ }
826937
+
826938
+ .composer-menu {
826939
+ position: fixed;
826940
+ right: 7px;
826941
+ bottom: 66px;
826942
+ left: 7px;
826943
+ width: auto;
826944
+ }
826945
+
826946
+ #workspace-sidebar,
826947
+ #agent-workspace-sidebar {
826948
+ position: absolute !important;
826949
+ z-index: 40;
826950
+ inset: 0 !important;
826951
+ width: 100% !important;
826952
+ padding: 18px !important;
826953
+ border-right: 0 !important;
826954
+ background: var(--workspace-canvas) !important;
826955
+ }
826956
+
826957
+ .msg.user > * { max-width: 88%; }
826958
+ .msg.assistant { padding-left: 38px; }
826959
+ }
826960
+
824870
826961
  @media (prefers-reduced-motion: reduce) {
824871
826962
  *, *::before, *::after {
824872
826963
  animation-duration: 0.01ms !important;
@@ -824877,6 +826968,17 @@ html { scrollbar-color: var(--nclp-line-strong) var(--nclp-bg-main); }
824877
826968
  </head>
824878
826969
  <body>
824879
826970
 
826971
+ <div id="mobile-sidebar-backdrop" onclick="closeMobileSidebar()" aria-hidden="true"></div>
826972
+ <header id="mobile-topbar">
826973
+ <button type="button" class="mobile-icon-btn" onclick="openMobileSidebar()" aria-label="Open navigation">
826974
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M4 6h16M4 12h16M4 18h16"/></svg>
826975
+ </button>
826976
+ <span id="mobile-route-title">Chat</span>
826977
+ <button type="button" class="mobile-icon-btn" onclick="toggleWorkspace()" aria-label="Open workspace files">
826978
+ <svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="4" y="5" width="16" height="14" rx="2"/><path d="M8 5v3h8V5"/></svg>
826979
+ </button>
826980
+ </header>
826981
+
824880
826982
  <!-- ════════════════════════════════════════════════════════════════
824881
826983
  OWUI-2: Open WebUI-shaped sidebar layout
824882
826984
  ────────────────────────────────────────────────────────────────
@@ -824890,11 +826992,18 @@ html { scrollbar-color: var(--nclp-line-strong) var(--nclp-bg-main); }
824890
826992
  <div id="omnius-shell" style="display:flex;flex:1;overflow:hidden;height:100vh">
824891
826993
 
824892
826994
  <aside id="omnius-sidebar" data-collapsed="false" style="width:260px;flex-shrink:0;background:var(--color-bg-elevated);border-right:1px solid var(--color-border);display:flex;flex-direction:column;position:relative;transition:width 0.18s ease">
824893
- <!-- Brand + collapse toggle -->
824894
- <div style="padding:14px 12px 8px;display:flex;align-items:center;gap:10px">
824895
- <button id="sidebar-toggle" onclick="toggleSidebar()" title="Toggle sidebar (Cmd/Ctrl+B)" style="background:transparent;border:none;color:var(--color-fg-muted);padding:4px;border-radius:var(--radius-sm);cursor:pointer;display:flex;align-items:center;justify-content:center;width:28px;height:28px">
824896
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M9 3v18"/></svg>
826995
+ <div class="sidebar-mode-row">
826996
+ <button type="button" class="sidebar-mode-button" onclick="switchTab('chat')" aria-label="Open chat">
826997
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M21 15a2 2 0 0 1-2 2H8l-5 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/><path d="M8 9h8M8 13h5"/></svg>
826998
+ <span class="sb-label">Chat</span>
824897
826999
  </button>
827000
+ <button id="sidebar-toggle" onclick="toggleSidebar()" title="Toggle sidebar (Cmd/Ctrl+B)" aria-label="Toggle sidebar">
827001
+ <svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="3" width="18" height="18" rx="3"/><path d="M9 3v18"/></svg>
827002
+ </button>
827003
+ </div>
827004
+
827005
+ <!-- Workspace selector -->
827006
+ <div class="sidebar-workspace-row">
824898
827007
  <div class="workspace-switcher">
824899
827008
  <button id="workspace-switcher-trigger" class="workspace-switcher__trigger" type="button" aria-haspopup="dialog" aria-expanded="false" aria-controls="workspace-picker" onclick="toggleWorkspacePicker(event)" title="Switch workspace">
824900
827009
  <span id="sidebar-brand-dot" title="Connection status" style="width:9px;height:9px;border-radius:999px;background:#f59e0b;box-shadow:0 0 0 2px rgba(245,158,11,.16);flex:0 0 auto"></span>
@@ -824914,15 +827023,15 @@ html { scrollbar-color: var(--nclp-line-strong) var(--nclp-bg-main); }
824914
827023
  </div>
824915
827024
 
824916
827025
  <!-- New chat button -->
824917
- <div id="sidebar-new-chat-row" style="padding:0 12px 10px">
824918
- <button onclick="newChatSession(); switchTab('chat')" title="New chat (Cmd/Ctrl+Shift+O)" style="width:100%;background:var(--color-bg-input);border:1px solid var(--color-border);color:var(--color-fg);padding:8px 12px;border-radius:var(--radius-md);cursor:pointer;display:flex;align-items:center;gap:10px;font-size:0.82rem;font-weight:500;transition:background 0.12s">
827026
+ <div id="sidebar-new-chat-row">
827027
+ <button onclick="newChatSession(); switchTab('chat')" title="New chat (Cmd/Ctrl+Shift+O)">
824919
827028
  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14"/><path d="M5 12h14"/></svg>
824920
- <span class="sb-label">New chat</span>
827029
+ <span class="sb-label">Create new</span>
824921
827030
  </button>
824922
827031
  </div>
824923
827032
 
824924
827033
  <!-- Search bar -->
824925
- <div id="sidebar-search-row" style="padding:0 12px 8px">
827034
+ <div id="sidebar-search-row">
824926
827035
  <div style="position:relative">
824927
827036
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="position:absolute;left:10px;top:50%;transform:translateY(-50%);color:var(--color-fg-faint);pointer-events:none"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
824928
827037
  <input id="sidebar-search" placeholder="Search chats..." class="sb-label" oninput="filterSidebarChats(this.value)" style="width:100%;background:var(--color-bg);border:1px solid var(--color-border);color:var(--color-fg);padding:6px 10px 6px 32px;border-radius:var(--radius-md);font-family:var(--font-ui);font-size:0.78rem;outline:none">
@@ -825004,6 +827113,10 @@ html { scrollbar-color: var(--nclp-line-strong) var(--nclp-bg-main); }
825004
827113
  <div id="omnius-main" style="flex:1;display:flex;flex-direction:column;min-width:0;overflow:hidden;background:var(--color-bg)">
825005
827114
 
825006
827115
  <div id="header">
827116
+ <div class="header-route-copy">
827117
+ <span class="header-route-kicker">Omnius</span>
827118
+ <strong id="desktop-route-title">Chat</strong>
827119
+ </div>
825007
827120
  <span class="status" id="status" aria-live="polite" style="display:none">connecting...</span>
825008
827121
  <span id="update-btn" style="display:none;background:#0f3f3d;border:1px solid var(--color-brand);color:var(--color-brand);padding:2px 8px;border-radius:3px;font-family:inherit;font-size:0.6rem;cursor:pointer" onclick="doUpdate()">update</span>
825009
827122
  <select id="model-select"><option>loading...</option></select>
@@ -825073,6 +827186,23 @@ html { scrollbar-color: var(--nclp-line-strong) var(--nclp-bg-main); }
825073
827186
  <div style="font-size:0.6rem;color:var(--color-brand);text-transform:uppercase;letter-spacing:0.08em;margin-bottom:4px">Plan</div>
825074
827187
  <ul id="todo-list" style="list-style:none;margin:0;padding:0;font-size:0.78rem"></ul>
825075
827188
  </div>
827189
+ <section id="chat-empty-state" aria-label="Start a new Omnius task">
827190
+ <h1>What are we doing today?</h1>
827191
+ <div class="chat-empty-composer-space" aria-hidden="true"></div>
827192
+ <div class="capability-grid" aria-label="Omnius capabilities">
827193
+ <button type="button" data-capability="code" onclick="launchCapability('code')"><span class="capability-icon capability-icon--code">&lt;/&gt;</span><span>Code</span></button>
827194
+ <button type="button" data-capability="images" onclick="launchCapability('images')"><span class="capability-icon capability-icon--image">▧</span><span>Images</span></button>
827195
+ <button type="button" data-capability="video" onclick="launchCapability('video')"><span class="capability-icon capability-icon--video">▻</span><span>Video</span></button>
827196
+ <button type="button" data-capability="documents" onclick="launchCapability('documents')"><span class="capability-icon capability-icon--document">▤</span><span>Documents</span></button>
827197
+ <button type="button" data-capability="audio" onclick="launchCapability('audio')"><span class="capability-icon capability-icon--audio">◉</span><span>Audio</span></button>
827198
+ <button type="button" data-capability="explore" onclick="launchCapability('explore')"><span class="capability-icon capability-icon--explore">⌘</span><span>Explore</span></button>
827199
+ </div>
827200
+ <div class="capability-secondary" aria-label="More capabilities">
827201
+ <button type="button" onclick="launchCapability('editor')">Editor</button>
827202
+ <button type="button" onclick="launchCapability('research')">Research</button>
827203
+ <button type="button" onclick="launchCapability('automate')">Automate</button>
827204
+ </div>
827205
+ </section>
825076
827206
  <div id="conversation" style="flex:1;overflow-y:auto;padding:12px 16px;display:flex;flex-direction:column;gap:4px;min-height:0"></div>
825077
827207
  </div>
825078
827208
  </div>
@@ -825582,7 +827712,7 @@ html { scrollbar-color: var(--nclp-line-strong) var(--nclp-bg-main); }
825582
827712
  stays focused on text + send/stop only.
825583
827713
  ════════════════════════════════════════════════════════════ -->
825584
827714
  <div id="input-toolbar">
825585
- <button class="ibtn" id="attach-btn" title="Attach files" onclick="document.getElementById('attach-input').click()">
827715
+ <button class="ibtn" id="attach-btn" title="Add context or media" aria-haspopup="menu" aria-expanded="false" onclick="toggleComposerMenu('attach', event)">
825586
827716
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>
825587
827717
  </button>
825588
827718
  <input type="file" id="attach-input" multiple style="display:none" onchange="handleAttachInput(this.files); this.value=''">
@@ -825599,9 +827729,14 @@ html { scrollbar-color: var(--nclp-line-strong) var(--nclp-bg-main); }
825599
827729
  sys
825600
827730
  </button>
825601
827731
 
827732
+ <button class="ibtn composer-context-btn" id="context-btn" title="Add workspace context" onclick="toggleWorkspace()">@</button>
827733
+
825602
827734
  <span class="ibtn-spacer" style="flex:1"></span>
825603
827735
 
825604
- <select id="input-model-mini" class="ibtn-model" onchange="if(this.value){const m=document.getElementById('model-select');if(m){m.value=this.value;m.dispatchEvent(new Event('change'));}}" title="Model"></select>
827736
+ <select id="input-model-mini" class="ibtn-model" onchange="if(this.value){const m=document.getElementById('model-select');if(m){m.value=this.value;m.dispatchEvent(new Event('change'));}}" title="Model" aria-label="Model"></select>
827737
+ <button type="button" id="composer-model-trigger" class="composer-model-trigger" aria-haspopup="menu" aria-expanded="false" onclick="toggleComposerMenu('model', event)">
827738
+ <span class="model-gem" aria-hidden="true">◆</span><span id="composer-model-label">Model</span><span aria-hidden="true">⌄</span>
827739
+ </button>
825605
827740
  </div>
825606
827741
 
825607
827742
  <!-- Attachments chip row (populated by handleAttachInput) -->
@@ -825612,6 +827747,14 @@ html { scrollbar-color: var(--nclp-line-strong) var(--nclp-bg-main); }
825612
827747
  <div id="slash-palette-list"></div>
825613
827748
  </div>
825614
827749
 
827750
+ <div id="composer-attach-menu" class="composer-menu" role="menu" aria-label="Add to this message">
827751
+ <button type="button" role="menuitem" onclick="document.getElementById('attach-input').click(); closeComposerMenus()"><span>Upload files</span><small>Images, audio, video, documents</small></button>
827752
+ <button type="button" role="menuitem" onclick="toggleWorkspace(); closeComposerMenus()"><span>Workspace files</span><small>Add files from the active project</small></button>
827753
+ <button type="button" role="menuitem" onclick="launchCapability('images'); closeComposerMenus()"><span>Create an image</span><small>Open media generation</small></button>
827754
+ <button type="button" role="menuitem" onclick="launchCapability('audio'); closeComposerMenus()"><span>Audio and voice</span><small>Generate, attach, or transcribe audio</small></button>
827755
+ </div>
827756
+ <div id="composer-model-menu" class="composer-menu composer-menu--model" role="menu" aria-label="Choose a model"></div>
827757
+
825615
827758
  <!-- Voice panel — collapsible TTS speak + ASR mic record. Toggle from
825616
827759
  the mic button on the input row. The panel hits /v1/voice/start
825617
827760
  to warm the engines on first open, /v1/voice/tts to synthesize
@@ -825637,9 +827780,9 @@ html { scrollbar-color: var(--nclp-line-strong) var(--nclp-bg-main); }
825637
827780
  <!-- Lower sub-row: existing input + send/stop -->
825638
827781
  <div id="input-row">
825639
827782
  <span id="system-prompt-toggle" onclick="toggleSystemPrompt()" style="display:none">sys</span>
825640
- <textarea id="input-area" placeholder="Type a message..." rows="1"></textarea>
825641
- <button id="voice-input-toggle-btn" type="button" onclick="toggleVoicePanel()" title="open voice panel" style="background:var(--color-bg-input);border:1px solid var(--color-border);color:var(--color-fg-muted);padding:10px 12px;border-radius:3px;font-family:inherit;font-size:0.75rem;cursor:pointer;flex-shrink:0">🎙</button>
825642
- <button id="send-btn" onclick="sendMessage()">send</button>
827783
+ <textarea id="input-area" placeholder="Ask Omnius anything" rows="1"></textarea>
827784
+ <button id="voice-input-toggle-btn" type="button" onclick="toggleVoicePanel()" title="Open voice panel" aria-label="Open voice panel" style="background:var(--color-bg-input);border:1px solid var(--color-border);color:var(--color-fg-muted);padding:10px 12px;border-radius:3px;font-family:inherit;font-size:0.75rem;cursor:pointer;flex-shrink:0"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><rect x="9" y="2" width="6" height="12" rx="3"/><path d="M5 10a7 7 0 0 0 14 0M12 17v5M8 22h8"/></svg></button>
827785
+ <button id="send-btn" onclick="sendMessage()" title="Send message" aria-label="Send message">↑</button>
825643
827786
  <button id="stop-btn" onclick="stopChat()" style="display:none;background:var(--color-bg-input);border:1px solid var(--color-error);color:var(--color-error);padding:10px 16px;border-radius:3px;font-family:inherit;font-size:0.75rem;cursor:pointer;flex-shrink:0">stop</button>
825644
827787
  <!-- WO-CHAT-CHECKIN — teal accent button that takes the place of stop
825645
827788
  when the user starts typing during an active run. Click sends a
@@ -825650,6 +827793,22 @@ html { scrollbar-color: var(--nclp-line-strong) var(--nclp-bg-main); }
825650
827793
  </div>
825651
827794
  </div>
825652
827795
 
827796
+ <nav id="mobile-bottom-nav" aria-label="Primary navigation">
827797
+ <button type="button" onclick="newChatSession(); switchTab('chat')" aria-label="New chat"><span>+</span><small>New</small></button>
827798
+ <button type="button" data-mobile-tab="chat" onclick="switchTab('chat')" class="active"><span>▢</span><small>Chat</small></button>
827799
+ <button type="button" id="mobile-more-trigger" onclick="toggleMobileMore(event)" aria-haspopup="menu" aria-expanded="false"><span>•••</span><small>More</small></button>
827800
+ </nav>
827801
+ <div id="mobile-more-menu" role="menu" aria-label="More Omnius areas">
827802
+ <button type="button" role="menuitem" onclick="switchTab('agent'); closeMobileMore()">Agent</button>
827803
+ <button type="button" role="menuitem" onclick="switchTab('generate'); closeMobileMore()">Generate</button>
827804
+ <button type="button" role="menuitem" onclick="switchTab('voice'); closeMobileMore()">Voice</button>
827805
+ <button type="button" role="menuitem" onclick="switchTab('projects'); closeMobileMore()">Projects</button>
827806
+ <button type="button" role="menuitem" onclick="switchTab('jobs'); closeMobileMore()">Dashboard</button>
827807
+ <button type="button" role="menuitem" onclick="switchTab('activity'); closeMobileMore()">Activity</button>
827808
+ <button type="button" role="menuitem" onclick="switchTab('discover'); closeMobileMore()">Discover</button>
827809
+ <button type="button" role="menuitem" onclick="switchTab('config'); closeMobileMore()">Settings</button>
827810
+ </div>
827811
+
825653
827812
  <div id="key-modal">
825654
827813
  <form class="modal" onsubmit="event.preventDefault(); saveKey();" autocomplete="off" style="min-width:480px">
825655
827814
  <h3>API Key &amp; Sharing</h3>
@@ -826209,9 +828368,17 @@ window.__omniusStores = {
826209
828368
 
826210
828369
  const conv = document.getElementById('conversation');
826211
828370
  const input = document.getElementById('input-area');
828371
+ function syncChatEmptyState() {
828372
+ const empty = document.getElementById('chat-empty-state');
828373
+ const hasMessages = !!conv?.querySelector('.msg');
828374
+ document.body.dataset.chatEmpty = hasMessages ? 'false' : 'true';
828375
+ if (empty) empty.hidden = hasMessages;
828376
+ }
828377
+ if (conv) new MutationObserver(syncChatEmptyState).observe(conv, { childList: true, subtree: false });
828378
+ syncChatEmptyState();
826212
828379
  // SUGGESTED FOLLOW-UP — ghost text (placeholder-grey) shown after a reply
826213
828380
  // completes; Tab accepts it as real text, typing dismisses it.
826214
- const DEFAULT_INPUT_PLACEHOLDER = 'Type a message...';
828381
+ const DEFAULT_INPUT_PLACEHOLDER = 'Ask Omnius anything';
826215
828382
  let chatFollowup = null;
826216
828383
  function setFollowupSuggestion(text) {
826217
828384
  text = (text || '').trim();
@@ -826751,6 +828918,7 @@ function addMessage(role, content) {
826751
828918
  appendAssistantActions(div, () => host.innerText || host.textContent || content || '');
826752
828919
  }
826753
828920
  conv.appendChild(div);
828921
+ syncChatEmptyState();
826754
828922
  // OWUI-3: schedule syntax highlight for any new code blocks.
826755
828923
  try { _highlightCodeBlocks(div); } catch {}
826756
828924
  maybeAutoScroll();
@@ -828489,6 +830657,60 @@ function syncRouteForTab(tab, replace) {
828489
830657
  const method = replace ? 'replaceState' : 'pushState';
828490
830658
  try { history[method]({}, '', next); } catch {}
828491
830659
  }
830660
+ const routeDisplayNames = {
830661
+ chat: 'Chat', agent: 'Agent', voice: 'Voice', generate: 'Generate',
830662
+ projects: 'Projects', jobs: 'Dashboard', activity: 'Activity',
830663
+ discover: 'Discover', config: 'Settings',
830664
+ };
830665
+ function openMobileSidebar() {
830666
+ document.body.classList.add('mobile-sidebar-open');
830667
+ document.getElementById('mobile-sidebar-backdrop')?.setAttribute('aria-hidden', 'false');
830668
+ }
830669
+ function closeMobileSidebar() {
830670
+ document.body.classList.remove('mobile-sidebar-open');
830671
+ document.getElementById('mobile-sidebar-backdrop')?.setAttribute('aria-hidden', 'true');
830672
+ }
830673
+ function toggleMobileMore(event) {
830674
+ event?.stopPropagation();
830675
+ const menu = document.getElementById('mobile-more-menu');
830676
+ const trigger = document.getElementById('mobile-more-trigger');
830677
+ if (!menu || !trigger) return;
830678
+ const open = menu.classList.toggle('open');
830679
+ trigger.setAttribute('aria-expanded', open ? 'true' : 'false');
830680
+ }
830681
+ function closeMobileMore() {
830682
+ document.getElementById('mobile-more-menu')?.classList.remove('open');
830683
+ document.getElementById('mobile-more-trigger')?.setAttribute('aria-expanded', 'false');
830684
+ }
830685
+ function launchCapability(kind) {
830686
+ closeComposerMenus();
830687
+ if (kind === 'images' || kind === 'video' || kind === 'audio') {
830688
+ switchTab('generate');
830689
+ setTimeout(() => { try { genSelectMode(kind === 'images' ? 'image' : kind); } catch {} }, 0);
830690
+ return;
830691
+ }
830692
+ if (kind === 'documents') {
830693
+ document.getElementById('attach-input')?.click();
830694
+ return;
830695
+ }
830696
+ if (kind === 'explore') {
830697
+ toggleWorkspace();
830698
+ return;
830699
+ }
830700
+ if (kind === 'research') {
830701
+ switchTab('discover');
830702
+ setTimeout(() => document.getElementById('discovery-query')?.focus(), 0);
830703
+ return;
830704
+ }
830705
+ switchTab('agent');
830706
+ const task = document.getElementById('agent-task');
830707
+ if (task && !task.value) {
830708
+ if (kind === 'code') task.placeholder = 'Describe the code change, review, or investigation...';
830709
+ if (kind === 'editor') task.placeholder = 'Describe what you want to create or edit...';
830710
+ if (kind === 'automate') task.placeholder = 'Describe the workflow you want Omnius to automate...';
830711
+ }
830712
+ setTimeout(() => task?.focus(), 0);
830713
+ }
828492
830714
  function switchTab(tab, opts) {
828493
830715
  opts = opts || {};
828494
830716
  const route = webUiRouteSpecs.find(candidate => candidate.tab === tab) || webUiRouteSpecs[0];
@@ -828517,6 +830739,16 @@ function switchTab(tab, opts) {
828517
830739
  else b.removeAttribute('aria-current');
828518
830740
  });
828519
830741
  if ($activeTab.get && $activeTab.get() !== tab) $activeTab.set(tab);
830742
+ const routeTitle = routeDisplayNames[tab] || 'Omnius';
830743
+ const desktopTitle = document.getElementById('desktop-route-title');
830744
+ const mobileTitle = document.getElementById('mobile-route-title');
830745
+ if (desktopTitle) desktopTitle.textContent = routeTitle;
830746
+ if (mobileTitle) mobileTitle.textContent = routeTitle;
830747
+ document.querySelectorAll('#mobile-bottom-nav [data-mobile-tab]').forEach(button => {
830748
+ button.classList.toggle('active', button.getAttribute('data-mobile-tab') === tab);
830749
+ });
830750
+ closeMobileMore();
830751
+ closeMobileSidebar();
828520
830752
  if (!opts.fromRoute) syncRouteForTab(tab, !!opts.replaceRoute);
828521
830753
  if (tab === 'jobs') loadJobs();
828522
830754
  if (tab === 'agent') {
@@ -834540,6 +836772,62 @@ function toggleVoiceMicFromInput() {
834540
836772
  if (btn) btn.classList.toggle('active');
834541
836773
  }
834542
836774
 
836775
+ function closeComposerMenus() {
836776
+ document.querySelectorAll('.composer-menu.open').forEach(menu => menu.classList.remove('open'));
836777
+ document.getElementById('attach-btn')?.setAttribute('aria-expanded', 'false');
836778
+ document.getElementById('composer-model-trigger')?.setAttribute('aria-expanded', 'false');
836779
+ }
836780
+
836781
+ function toggleComposerMenu(kind, event) {
836782
+ event?.stopPropagation();
836783
+ const id = kind === 'model' ? 'composer-model-menu' : 'composer-attach-menu';
836784
+ const target = document.getElementById(id);
836785
+ if (!target) return;
836786
+ const willOpen = !target.classList.contains('open');
836787
+ closeComposerMenus();
836788
+ if (!willOpen) return;
836789
+ if (kind === 'model') renderComposerModelMenu();
836790
+ target.classList.add('open');
836791
+ document.getElementById(kind === 'model' ? 'composer-model-trigger' : 'attach-btn')?.setAttribute('aria-expanded', 'true');
836792
+ }
836793
+
836794
+ function renderComposerModelMenu() {
836795
+ const source = document.getElementById('model-select');
836796
+ const menu = document.getElementById('composer-model-menu');
836797
+ const label = document.getElementById('composer-model-label');
836798
+ if (!source || !menu) return;
836799
+ menu.replaceChildren();
836800
+ const selected = source.options[source.selectedIndex];
836801
+ if (label) label.textContent = selected?.textContent || source.value || 'Model';
836802
+ for (const option of source.options) {
836803
+ if (!option.value) continue;
836804
+ const button = document.createElement('button');
836805
+ button.type = 'button';
836806
+ button.setAttribute('role', 'menuitemradio');
836807
+ button.setAttribute('aria-checked', option.value === source.value ? 'true' : 'false');
836808
+ const name = document.createElement('span');
836809
+ name.textContent = option.textContent || option.value;
836810
+ const marker = document.createElement('small');
836811
+ marker.textContent = option.value === source.value ? 'Selected' : 'Use model';
836812
+ button.append(name, marker);
836813
+ button.addEventListener('click', () => {
836814
+ source.value = option.value;
836815
+ source.dispatchEvent(new Event('change'));
836816
+ const mirror = document.getElementById('input-model-mini');
836817
+ if (mirror) mirror.value = option.value;
836818
+ if (label) label.textContent = option.textContent || option.value;
836819
+ closeComposerMenus();
836820
+ });
836821
+ menu.appendChild(button);
836822
+ }
836823
+ if (!menu.children.length) {
836824
+ const empty = document.createElement('div');
836825
+ empty.className = 'composer-menu-empty';
836826
+ empty.textContent = 'No models are available';
836827
+ menu.appendChild(empty);
836828
+ }
836829
+ }
836830
+
834543
836831
  // Model picker mirror — keep #input-model-mini in sync with the legacy
834544
836832
  // #model-select so OWUI-4's compact picker reflects the truth.
834545
836833
  (function _mirrorModelPicker() {
@@ -834557,6 +836845,7 @@ function toggleVoiceMicFromInput() {
834557
836845
  }
834558
836846
  }
834559
836847
  dst.value = src.value;
836848
+ renderComposerModelMenu();
834560
836849
  };
834561
836850
  // Run after every load + on a small interval.
834562
836851
  setTimeout(sync, 600);
@@ -834570,6 +836859,12 @@ document.addEventListener('click', (e) => {
834570
836859
  if (!p.contains(e.target) && e.target !== input && e.target.id !== 'slash-btn') {
834571
836860
  p.style.display = 'none';
834572
836861
  }
836862
+ if (!e.target.closest?.('.composer-menu') && !e.target.closest?.('#attach-btn') && !e.target.closest?.('#composer-model-trigger')) {
836863
+ closeComposerMenus();
836864
+ }
836865
+ if (!e.target.closest?.('#mobile-more-menu') && !e.target.closest?.('#mobile-more-trigger')) {
836866
+ closeMobileMore();
836867
+ }
834573
836868
  });
834574
836869
 
834575
836870
  input.focus();
@@ -836819,7 +839114,7 @@ import {
836819
839114
  readdirSync as readdirSync66,
836820
839115
  existsSync as existsSync194,
836821
839116
  watch as fsWatch5,
836822
- renameSync as renameSync32,
839117
+ renameSync as renameSync33,
836823
839118
  unlinkSync as unlinkSync47,
836824
839119
  statSync as statSync78,
836825
839120
  openSync as openSync12,
@@ -838777,7 +841072,7 @@ function atomicJobWrite(dir, id2, job) {
838777
841072
  const tmpPath = `${finalPath}.tmp.${process.pid}.${Date.now()}`;
838778
841073
  try {
838779
841074
  writeFileSync112(tmpPath, JSON.stringify(job, null, 2), "utf-8");
838780
- renameSync32(tmpPath, finalPath);
841075
+ renameSync33(tmpPath, finalPath);
838781
841076
  } catch {
838782
841077
  try {
838783
841078
  writeFileSync112(finalPath, JSON.stringify(job, null, 2), "utf-8");
@@ -841065,7 +843360,7 @@ function writeUpdateState2(state5) {
841065
843360
  const finalPath = updateStateFile();
841066
843361
  const tmpPath = `${finalPath}.tmp.${process.pid}`;
841067
843362
  writeFileSync112(tmpPath, JSON.stringify(state5, null, 2), "utf-8");
841068
- renameSync32(tmpPath, finalPath);
843363
+ renameSync33(tmpPath, finalPath);
841069
843364
  } catch {
841070
843365
  }
841071
843366
  }