copilot-tap-extension 2.0.6 → 2.0.8

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.
@@ -4568,12 +4568,12 @@ function createEmitterTools(deps) {
4568
4568
  properties: {
4569
4569
  name: { type: "string", description: "Unique emitter name." },
4570
4570
  command: { type: "string", description: "Shell command to run (creates a CommandEmitter). Output goes through EventFilter rules to determine whether lines are kept, surfaced, or injected. Use for log tailing, process monitoring, or any external command with stdout." },
4571
- prompt: { type: "string", description: "Prompt to send to the agent (creates a PromptEmitter). Always injects \u2014 bypasses EventFilter entirely. Use for repeated agent tasks, status checks, or simple messages." },
4571
+ prompt: { type: "string", description: "Prompt to send to the agent (creates a PromptEmitter). Always injects \u2014 bypasses EventFilter entirely. Use for repeated agent tasks, status checks, simple messages, or autonomous goal loops." },
4572
4572
  description: { type: "string", description: "Short summary." },
4573
4573
  channel: { type: "string", description: "EventStream to receive accepted events." },
4574
4574
  cwd: { type: "string", description: "Optional subdirectory relative to the session cwd. Absolute paths and paths that escape the session cwd are rejected." },
4575
- every: { type: "string", description: "Optional repeat interval like 30s, 5m, 2h, or 1d (maximum about 24 days). Use 'idle' for prompts that re-run whenever the session is idle. When omitted, commands run continuously and prompts run once." },
4576
- everySchedule: { type: "array", minItems: 1, items: { type: "string" }, description: "Optional backoff schedule \u2014 an ordered non-empty list of interval strings (e.g. ['10s','20s','30s','1m','2m','5m','10m']; each maximum about 24 days). The emitter uses each interval in sequence, then repeats the last one forever. Overrides 'every' when provided. Cannot be 'idle' entries." },
4575
+ every: { type: "string", description: "Optional repeat interval like 30s, 5m, 2h, or 1d (maximum about 24 days). Use 'idle' for conservative prompt loops that re-run only when the session is idle. When omitted, commands run continuously and prompts run once." },
4576
+ everySchedule: { type: "array", minItems: 1, items: { type: "string" }, description: "Optional backoff schedule \u2014 an ordered non-empty list of interval strings (e.g. ['10s','20s','30s','1m','2m','5m','10m']; each maximum about 24 days). The emitter uses each interval in sequence, then repeats the last one forever. Overrides 'every' when provided. Cannot be 'idle' entries. Prefer this for autopilot-compatible goal loops that may need timed nudges while the session stays busy." },
4577
4577
  lifespan: policyLifespanParameter(),
4578
4578
  ownership: policyOwnershipParameter(),
4579
4579
  scope: policyScopeParameter(),
@@ -4583,7 +4583,7 @@ function createEmitterTools(deps) {
4583
4583
  eventFilter: EVENT_FILTER_PARAMETER_SCHEMA,
4584
4584
  subscribe: { type: "boolean", description: "Whether to attach a session injector to the stream as part of emitter creation." },
4585
4585
  delivery: { type: "string", description: "Session injector delivery mode. 'important'/'inject' only send inject-outcome lines, 'surface' surfaces surface outcomes and sends inject outcomes, 'all' surfaces all accepted lines while inject outcomes still push into the conversation, and 'keep'/'drop' store without proactive delivery." },
4586
- maxRuns: { type: "integer", description: "Maximum number of iterations before the emitter auto-completes. Useful for idle and timed loops." },
4586
+ maxRuns: { type: "integer", description: "Maximum number of real iterations before the emitter stops. Useful for idle and timed loops. For goal loops this is a budget limit, not proof that the objective is complete." },
4587
4587
  force: policyForceParameter("emitter")
4588
4588
  },
4589
4589
  required: ["name"]
@@ -4654,12 +4654,29 @@ function renderCanvasOpenResult(result) {
4654
4654
  result?.availability ? `availability=${result.availability}` : null
4655
4655
  ].filter(Boolean).join("\n");
4656
4656
  }
4657
+ function summarizeRuntimeState(state) {
4658
+ const mode = state?.mode?.ok ? state.mode.value : `unavailable (${state?.mode?.error ?? "unknown"})`;
4659
+ const model = state?.model?.ok ? state.model.value : null;
4660
+ const taskCount = state?.tasks?.ok ? state.tasks.value?.tasks?.length ?? 0 : null;
4661
+ const scheduleCount = state?.schedules?.ok ? state.schedules.value?.entries?.length ?? 0 : null;
4662
+ const canvasCount = state?.openCanvases?.ok ? state.openCanvases.value?.openCanvases?.length ?? 0 : null;
4663
+ return [
4664
+ `sessionId=${state?.sessionId ?? "(none)"}`,
4665
+ `mode=${typeof mode === "string" ? mode : JSON.stringify(mode)}`,
4666
+ model ? `model=${model.modelId ?? "unknown"} reasoning=${model.reasoningEffort ?? "default"} context=${model.contextTier ?? "default"}` : null,
4667
+ taskCount !== null ? `tasks=${taskCount}` : null,
4668
+ scheduleCount !== null ? `schedules=${scheduleCount}` : null,
4669
+ canvasCount !== null ? `openCanvases=${canvasCount}` : null,
4670
+ `elicitation=${state?.capabilities?.ui?.elicitation === true ? "available" : "unavailable"}`,
4671
+ `canvases=${state?.capabilities?.ui?.canvases === true ? "available" : "host-gated"}`
4672
+ ].filter(Boolean).join("\n");
4673
+ }
4657
4674
  function createDiagnosticsTools(deps) {
4658
4675
  const diagnostics2 = deps.diagnostics ?? deps.runtime;
4659
4676
  if (!diagnostics2 || typeof diagnostics2.openCanvas !== "function") {
4660
4677
  return [];
4661
4678
  }
4662
- return [
4679
+ const tools = [
4663
4680
  {
4664
4681
  name: "tap_open_diagnostics_canvas",
4665
4682
  description: "Opens or focuses the Tap diagnostics canvas, a live flight recorder for streams, emitters, providers, logs, injection queues, and session events.",
@@ -4684,6 +4701,105 @@ function createDiagnosticsTools(deps) {
4684
4701
  })
4685
4702
  }
4686
4703
  ];
4704
+ if (typeof diagnostics2.getSessionRuntimeState === "function") {
4705
+ tools.push({
4706
+ name: "tap_get_session_state",
4707
+ description: "Reads current Copilot session runtime state for mode-aware tap workflows: mode, model, tasks, schedules, open canvases, and UI capabilities. This is read-only.",
4708
+ parameters: {
4709
+ type: "object",
4710
+ properties: {}
4711
+ },
4712
+ handler: wrapToolHandler("tap_get_session_state", {}, async () => {
4713
+ const state = await diagnostics2.getSessionRuntimeState();
4714
+ return summarizeRuntimeState(state);
4715
+ })
4716
+ });
4717
+ }
4718
+ return tools;
4719
+ }
4720
+
4721
+ // src/tools/goal-verification.mjs
4722
+ function renderVerification(prefix, result) {
4723
+ const lines = [
4724
+ `${prefix}: ${result.passed ? "passed" : "failed"}`,
4725
+ ...result.results.map((item) => {
4726
+ const label = item.description ?? item.claim ?? item.path ?? item.channel ?? item.label ?? `check ${item.index}`;
4727
+ return `- ${item.passed ? "PASS" : "FAIL"} ${label}${item.error ? ` \u2014 ${item.error}` : ""}`;
4728
+ })
4729
+ ];
4730
+ return lines.join("\n");
4731
+ }
4732
+ var CHECK_SCHEMA = {
4733
+ type: "object",
4734
+ properties: {
4735
+ type: {
4736
+ type: "string",
4737
+ description: "Check type: 'file', 'stream', or 'command_evidence'."
4738
+ },
4739
+ description: { type: "string" },
4740
+ path: { type: "string", description: "Workspace-relative file path for file checks." },
4741
+ nonEmpty: { type: "boolean", description: "For file checks, require a non-empty file." },
4742
+ channel: { type: "string", description: "EventStream name for stream checks." },
4743
+ limit: { type: "integer", description: "Recent stream entries to inspect." },
4744
+ minEntries: { type: "integer", description: "Minimum retained entries required." },
4745
+ contains: { type: "string", description: "Literal text that must be present." },
4746
+ pattern: { type: "string", description: "Regex pattern that must match." },
4747
+ label: { type: "string", description: "Human label for command evidence." },
4748
+ exitCode: { type: "integer", description: "Exit code from an already-run command." },
4749
+ success: { type: "boolean", description: "Whether already-run command evidence succeeded." }
4750
+ }
4751
+ };
4752
+ function createGoalVerificationTools(deps) {
4753
+ const verification = deps.verification ?? deps.runtime;
4754
+ if (!verification || typeof verification.verifyGoalOutput !== "function") {
4755
+ return [];
4756
+ }
4757
+ return [
4758
+ {
4759
+ name: "tap_verify_goal_output",
4760
+ description: "Verifies goal completion evidence without executing commands. Checks workspace files, EventStream history, or caller-supplied command evidence.",
4761
+ parameters: {
4762
+ type: "object",
4763
+ properties: {
4764
+ checks: {
4765
+ type: "array",
4766
+ items: CHECK_SCHEMA,
4767
+ description: "Evidence checks to perform. Command checks must use already-run command evidence; this tool does not execute shell commands."
4768
+ }
4769
+ },
4770
+ required: ["checks"]
4771
+ },
4772
+ handler: wrapToolHandler("tap_verify_goal_output", {}, async (args) => {
4773
+ const result = verification.verifyGoalOutput(args ?? {});
4774
+ return renderVerification("Goal output verification", result);
4775
+ })
4776
+ },
4777
+ {
4778
+ name: "tap_audit_claims",
4779
+ description: "Audits machine-readable goal claims against file, stream, or command-evidence surfaces before marking a goal complete.",
4780
+ parameters: {
4781
+ type: "object",
4782
+ properties: {
4783
+ claims: {
4784
+ type: "array",
4785
+ items: {
4786
+ type: "object",
4787
+ properties: {
4788
+ claim: { type: "string" },
4789
+ evidence: CHECK_SCHEMA
4790
+ },
4791
+ required: ["claim", "evidence"]
4792
+ }
4793
+ }
4794
+ },
4795
+ required: ["claims"]
4796
+ },
4797
+ handler: wrapToolHandler("tap_audit_claims", {}, async (args) => {
4798
+ const result = verification.auditClaims(args ?? {});
4799
+ return renderVerification("Claim audit", result);
4800
+ })
4801
+ }
4802
+ ];
4687
4803
  }
4688
4804
 
4689
4805
  // src/tools/index.mjs
@@ -4692,10 +4808,12 @@ function createTools(deps) {
4692
4808
  const streams = deps?.streams ?? source.streams ?? deps?.runtime;
4693
4809
  const emitters = deps?.emitters ?? source.emitters ?? deps?.runtime;
4694
4810
  const diagnostics2 = deps?.diagnostics ?? source.diagnostics ?? deps?.runtime;
4811
+ const verification = deps?.verification ?? source.verification ?? deps?.runtime;
4695
4812
  return [
4696
4813
  ...createStreamTools({ streams }),
4697
4814
  ...createEmitterTools({ emitters }),
4698
- ...createDiagnosticsTools({ diagnostics: diagnostics2 })
4815
+ ...createDiagnosticsTools({ diagnostics: diagnostics2 }),
4816
+ ...createGoalVerificationTools({ verification })
4699
4817
  ];
4700
4818
  }
4701
4819
 
@@ -8632,7 +8750,7 @@ function shouldPersistLoadedConfig(parsedConfig, normalizedConfig) {
8632
8750
  return !isDeepStrictEqual(parsedConfig, serializeConfigForComparison(normalizedConfig));
8633
8751
  }
8634
8752
  function createConfigStore(options = {}) {
8635
- const fs2 = options.fs ?? { existsSync: existsSync2, readFileSync: readFileSync2, writeFileSync: writeFileSync2 };
8753
+ const fs3 = options.fs ?? { existsSync: existsSync2, readFileSync: readFileSync2, writeFileSync: writeFileSync2 };
8636
8754
  const state = {
8637
8755
  cwd: normalizeBaseCwd(options.cwd),
8638
8756
  filePath: null,
@@ -8660,7 +8778,7 @@ function createConfigStore(options = {}) {
8660
8778
  const exists = withConfigLoadPhase(
8661
8779
  "checking config path",
8662
8780
  "Unable to check whether the tap config file exists.",
8663
- () => fs2.existsSync(filePath),
8781
+ () => fs3.existsSync(filePath),
8664
8782
  { filePath }
8665
8783
  );
8666
8784
  if (!exists) {
@@ -8669,7 +8787,7 @@ function createConfigStore(options = {}) {
8669
8787
  const rawConfig = withConfigLoadPhase(
8670
8788
  "reading config file",
8671
8789
  "Unable to read the tap config file.",
8672
- () => fs2.readFileSync(filePath, "utf8"),
8790
+ () => fs3.readFileSync(filePath, "utf8"),
8673
8791
  { filePath }
8674
8792
  );
8675
8793
  const parsedConfig = withConfigLoadPhase(
@@ -8727,7 +8845,7 @@ function createConfigStore(options = {}) {
8727
8845
  state.filePath = defaultConfigPath(state.cwd);
8728
8846
  }
8729
8847
  const payload = serializeConfig(state.config, LATEST_CONFIG_VERSION);
8730
- fs2.writeFileSync(state.filePath, `${JSON.stringify(payload, null, 2)}
8848
+ fs3.writeFileSync(state.filePath, `${JSON.stringify(payload, null, 2)}
8731
8849
  `, "utf8");
8732
8850
  }
8733
8851
  function findStreamIndex(name) {
@@ -9003,7 +9121,7 @@ function buildCompleteMessage(state) {
9003
9121
  return `Emitter '${state.name}' completed one run of ${state.emitterType} work.`;
9004
9122
  }
9005
9123
  if (state.maxRuns && state.runCount >= state.maxRuns) {
9006
- return `Emitter '${state.name}' completed ${state.runCount} of ${state.maxRuns} runs.`;
9124
+ return `Emitter '${state.name}' reached its run budget (${state.runCount} of ${state.maxRuns} runs). This stops the emitter; goal-style loops must use their final evidence summary to decide whether the objective is complete.`;
9007
9125
  }
9008
9126
  return null;
9009
9127
  }
@@ -9158,12 +9276,12 @@ function computeDeferredIterationTransition(currentState, state) {
9158
9276
  };
9159
9277
  }
9160
9278
  function computeFailedIterationTransition(currentState, state, event, result) {
9161
- if (isRunBudgetExhausted(state)) {
9162
- return computeRunBudgetExhaustedTransition(currentState, state, event, result);
9163
- }
9164
9279
  if (result.deferred) {
9165
9280
  return computeDeferredIterationTransition(currentState, state);
9166
9281
  }
9282
+ if (isRunBudgetExhausted(state)) {
9283
+ return computeRunBudgetExhaustedTransition(currentState, state, event, result);
9284
+ }
9167
9285
  if (state.runSchedule === RUN_SCHEDULE.ONE_TIME) {
9168
9286
  return {
9169
9287
  currentState,
@@ -9231,9 +9349,39 @@ function createDefaultProcessAdapter() {
9231
9349
  readLines
9232
9350
  };
9233
9351
  }
9352
+ function recordScheduledTrace(emitter, context, { startedAt, endedAt = nowIso(), runIndex, result = null, error = null, consumedRun = true } = {}) {
9353
+ try {
9354
+ context.diagnostics?.trace?.({
9355
+ traceId: `${stableTraceComponent(emitter.name)}-${Number(runIndex ?? emitter.runCount) || 0}-${Date.parse(startedAt ?? endedAt) || Date.now()}`,
9356
+ emitterId: emitter.name,
9357
+ emitterName: emitter.name,
9358
+ runIndex: Number(runIndex ?? emitter.runCount) || null,
9359
+ emitterType: emitter.emitterType,
9360
+ runSchedule: emitter.runSchedule,
9361
+ startedAt: startedAt ?? endedAt,
9362
+ endedAt,
9363
+ status: result?.deferred ? "deferred" : result?.ok ? "success" : "failure",
9364
+ ok: result?.ok === true,
9365
+ consumedRun,
9366
+ lineCount: emitter.lineCount,
9367
+ droppedLineCount: emitter.droppedLineCount,
9368
+ error: error ?? result?.error ?? null,
9369
+ metadata: {
9370
+ every: emitter.every ?? null,
9371
+ everySchedule: emitter.everySchedule ?? null,
9372
+ maxRuns: emitter.maxRuns ?? null,
9373
+ stopRequested: emitter.stopRequested === true
9374
+ }
9375
+ });
9376
+ } catch {
9377
+ }
9378
+ }
9234
9379
  var SESSION_ATTACH_RETRY_MS = 100;
9235
9380
  var DEFAULT_STOP_WAIT_TIMEOUT_MS = 1e4;
9236
9381
  var STOP_WAIT_POLL_MS = 50;
9382
+ function stableTraceComponent(value) {
9383
+ return String(value ?? "unknown").trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "unknown";
9384
+ }
9237
9385
  function errorMessage(error) {
9238
9386
  return error?.message ?? String(error ?? "unknown error");
9239
9387
  }
@@ -9503,11 +9651,12 @@ async function runPromptIteration(emitter, context) {
9503
9651
  } catch (error) {
9504
9652
  const message = error?.message ?? String(error ?? "unknown error");
9505
9653
  const sessionNotAttached = isSessionNotAttachedMessage(message);
9654
+ const deferred = sessionNotAttached || (emitter.runSchedule === RUN_SCHEDULE.TIMED || emitter.runSchedule === RUN_SCHEDULE.IDLE) && /\bsession\.idle\b/i.test(message);
9506
9655
  return {
9507
9656
  ok: false,
9508
9657
  error: message,
9509
- deferred: sessionNotAttached || (emitter.runSchedule === RUN_SCHEDULE.TIMED || emitter.runSchedule === RUN_SCHEDULE.IDLE) && /\bsession\.idle\b/i.test(message),
9510
- consumeRun: sessionNotAttached ? false : true,
9658
+ deferred,
9659
+ consumeRun: deferred ? false : true,
9511
9660
  deferredReason: sessionNotAttached ? "session-not-attached" : null
9512
9661
  };
9513
9662
  }
@@ -9568,6 +9717,8 @@ async function runScheduledIteration(emitter, context) {
9568
9717
  emitter.status = EMITTER_STATUS.RUNNING;
9569
9718
  emitter.runCount += 1;
9570
9719
  emitter.lastRunAt = nowIso();
9720
+ const traceStartedAt = emitter.lastRunAt;
9721
+ const traceRunIndex = emitter.runCount;
9571
9722
  let result;
9572
9723
  try {
9573
9724
  result = emitter.emitterType === EMITTER_TYPE.PROMPT ? await runPromptIteration(emitter, context) : await runCommandLoopIteration(emitter, context);
@@ -9585,6 +9736,12 @@ async function runScheduledIteration(emitter, context) {
9585
9736
  if (result?.consumeRun === false) {
9586
9737
  emitter.runCount = previousRunCount;
9587
9738
  emitter.lastRunAt = previousLastRunAt;
9739
+ recordScheduledTrace(emitter, context, {
9740
+ startedAt: traceStartedAt,
9741
+ runIndex: traceRunIndex,
9742
+ result,
9743
+ consumedRun: false
9744
+ });
9588
9745
  if (emitter.stopRequested) {
9589
9746
  applyLifecycleTransition(emitter, {
9590
9747
  type: LIFECYCLE_EVENT.ITERATION_RESULT,
@@ -9603,7 +9760,20 @@ async function runScheduledIteration(emitter, context) {
9603
9760
  result,
9604
9761
  timestamp: nowIso()
9605
9762
  }, context);
9763
+ recordScheduledTrace(emitter, context, {
9764
+ startedAt: traceStartedAt,
9765
+ runIndex: traceRunIndex,
9766
+ result,
9767
+ consumedRun: true
9768
+ });
9606
9769
  } catch (error) {
9770
+ recordScheduledTrace(emitter, context, {
9771
+ startedAt: traceStartedAt,
9772
+ runIndex: traceRunIndex,
9773
+ result: { ok: false, error: errorMessage(error) },
9774
+ error: errorMessage(error),
9775
+ consumedRun: true
9776
+ });
9607
9777
  recordScheduledTransitionFailure(emitter, error, context);
9608
9778
  } finally {
9609
9779
  emitter.inFlight = false;
@@ -9617,7 +9787,8 @@ function createLifecycle({
9617
9787
  sessionPort,
9618
9788
  timerAdapter = createDefaultTimerAdapter(),
9619
9789
  processAdapter = createDefaultProcessAdapter(),
9620
- loggerAdapter = createDefaultLoggerAdapter(sessionPort)
9790
+ loggerAdapter = createDefaultLoggerAdapter(sessionPort),
9791
+ diagnostics: diagnostics2 = null
9621
9792
  }) {
9622
9793
  function start(emitter) {
9623
9794
  if (emitter.runSchedule === RUN_SCHEDULE.CONTINUOUS) {
@@ -9627,6 +9798,7 @@ function createLifecycle({
9627
9798
  contextStartScheduled(emitter);
9628
9799
  }
9629
9800
  function contextStartScheduled(emitter) {
9801
+ const context = { lineRouter, timerAdapter, processAdapter, loggerAdapter, sessionPort, diagnostics: diagnostics2 };
9630
9802
  const scheduleLabel2 = emitter.runSchedule === RUN_SCHEDULE.TIMED ? emitter.everySchedule ? `backoff [${emitter.everySchedule.join(", ")}]` : `every ${emitter.every}` : emitter.runSchedule === RUN_SCHEDULE.IDLE ? "when idle" : RUN_SCHEDULE.ONE_TIME;
9631
9803
  lineRouter.appendSystemMessage(
9632
9804
  emitter,
@@ -9639,17 +9811,17 @@ function createLifecycle({
9639
9811
  );
9640
9812
  if (emitter.runSchedule === RUN_SCHEDULE.IDLE) {
9641
9813
  if (sessionPort.isIdle()) {
9642
- scheduleIteration(emitter, { lineRouter, timerAdapter, processAdapter, loggerAdapter, sessionPort }, IDLE_PROMPT_DELAY_MS);
9814
+ scheduleIteration(emitter, context, IDLE_PROMPT_DELAY_MS);
9643
9815
  }
9644
9816
  return;
9645
9817
  }
9646
- scheduleIteration(emitter, { lineRouter, timerAdapter, processAdapter, loggerAdapter, sessionPort }, 0);
9818
+ scheduleIteration(emitter, context, 0);
9647
9819
  }
9648
9820
  async function stop(emitter) {
9649
9821
  applyLifecycleTransition(
9650
9822
  emitter,
9651
9823
  { type: LIFECYCLE_EVENT.STOP, timestamp: nowIso() },
9652
- { lineRouter, timerAdapter, processAdapter, loggerAdapter, sessionPort }
9824
+ { lineRouter, timerAdapter, processAdapter, loggerAdapter, sessionPort, diagnostics: diagnostics2 }
9653
9825
  );
9654
9826
  if (isTerminalEmitterStatus(emitter.status)) {
9655
9827
  return;
@@ -9825,14 +9997,14 @@ function restorePersistentStreamConfigBestEffort(configStore, snapshot) {
9825
9997
 
9826
9998
  // src/emitter/supervisor.mjs
9827
9999
  var ROLLBACK_STOP_WAIT_TIMEOUT_MS = 1e4;
9828
- function createEmitterSupervisor({ streams, configStore, notifications, sessionPort, emitterWorkspace, persist, lifecycle: lifecycleOverride }) {
10000
+ function createEmitterSupervisor({ streams, configStore, notifications, sessionPort, emitterWorkspace, persist, lifecycle: lifecycleOverride, diagnostics: diagnostics2 = null }) {
9829
10001
  const emitters = /* @__PURE__ */ new Map();
9830
10002
  const lineRouter = createLineRouter({
9831
10003
  streams,
9832
10004
  notifications,
9833
10005
  surface: (message, options) => sessionPort.log(message, options)
9834
10006
  });
9835
- const lifecycle = lifecycleOverride ?? createLifecycle({ lineRouter, sessionPort });
10007
+ const lifecycle = lifecycleOverride ?? createLifecycle({ lineRouter, sessionPort, diagnostics: diagnostics2 });
9836
10008
  function hasExplicitPolicyValue(value) {
9837
10009
  return value !== void 0 && value !== null;
9838
10010
  }
@@ -10366,6 +10538,29 @@ function createSessionPort(initialSession = null) {
10366
10538
  }
10367
10539
  return canvasApi.open(params);
10368
10540
  }
10541
+ async function getRuntimeState() {
10542
+ if (!session2) {
10543
+ throw new LifecycleError("Session is not attached; cannot inspect runtime state.");
10544
+ }
10545
+ const rpc = session2.rpc ?? {};
10546
+ const read = async (label, fn) => {
10547
+ try {
10548
+ return { ok: true, value: await fn() };
10549
+ } catch (error) {
10550
+ return { ok: false, error: error?.message ?? String(error ?? "unknown error") };
10551
+ }
10552
+ };
10553
+ return {
10554
+ sessionId: session2.sessionId ?? null,
10555
+ capabilities: session2.capabilities ?? null,
10556
+ mode: await read("mode", () => rpc.mode?.get?.()),
10557
+ model: await read("model", () => rpc.model?.getCurrent?.()),
10558
+ tasks: await read("tasks", () => rpc.tasks?.list?.()),
10559
+ schedules: await read("schedules", () => rpc.schedule?.list?.()),
10560
+ skills: await read("skills", () => rpc.skills?.list?.()),
10561
+ openCanvases: await read("openCanvases", () => rpc.canvas?.listOpen?.())
10562
+ };
10563
+ }
10369
10564
  function registerTools(tools) {
10370
10565
  if (!session2) return;
10371
10566
  try {
@@ -10402,6 +10597,7 @@ function createSessionPort(initialSession = null) {
10402
10597
  send,
10403
10598
  sendAndWait,
10404
10599
  openCanvas,
10600
+ getRuntimeState,
10405
10601
  registerTools,
10406
10602
  reloadExtension
10407
10603
  };
@@ -10682,7 +10878,8 @@ function createRuntimeSubsystems(options = {}) {
10682
10878
  notifications,
10683
10879
  sessionPort,
10684
10880
  emitterWorkspace,
10685
- persist
10881
+ persist,
10882
+ diagnostics: options.diagnostics
10686
10883
  });
10687
10884
  return {
10688
10885
  sessionContext,
@@ -10857,10 +11054,170 @@ function createStreamService(deps) {
10857
11054
  };
10858
11055
  }
10859
11056
 
11057
+ // src/services/goal-verification-service.mjs
11058
+ import fs2 from "node:fs";
11059
+ import path7 from "node:path";
11060
+ function normalizeText(value) {
11061
+ return String(value ?? "").trim();
11062
+ }
11063
+ function safeRegex(pattern) {
11064
+ try {
11065
+ return new RegExp(String(pattern));
11066
+ } catch {
11067
+ return null;
11068
+ }
11069
+ }
11070
+ function resolveWithinBase(baseCwd, requestedPath) {
11071
+ const raw = normalizeText(requestedPath);
11072
+ if (!raw) {
11073
+ return { ok: false, error: "path is required" };
11074
+ }
11075
+ const base = path7.resolve(baseCwd || process.cwd());
11076
+ const resolved = path7.isAbsolute(raw) ? path7.resolve(raw) : path7.resolve(base, raw);
11077
+ const relative = path7.relative(base, resolved);
11078
+ if (relative.startsWith("..") || path7.isAbsolute(relative)) {
11079
+ return { ok: false, error: `path '${raw}' is outside the session workspace` };
11080
+ }
11081
+ return { ok: true, path: resolved, displayPath: relative || "." };
11082
+ }
11083
+ function textMatches(text, check = {}) {
11084
+ const contains = normalizeText(check.contains);
11085
+ if (contains && !String(text).includes(contains)) {
11086
+ return { ok: false, error: `expected text to contain '${contains}'` };
11087
+ }
11088
+ const pattern = normalizeText(check.pattern);
11089
+ if (pattern) {
11090
+ const regex = safeRegex(pattern);
11091
+ if (!regex) {
11092
+ return { ok: false, error: `invalid regex '${pattern}'` };
11093
+ }
11094
+ if (!regex.test(String(text))) {
11095
+ return { ok: false, error: `expected text to match /${pattern}/` };
11096
+ }
11097
+ }
11098
+ return { ok: true };
11099
+ }
11100
+ function verifyFile(check, { baseCwd }) {
11101
+ const resolved = resolveWithinBase(baseCwd, check.path);
11102
+ if (!resolved.ok) {
11103
+ return { ...resolved, passed: false };
11104
+ }
11105
+ if (!fs2.existsSync(resolved.path)) {
11106
+ return { passed: false, path: resolved.displayPath, error: "file does not exist" };
11107
+ }
11108
+ const stat = fs2.statSync(resolved.path);
11109
+ if (!stat.isFile()) {
11110
+ return { passed: false, path: resolved.displayPath, error: "path is not a file" };
11111
+ }
11112
+ if (check.nonEmpty === true && stat.size === 0) {
11113
+ return { passed: false, path: resolved.displayPath, error: "file is empty" };
11114
+ }
11115
+ if (check.contains || check.pattern) {
11116
+ const content = fs2.readFileSync(resolved.path, "utf8");
11117
+ const match = textMatches(content, check);
11118
+ if (!match.ok) {
11119
+ return { passed: false, path: resolved.displayPath, error: match.error };
11120
+ }
11121
+ }
11122
+ return { passed: true, path: resolved.displayPath, size: stat.size };
11123
+ }
11124
+ function verifyStream(check, { getStreamHistory }) {
11125
+ const channel = normalizeText(check.channel);
11126
+ if (!channel) {
11127
+ return { passed: false, error: "channel is required" };
11128
+ }
11129
+ let stream;
11130
+ try {
11131
+ stream = getStreamHistory(channel, check.limit)?.stream;
11132
+ } catch (error) {
11133
+ return { passed: false, channel, error: error?.message ?? String(error) };
11134
+ }
11135
+ const entries = Array.isArray(stream?.entries) ? stream.entries : [];
11136
+ if (check.minEntries !== void 0 && entries.length < Number(check.minEntries)) {
11137
+ return { passed: false, channel, entries: entries.length, error: `expected at least ${check.minEntries} entries` };
11138
+ }
11139
+ const text = entries.map((entry) => entry.text ?? "").join("\n");
11140
+ const match = textMatches(text, check);
11141
+ if (!match.ok) {
11142
+ return { passed: false, channel, entries: entries.length, error: match.error };
11143
+ }
11144
+ return { passed: true, channel, entries: entries.length };
11145
+ }
11146
+ function verifyCommandEvidence(check) {
11147
+ const label = normalizeText(check.label ?? check.command ?? "command evidence");
11148
+ if (check.exitCode === void 0 && check.success !== true) {
11149
+ return { passed: false, label, error: "provide exitCode or success=true from an already-run command" };
11150
+ }
11151
+ const passed = check.success === true || Number(check.exitCode) === 0;
11152
+ return {
11153
+ passed,
11154
+ label,
11155
+ exitCode: check.exitCode ?? null,
11156
+ error: passed ? null : `command evidence did not indicate success`
11157
+ };
11158
+ }
11159
+ function verifyCheck(check = {}, context) {
11160
+ const type = normalizeText(check.type || check.kind);
11161
+ if (type === "file" || type === "file_exists") {
11162
+ return verifyFile(check, context);
11163
+ }
11164
+ if (type === "stream" || type === "stream_contains" || type === "channel") {
11165
+ return verifyStream(check, context);
11166
+ }
11167
+ if (type === "command" || type === "command_evidence") {
11168
+ return verifyCommandEvidence(check);
11169
+ }
11170
+ return { passed: false, error: `unsupported check type '${type || "<missing>"}'` };
11171
+ }
11172
+ function createGoalVerificationService({ getBaseCwd, getStreamHistory } = {}) {
11173
+ function context() {
11174
+ return {
11175
+ baseCwd: typeof getBaseCwd === "function" ? getBaseCwd() : process.cwd(),
11176
+ getStreamHistory
11177
+ };
11178
+ }
11179
+ function verifyGoalOutput(input = {}) {
11180
+ const checks = Array.isArray(input.checks) ? input.checks : [];
11181
+ const results = checks.map((check, index) => ({
11182
+ index,
11183
+ description: check.description ?? check.claim ?? null,
11184
+ type: check.type ?? check.kind ?? null,
11185
+ ...verifyCheck(check, context())
11186
+ }));
11187
+ return {
11188
+ passed: results.length > 0 && results.every((result) => result.passed === true),
11189
+ results
11190
+ };
11191
+ }
11192
+ function auditClaims(input = {}) {
11193
+ const claims = Array.isArray(input.claims) ? input.claims : [];
11194
+ const results = claims.map((claim, index) => {
11195
+ const evidence = claim.evidence && typeof claim.evidence === "object" ? claim.evidence : {};
11196
+ const verification = verifyCheck({
11197
+ ...evidence,
11198
+ description: evidence.description ?? claim.claim,
11199
+ claim: claim.claim
11200
+ }, context());
11201
+ return {
11202
+ index,
11203
+ claim: claim.claim ?? null,
11204
+ status: verification.passed ? "confirmed" : "blocked",
11205
+ ...verification
11206
+ };
11207
+ });
11208
+ return {
11209
+ passed: results.length > 0 && results.every((result) => result.passed === true),
11210
+ results
11211
+ };
11212
+ }
11213
+ return { verifyGoalOutput, auditClaims };
11214
+ }
11215
+
10860
11216
  // src/diagnostics/store.mjs
10861
11217
  var DEFAULT_MAX_LOGS = 300;
10862
11218
  var DEFAULT_MAX_EVENTS = 300;
10863
11219
  var DEFAULT_MAX_RUNTIME_EVENTS = 300;
11220
+ var DEFAULT_MAX_TRACES = 300;
10864
11221
  var MAX_STRING_LENGTH = 1200;
10865
11222
  var MAX_COLLECTION_ITEMS = 40;
10866
11223
  var MAX_DEPTH = 4;
@@ -10996,10 +11353,12 @@ function createDiagnosticsStore(options = {}) {
10996
11353
  const logs = createRingBuffer(options.maxLogs ?? DEFAULT_MAX_LOGS);
10997
11354
  const sessionEvents = createRingBuffer(options.maxSessionEvents ?? DEFAULT_MAX_EVENTS);
10998
11355
  const runtimeEvents = createRingBuffer(options.maxRuntimeEvents ?? DEFAULT_MAX_RUNTIME_EVENTS);
11356
+ const traces = createRingBuffer(options.maxTraces ?? DEFAULT_MAX_TRACES);
10999
11357
  const sessionEventCounts = /* @__PURE__ */ new Map();
11000
11358
  let logCount = 0;
11001
11359
  let runtimeEventCount = 0;
11002
11360
  let sessionEventCount = 0;
11361
+ let traceCount = 0;
11003
11362
  let cleanupSessionListener = () => {
11004
11363
  };
11005
11364
  function recordLog(source, message, options2 = {}) {
@@ -11031,6 +11390,38 @@ function createDiagnosticsStore(options = {}) {
11031
11390
  })
11032
11391
  });
11033
11392
  }
11393
+ function recordTrace(trace = {}) {
11394
+ traceCount += 1;
11395
+ const startedAt = trace.startedAt ?? trace.timestamp ?? nowIso();
11396
+ const endedAt = trace.endedAt ?? nowIso();
11397
+ const startMs = Date.parse(startedAt);
11398
+ const endMs = Date.parse(endedAt);
11399
+ const durationMs = Number.isFinite(startMs) && Number.isFinite(endMs) ? Math.max(0, endMs - startMs) : null;
11400
+ return traces.append({
11401
+ id: createId("trace", traceCount),
11402
+ traceId: String(trace.traceId ?? `trace-${traceCount.toString(36)}`),
11403
+ timestamp: endedAt,
11404
+ startedAt,
11405
+ endedAt,
11406
+ durationMs,
11407
+ emitterId: trace.emitterId ?? trace.emitterName ?? null,
11408
+ emitterName: trace.emitterName ?? trace.emitterId ?? null,
11409
+ runIndex: Number.isFinite(Number(trace.runIndex)) ? Number(trace.runIndex) : null,
11410
+ emitterType: trace.emitterType ?? null,
11411
+ runSchedule: trace.runSchedule ?? null,
11412
+ status: trace.status ?? "unknown",
11413
+ ok: trace.ok === true,
11414
+ consumedRun: trace.consumedRun !== false,
11415
+ lineCount: Number.isFinite(Number(trace.lineCount)) ? Number(trace.lineCount) : null,
11416
+ droppedLineCount: Number.isFinite(Number(trace.droppedLineCount)) ? Number(trace.droppedLineCount) : null,
11417
+ error: trace.error ? truncateString(trace.error, MAX_STRING_LENGTH) : null,
11418
+ metadata: safeClone(trace.metadata ?? null, {
11419
+ maxDepth: 3,
11420
+ maxStringLength: 700,
11421
+ maxCollectionItems: 20
11422
+ })
11423
+ });
11424
+ }
11034
11425
  function recordSessionEvent(event) {
11035
11426
  sessionEventCount += 1;
11036
11427
  const type = String(event?.type ?? "unknown");
@@ -11073,11 +11464,13 @@ function createDiagnosticsStore(options = {}) {
11073
11464
  generatedAt: nowIso(),
11074
11465
  logs: logs.snapshot(options2.logLimit ?? 140),
11075
11466
  runtimeEvents: runtimeEvents.snapshot(options2.runtimeEventLimit ?? 140),
11467
+ traces: traces.snapshot(options2.traceLimit ?? 140),
11076
11468
  sessionEvents: sessionEvents.snapshot(options2.sessionEventLimit ?? 140),
11077
11469
  sessionEventCounts: Object.fromEntries([...sessionEventCounts.entries()].sort(([left], [right]) => left.localeCompare(right))),
11078
11470
  stats: {
11079
11471
  logs: logs.stats(),
11080
11472
  runtimeEvents: runtimeEvents.stats(),
11473
+ traces: traces.stats(),
11081
11474
  sessionEvents: sessionEvents.stats()
11082
11475
  }
11083
11476
  };
@@ -11085,6 +11478,7 @@ function createDiagnosticsStore(options = {}) {
11085
11478
  return {
11086
11479
  log: recordLog,
11087
11480
  event: recordRuntimeEvent,
11481
+ trace: recordTrace,
11088
11482
  attachSession,
11089
11483
  detachSession,
11090
11484
  snapshot
@@ -11124,7 +11518,10 @@ function createTapRuntimeService(options = {}) {
11124
11518
  configWorkspace,
11125
11519
  emitterWorkspace,
11126
11520
  persist
11127
- } = createRuntimeSubsystems(options);
11521
+ } = createRuntimeSubsystems({
11522
+ ...options,
11523
+ diagnostics: diagnosticsStore
11524
+ });
11128
11525
  const streamService = createStreamService({
11129
11526
  streams,
11130
11527
  configStore,
@@ -11137,6 +11534,10 @@ function createTapRuntimeService(options = {}) {
11137
11534
  supervisor,
11138
11535
  emitterWorkspace
11139
11536
  });
11537
+ const goalVerificationService = createGoalVerificationService({
11538
+ getBaseCwd: sessionContext.getBaseCwd,
11539
+ getStreamHistory: (channel, limit) => streamService.getStreamHistory(channel, limit)
11540
+ });
11140
11541
  const configBootstrapService = createConfigBootstrapService({
11141
11542
  streams,
11142
11543
  configStore,
@@ -11144,7 +11545,8 @@ function createTapRuntimeService(options = {}) {
11144
11545
  sessionPort,
11145
11546
  configWorkspace
11146
11547
  });
11147
- const { loadPersistentConfig } = configBootstrapService;
11548
+ const { loadPersistentConfig: loadPersistentConfigRaw } = configBootstrapService;
11549
+ let persistentConfigLoadPromise = null;
11148
11550
  const sessionActivityBridge = createSessionActivityBridge({ sessionPort, supervisor });
11149
11551
  const providerPushService = createProviderPushService({
11150
11552
  streams,
@@ -11156,11 +11558,27 @@ function createTapRuntimeService(options = {}) {
11156
11558
  const session2 = sessionPort.current();
11157
11559
  return sessionContext.getSessionInfo(session2);
11158
11560
  }
11561
+ function loadPersistentConfigOnce(inputCwd) {
11562
+ if (persistentConfigLoadPromise) {
11563
+ return persistentConfigLoadPromise;
11564
+ }
11565
+ persistentConfigLoadPromise = Promise.resolve().then(() => loadPersistentConfigRaw(inputCwd)).catch((error) => {
11566
+ persistentConfigLoadPromise = null;
11567
+ throw error;
11568
+ });
11569
+ return persistentConfigLoadPromise;
11570
+ }
11159
11571
  function attachSession(session2) {
11160
11572
  sessionContext.attachSession(session2);
11161
11573
  sessionPort.attach(session2);
11162
11574
  sessionActivityBridge.attach(session2);
11163
11575
  diagnosticsStore.attachSession(session2);
11576
+ void loadPersistentConfigOnce(sessionContext.getBaseCwd()).then((summary) => {
11577
+ void sessionPort.log(summary);
11578
+ }).catch((error) => {
11579
+ const message = error?.message ?? String(error ?? "unknown error");
11580
+ void sessionPort.log(`Config load failed during extension attach: ${message}`, { level: "warning" });
11581
+ });
11164
11582
  }
11165
11583
  function clearNotificationsForLifecycle(options2 = {}) {
11166
11584
  if (options2.clearNotifications === true && typeof notifications.clear === "function") {
@@ -11215,7 +11633,7 @@ function createTapRuntimeService(options = {}) {
11215
11633
  } = createRuntimeHooks({
11216
11634
  streams,
11217
11635
  sessionPort,
11218
- loadPersistentConfig,
11636
+ loadPersistentConfig: loadPersistentConfigOnce,
11219
11637
  stopAllEmitters,
11220
11638
  stopAllEmittersAndWait,
11221
11639
  shutdownSession,
@@ -11278,6 +11696,7 @@ function createTapRuntimeService(options = {}) {
11278
11696
  input: canvasInput
11279
11697
  });
11280
11698
  },
11699
+ getSessionRuntimeState: () => sessionPort.getRuntimeState(),
11281
11700
  attachSession: diagnosticsStore.attachSession,
11282
11701
  detachSession: diagnosticsStore.detachSession
11283
11702
  };
@@ -11285,12 +11704,14 @@ function createTapRuntimeService(options = {}) {
11285
11704
  tools: {
11286
11705
  streams: streamCapabilities,
11287
11706
  emitters: emitterCapabilities,
11288
- diagnostics: diagnosticsCapabilities
11707
+ diagnostics: diagnosticsCapabilities,
11708
+ verification: goalVerificationService
11289
11709
  },
11290
11710
  hooks: hookCapabilities,
11291
11711
  session: sessionCapabilities,
11292
11712
  provider: providerCapabilities,
11293
11713
  diagnostics: diagnosticsCapabilities,
11714
+ verification: goalVerificationService,
11294
11715
  getBaseCwd: sessionContext.getBaseCwd,
11295
11716
  getSessionInfo,
11296
11717
  attachSession,
@@ -11299,7 +11720,7 @@ function createTapRuntimeService(options = {}) {
11299
11720
  appendStreamMessage,
11300
11721
  getSessionStartContext,
11301
11722
  getPromptContext,
11302
- loadPersistentConfig,
11723
+ loadPersistentConfig: loadPersistentConfigOnce,
11303
11724
  listEmitters: emitterCapabilities.listEmitters,
11304
11725
  listStreams: streamCapabilities.listStreams,
11305
11726
  postToStream: streamCapabilities.postToStream,
@@ -11340,6 +11761,7 @@ function sanitizeSnapshotOptions(input = {}) {
11340
11761
  return {
11341
11762
  streamEntryLimit: Number.isFinite(limit) ? Math.max(10, Math.min(200, Math.floor(limit))) : 80,
11342
11763
  logLimit: Number.isFinite(limit) ? Math.max(20, Math.min(300, Math.floor(limit))) : 160,
11764
+ traceLimit: Number.isFinite(limit) ? Math.max(20, Math.min(300, Math.floor(limit))) : 160,
11343
11765
  sessionEventLimit: Number.isFinite(limit) ? Math.max(20, Math.min(300, Math.floor(limit))) : 160,
11344
11766
  runtimeEventLimit: Number.isFinite(limit) ? Math.max(20, Math.min(300, Math.floor(limit))) : 160
11345
11767
  };
@@ -11350,8 +11772,9 @@ function summarizeSnapshot(snapshot) {
11350
11772
  const streams = Array.isArray(snapshot?.streams) ? snapshot.streams.length : 0;
11351
11773
  const providers = Array.isArray(snapshot?.gateway?.providers) ? snapshot.gateway.providers.length : 0;
11352
11774
  const logs = snapshot?.diagnostics?.stats?.logs?.retained ?? 0;
11775
+ const traces = snapshot?.diagnostics?.stats?.traces?.retained ?? 0;
11353
11776
  const sessionEvents = snapshot?.diagnostics?.stats?.sessionEvents?.retained ?? 0;
11354
- return { streams, runningEmitters, configuredEmitters, providers, logs, sessionEvents };
11777
+ return { streams, runningEmitters, configuredEmitters, providers, logs, traces, sessionEvents };
11355
11778
  }
11356
11779
  function createHtml() {
11357
11780
  return `<!doctype html>
@@ -11581,7 +12004,7 @@ function createHtml() {
11581
12004
  overflow: auto;
11582
12005
  }
11583
12006
 
11584
- .stream-grid, .emitter-grid, .provider-grid {
12007
+ .stream-grid, .emitter-grid, .provider-grid, .trace-grid {
11585
12008
  display: grid;
11586
12009
  gap: 12px;
11587
12010
  }
@@ -11753,6 +12176,16 @@ function createHtml() {
11753
12176
  </div>
11754
12177
  </section>
11755
12178
 
12179
+ <section class="panel">
12180
+ <div class="panel-head">
12181
+ <h2>Goals and traces</h2>
12182
+ <small id="trace-count">0 traces</small>
12183
+ </div>
12184
+ <div class="panel-body">
12185
+ <div class="trace-grid" id="traces"></div>
12186
+ </div>
12187
+ </section>
12188
+
11756
12189
  <section class="panel timeline-panel">
11757
12190
  <div class="panel-head">
11758
12191
  <h2>Evidence timeline</h2>
@@ -11789,12 +12222,14 @@ function createHtml() {
11789
12222
  const streams = snapshot.streams?.length ?? 0;
11790
12223
  const providers = snapshot.gateway?.providers?.length ?? 0;
11791
12224
  const queue = snapshot.notifications?.queueSize ?? 0;
12225
+ const traces = snapshot.diagnostics?.stats?.traces?.retained ?? 0;
11792
12226
  const sessionEvents = snapshot.diagnostics?.stats?.sessionEvents?.retained ?? 0;
11793
12227
  el("metrics").innerHTML = [
11794
12228
  metric("streams", streams),
11795
12229
  metric("running emitters", running),
11796
12230
  metric("providers", providers),
11797
12231
  metric("queued injections", queue),
12232
+ metric("traces", traces),
11798
12233
  metric("configured emitters", configured),
11799
12234
  metric("session events", sessionEvents)
11800
12235
  ].join("");
@@ -11834,6 +12269,21 @@ function createHtml() {
11834
12269
  el("providers").innerHTML = gatewayRecord + rows;
11835
12270
  }
11836
12271
 
12272
+ function renderTraces(snapshot) {
12273
+ const traces = snapshot.diagnostics?.traces ?? [];
12274
+ const goalStreams = (snapshot.streams ?? []).filter((stream) => String(stream.name ?? "").startsWith("goal-"));
12275
+ el("trace-count").textContent = traces.length + " traces";
12276
+ const traceRows = traces.slice(-12).reverse().map((trace) => {
12277
+ const tone = trace.ok ? "ready" : trace.status === "deferred" ? "warning" : "error";
12278
+ return '<div class="record"><strong>' + escapeHtml(trace.emitterName ?? trace.emitterId ?? "unknown") + ' <span class="badge ' + tone + '">' + escapeHtml(trace.status ?? "unknown") + '</span></strong><div class="meta">run=' + escapeHtml(trace.runIndex ?? "?") + ' duration=' + escapeHtml(trace.durationMs ?? "?") + 'ms | trace=' + escapeHtml(trace.traceId ?? "?") + '</div>' + (trace.error ? '<div class="details">' + escapeHtml(trace.error) + '</div>' : '') + '</div>';
12279
+ }).join("");
12280
+ const goalRows = goalStreams.map((stream) => {
12281
+ const latest = (stream.entries ?? []).slice(-2).reverse().map((entry) => '<div class="entry"><span class="meta">' + escapeHtml(timeOnly(entry.timestamp)) + '</span>\\n' + escapeHtml(entry.text) + '</div>').join("");
12282
+ return '<div class="record"><strong>' + escapeHtml(stream.name) + ' <span class="badge info">goal stream</span></strong><div class="meta">' + escapeHtml(stream.entries?.length ?? 0) + ' retained entries</div>' + (latest || '<div class="entry">No goal ledger entries retained.</div>') + '</div>';
12283
+ }).join("");
12284
+ el("traces").innerHTML = (traceRows || '<div class="empty">No emitter-run traces retained yet.</div>') + (goalRows ? '<div class="panel-head" style="margin:14px -14px 12px"><h2>Goal ledgers</h2><small>' + goalStreams.length + ' streams</small></div>' + goalRows : "");
12285
+ }
12286
+
11837
12287
  function collectTimeline(snapshot) {
11838
12288
  const items = [];
11839
12289
  for (const stream of snapshot.streams ?? []) {
@@ -11850,6 +12300,9 @@ function createHtml() {
11850
12300
  for (const event of snapshot.diagnostics?.runtimeEvents ?? []) {
11851
12301
  items.push({ group: "runtime", timestamp: event.timestamp, source: event.type, level: "info", message: event.message, detail: compactJson(event.metadata) });
11852
12302
  }
12303
+ for (const trace of snapshot.diagnostics?.traces ?? []) {
12304
+ items.push({ group: "runtime", timestamp: trace.endedAt ?? trace.timestamp, source: "trace/" + (trace.emitterName ?? trace.emitterId ?? "unknown"), level: trace.ok ? "info" : "warning", message: "emitter run " + (trace.status ?? "unknown"), detail: compactJson({ traceId: trace.traceId, runIndex: trace.runIndex, durationMs: trace.durationMs, error: trace.error }) });
12305
+ }
11853
12306
  return items.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
11854
12307
  }
11855
12308
 
@@ -11873,6 +12326,7 @@ function createHtml() {
11873
12326
  renderMetrics(snapshot);
11874
12327
  renderStreams(snapshot);
11875
12328
  renderProviders(snapshot);
12329
+ renderTraces(snapshot);
11876
12330
  renderTimeline(snapshot);
11877
12331
  }
11878
12332