copilot-tap-extension 2.0.7 → 2.0.9

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.
Files changed (58) hide show
  1. package/README.md +4 -1
  2. package/SOUL.md +51 -0
  3. package/bin/install.mjs +7 -1
  4. package/dist/copilot-instructions.md +15 -0
  5. package/dist/extension.mjs +823 -29
  6. package/dist/skills/tap-goal/SKILL.md +13 -2
  7. package/dist/skills/tap-loop/SKILL.md +6 -0
  8. package/dist/skills/tap-monitor/SKILL.md +19 -3
  9. package/dist/skills/tap-orchestrate/SKILL.md +81 -0
  10. package/dist/version.json +1 -1
  11. package/docs/adr/0001-persistent-config-default-ownership.md +33 -0
  12. package/docs/adr/0002-local-provider-gateway-runtime-security.md +36 -0
  13. package/docs/adr/0003-emitter-delivery-lifecycle.md +68 -0
  14. package/docs/adr/0004-persistent-config-canonical-streams.md +86 -0
  15. package/docs/adr/0005-provider-sdk-push-and-dynamic-tools.md +48 -0
  16. package/docs/adr/0006-command-emitter-cwd-workspace-boundary.md +46 -0
  17. package/docs/adr/0007-runtime-session-workspace-context.md +62 -0
  18. package/docs/evals.md +41 -0
  19. package/docs/evolution-of-tap-icon.html +989 -0
  20. package/docs/providers.md +242 -0
  21. package/docs/recipes/adaptive-agent.md +303 -0
  22. package/docs/recipes/agent-brainstorm/100-extension-ideas.md +288 -0
  23. package/docs/recipes/agent-brainstorm/deep-ideas.md +216 -0
  24. package/docs/recipes/ambient-guardian.md +314 -0
  25. package/docs/recipes/browser-bridge.md +162 -0
  26. package/docs/recipes/codex-goals-for-tap-goal.md +136 -0
  27. package/docs/recipes/copilot-sdk-canvas.md +147 -0
  28. package/docs/recipes/deferred-cognition.md +310 -0
  29. package/docs/recipes/provider-integration-patterns.md +93 -0
  30. package/docs/recipes/provider-interface-advanced.md +1364 -0
  31. package/docs/recipes/provider-interface-core-profile.md +568 -0
  32. package/docs/recipes/tap-control-plane-roadmap.md +60 -0
  33. package/docs/recipes/universal-tool-gateway.md +202 -0
  34. package/docs/reference.md +229 -0
  35. package/docs/use-cases.md +348 -0
  36. package/package.json +4 -1
  37. package/providers/detour/README.md +84 -0
  38. package/providers/detour/bridge.js +219 -0
  39. package/providers/detour/index.mjs +322 -0
  40. package/providers/detour/package-lock.json +577 -0
  41. package/providers/detour/package.json +19 -0
  42. package/providers/detour/scripts/build.mjs +31 -0
  43. package/providers/detour/src/bridge.js +256 -0
  44. package/providers/detour/src/contracts.js +40 -0
  45. package/providers/detour/src/inspector.js +260 -0
  46. package/providers/detour/src/inspector.test.mjs +53 -0
  47. package/providers/detour/src/panel.js +465 -0
  48. package/providers/detour/src/provider-core.js +233 -0
  49. package/providers/detour/src/provider-core.test.mjs +185 -0
  50. package/providers/detour/src/react-context-core.js +143 -0
  51. package/providers/detour/src/react-context.js +44 -0
  52. package/providers/detour/src/react-context.test.mjs +41 -0
  53. package/providers/templates/README.md +23 -0
  54. package/providers/templates/ci-review-provider.mjs +46 -0
  55. package/providers/templates/detour-workflow-provider.mjs +41 -0
  56. package/providers/templates/jira-github-provider.mjs +42 -0
  57. package/providers/templates/provider-utils.mjs +45 -0
  58. package/providers/templates/sast-triage-provider.mjs +51 -0
@@ -4654,12 +4654,31 @@ 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 permissionCount = state?.permissions?.ok ? state.permissions.value?.items?.length ?? 0 : null;
4663
+ const canvasCount = state?.openCanvases?.ok ? state.openCanvases.value?.openCanvases?.length ?? 0 : null;
4664
+ return [
4665
+ `sessionId=${state?.sessionId ?? "(none)"}`,
4666
+ `mode=${typeof mode === "string" ? mode : JSON.stringify(mode)}`,
4667
+ model ? `model=${model.modelId ?? "unknown"} reasoning=${model.reasoningEffort ?? "default"} context=${model.contextTier ?? "default"}` : null,
4668
+ taskCount !== null ? `tasks=${taskCount}` : null,
4669
+ scheduleCount !== null ? `schedules=${scheduleCount}` : null,
4670
+ permissionCount !== null ? `pendingPermissions=${permissionCount}` : null,
4671
+ canvasCount !== null ? `openCanvases=${canvasCount}` : null,
4672
+ `elicitation=${state?.capabilities?.ui?.elicitation === true ? "available" : "unavailable"}`,
4673
+ `canvases=${state?.capabilities?.ui?.canvases === true ? "available" : "host-gated"}`
4674
+ ].filter(Boolean).join("\n");
4675
+ }
4657
4676
  function createDiagnosticsTools(deps) {
4658
4677
  const diagnostics2 = deps.diagnostics ?? deps.runtime;
4659
4678
  if (!diagnostics2 || typeof diagnostics2.openCanvas !== "function") {
4660
4679
  return [];
4661
4680
  }
4662
- return [
4681
+ const tools = [
4663
4682
  {
4664
4683
  name: "tap_open_diagnostics_canvas",
4665
4684
  description: "Opens or focuses the Tap diagnostics canvas, a live flight recorder for streams, emitters, providers, logs, injection queues, and session events.",
@@ -4684,6 +4703,163 @@ function createDiagnosticsTools(deps) {
4684
4703
  })
4685
4704
  }
4686
4705
  ];
4706
+ if (typeof diagnostics2.getSessionRuntimeState === "function") {
4707
+ tools.push({
4708
+ name: "tap_get_session_state",
4709
+ 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.",
4710
+ parameters: {
4711
+ type: "object",
4712
+ properties: {}
4713
+ },
4714
+ handler: wrapToolHandler("tap_get_session_state", {}, async () => {
4715
+ const state = await diagnostics2.getSessionRuntimeState();
4716
+ return summarizeRuntimeState(state);
4717
+ })
4718
+ });
4719
+ if (typeof diagnostics2.setSessionMode === "function") {
4720
+ tools.push({
4721
+ name: "tap_set_session_mode",
4722
+ description: "Guarded Copilot mode switch for tap workflows. Requires explicit confirmation text and is intended for interactive/plan/autopilot transitions only.",
4723
+ parameters: {
4724
+ type: "object",
4725
+ properties: {
4726
+ mode: {
4727
+ type: "string",
4728
+ enum: ["interactive", "plan", "autopilot"],
4729
+ description: "Target Copilot session mode."
4730
+ },
4731
+ reason: {
4732
+ type: "string",
4733
+ description: "Why this mode switch is needed."
4734
+ },
4735
+ confirm: {
4736
+ type: "string",
4737
+ description: "Must be exactly 'set-session-mode' to confirm this user-visible mode change."
4738
+ }
4739
+ },
4740
+ required: ["mode", "reason", "confirm"]
4741
+ },
4742
+ handler: wrapToolHandler("tap_set_session_mode", (args) => ({ mode: args.mode }), async (args) => {
4743
+ if (args.confirm !== "set-session-mode") {
4744
+ throw new Error("Refusing to change session mode without confirm='set-session-mode'.");
4745
+ }
4746
+ const nextMode = await diagnostics2.setSessionMode(args.mode);
4747
+ return `Session mode set to ${nextMode}. reason=${args.reason}`;
4748
+ })
4749
+ });
4750
+ }
4751
+ }
4752
+ if (typeof diagnostics2.queryRecords === "function") {
4753
+ tools.push({
4754
+ name: "tap_query_records",
4755
+ description: "Reads structured tap records persisted in the session workspace, such as traces and stream-posts.",
4756
+ parameters: {
4757
+ type: "object",
4758
+ properties: {
4759
+ collection: {
4760
+ type: "string",
4761
+ description: "Record collection name, for example 'traces' or 'stream-posts'."
4762
+ },
4763
+ limit: {
4764
+ type: "integer",
4765
+ minimum: 1,
4766
+ maximum: 500,
4767
+ description: "Maximum recent records to return."
4768
+ }
4769
+ },
4770
+ required: ["collection"]
4771
+ },
4772
+ handler: wrapToolHandler("tap_query_records", (args) => ({ collection: args.collection }), async (args) => {
4773
+ const result = diagnostics2.queryRecords(args.collection, { limit: args.limit });
4774
+ return JSON.stringify(result, null, 2);
4775
+ })
4776
+ });
4777
+ }
4778
+ return tools;
4779
+ }
4780
+
4781
+ // src/tools/goal-verification.mjs
4782
+ function renderVerification(prefix, result) {
4783
+ const lines = [
4784
+ `${prefix}: ${result.passed ? "passed" : "failed"}`,
4785
+ ...result.results.map((item) => {
4786
+ const label = item.description ?? item.claim ?? item.path ?? item.channel ?? item.label ?? `check ${item.index}`;
4787
+ return `- ${item.passed ? "PASS" : "FAIL"} ${label}${item.error ? ` \u2014 ${item.error}` : ""}`;
4788
+ })
4789
+ ];
4790
+ return lines.join("\n");
4791
+ }
4792
+ var CHECK_SCHEMA = {
4793
+ type: "object",
4794
+ properties: {
4795
+ type: {
4796
+ type: "string",
4797
+ description: "Check type: 'file', 'stream', or 'command_evidence'."
4798
+ },
4799
+ description: { type: "string" },
4800
+ path: { type: "string", description: "Workspace-relative file path for file checks." },
4801
+ nonEmpty: { type: "boolean", description: "For file checks, require a non-empty file." },
4802
+ channel: { type: "string", description: "EventStream name for stream checks." },
4803
+ limit: { type: "integer", description: "Recent stream entries to inspect." },
4804
+ minEntries: { type: "integer", description: "Minimum retained entries required." },
4805
+ contains: { type: "string", description: "Literal text that must be present." },
4806
+ pattern: { type: "string", description: "Regex pattern that must match." },
4807
+ label: { type: "string", description: "Human label for command evidence." },
4808
+ exitCode: { type: "integer", description: "Exit code from an already-run command." },
4809
+ success: { type: "boolean", description: "Whether already-run command evidence succeeded." }
4810
+ }
4811
+ };
4812
+ function createGoalVerificationTools(deps) {
4813
+ const verification = deps.verification ?? deps.runtime;
4814
+ if (!verification || typeof verification.verifyGoalOutput !== "function") {
4815
+ return [];
4816
+ }
4817
+ return [
4818
+ {
4819
+ name: "tap_verify_goal_output",
4820
+ description: "Verifies goal completion evidence without executing commands. Checks workspace files, EventStream history, or caller-supplied command evidence.",
4821
+ parameters: {
4822
+ type: "object",
4823
+ properties: {
4824
+ checks: {
4825
+ type: "array",
4826
+ items: CHECK_SCHEMA,
4827
+ description: "Evidence checks to perform. Command checks must use already-run command evidence; this tool does not execute shell commands."
4828
+ }
4829
+ },
4830
+ required: ["checks"]
4831
+ },
4832
+ handler: wrapToolHandler("tap_verify_goal_output", {}, async (args) => {
4833
+ const result = verification.verifyGoalOutput(args ?? {});
4834
+ return renderVerification("Goal output verification", result);
4835
+ })
4836
+ },
4837
+ {
4838
+ name: "tap_audit_claims",
4839
+ description: "Audits machine-readable goal claims against file, stream, or command-evidence surfaces before marking a goal complete.",
4840
+ parameters: {
4841
+ type: "object",
4842
+ properties: {
4843
+ claims: {
4844
+ type: "array",
4845
+ items: {
4846
+ type: "object",
4847
+ properties: {
4848
+ claim: { type: "string" },
4849
+ evidence: CHECK_SCHEMA
4850
+ },
4851
+ required: ["claim", "evidence"]
4852
+ }
4853
+ }
4854
+ },
4855
+ required: ["claims"]
4856
+ },
4857
+ handler: wrapToolHandler("tap_audit_claims", {}, async (args) => {
4858
+ const result = verification.auditClaims(args ?? {});
4859
+ return renderVerification("Claim audit", result);
4860
+ })
4861
+ }
4862
+ ];
4687
4863
  }
4688
4864
 
4689
4865
  // src/tools/index.mjs
@@ -4692,10 +4868,12 @@ function createTools(deps) {
4692
4868
  const streams = deps?.streams ?? source.streams ?? deps?.runtime;
4693
4869
  const emitters = deps?.emitters ?? source.emitters ?? deps?.runtime;
4694
4870
  const diagnostics2 = deps?.diagnostics ?? source.diagnostics ?? deps?.runtime;
4871
+ const verification = deps?.verification ?? source.verification ?? deps?.runtime;
4695
4872
  return [
4696
4873
  ...createStreamTools({ streams }),
4697
4874
  ...createEmitterTools({ emitters }),
4698
- ...createDiagnosticsTools({ diagnostics: diagnostics2 })
4875
+ ...createDiagnosticsTools({ diagnostics: diagnostics2 }),
4876
+ ...createGoalVerificationTools({ verification })
4699
4877
  ];
4700
4878
  }
4701
4879
 
@@ -6801,6 +6979,10 @@ function createProviderGateway(options = {}, adapters = {}) {
6801
6979
  };
6802
6980
  }
6803
6981
 
6982
+ // src/services/tap-runtime-service.mjs
6983
+ import fs4 from "node:fs";
6984
+ import path9 from "node:path";
6985
+
6804
6986
  // src/session/listeners.mjs
6805
6987
  var SESSION_ACTIVITY_EVENTS = [
6806
6988
  "session.start",
@@ -8632,7 +8814,7 @@ function shouldPersistLoadedConfig(parsedConfig, normalizedConfig) {
8632
8814
  return !isDeepStrictEqual(parsedConfig, serializeConfigForComparison(normalizedConfig));
8633
8815
  }
8634
8816
  function createConfigStore(options = {}) {
8635
- const fs2 = options.fs ?? { existsSync: existsSync2, readFileSync: readFileSync2, writeFileSync: writeFileSync2 };
8817
+ const fs5 = options.fs ?? { existsSync: existsSync2, readFileSync: readFileSync2, writeFileSync: writeFileSync2 };
8636
8818
  const state = {
8637
8819
  cwd: normalizeBaseCwd(options.cwd),
8638
8820
  filePath: null,
@@ -8660,7 +8842,7 @@ function createConfigStore(options = {}) {
8660
8842
  const exists = withConfigLoadPhase(
8661
8843
  "checking config path",
8662
8844
  "Unable to check whether the tap config file exists.",
8663
- () => fs2.existsSync(filePath),
8845
+ () => fs5.existsSync(filePath),
8664
8846
  { filePath }
8665
8847
  );
8666
8848
  if (!exists) {
@@ -8669,7 +8851,7 @@ function createConfigStore(options = {}) {
8669
8851
  const rawConfig = withConfigLoadPhase(
8670
8852
  "reading config file",
8671
8853
  "Unable to read the tap config file.",
8672
- () => fs2.readFileSync(filePath, "utf8"),
8854
+ () => fs5.readFileSync(filePath, "utf8"),
8673
8855
  { filePath }
8674
8856
  );
8675
8857
  const parsedConfig = withConfigLoadPhase(
@@ -8727,7 +8909,7 @@ function createConfigStore(options = {}) {
8727
8909
  state.filePath = defaultConfigPath(state.cwd);
8728
8910
  }
8729
8911
  const payload = serializeConfig(state.config, LATEST_CONFIG_VERSION);
8730
- fs2.writeFileSync(state.filePath, `${JSON.stringify(payload, null, 2)}
8912
+ fs5.writeFileSync(state.filePath, `${JSON.stringify(payload, null, 2)}
8731
8913
  `, "utf8");
8732
8914
  }
8733
8915
  function findStreamIndex(name) {
@@ -9231,9 +9413,64 @@ function createDefaultProcessAdapter() {
9231
9413
  readLines
9232
9414
  };
9233
9415
  }
9416
+ function recordScheduledTrace(emitter, context, { startedAt, endedAt = nowIso(), runIndex, result = null, error = null, consumedRun = true } = {}) {
9417
+ try {
9418
+ const start = startedAt ?? endedAt;
9419
+ const status = result?.deferred ? "deferred" : result?.ok ? "success" : "failure";
9420
+ context.diagnostics?.trace?.({
9421
+ traceId: `${stableTraceComponent(emitter.name)}-${Number(runIndex ?? emitter.runCount) || 0}-${Date.parse(start) || Date.now()}`,
9422
+ emitterId: emitter.name,
9423
+ emitterName: emitter.name,
9424
+ runIndex: Number(runIndex ?? emitter.runCount) || null,
9425
+ emitterType: emitter.emitterType,
9426
+ runSchedule: emitter.runSchedule,
9427
+ startedAt: start,
9428
+ endedAt,
9429
+ status,
9430
+ ok: result?.ok === true,
9431
+ consumedRun,
9432
+ lineCount: emitter.lineCount,
9433
+ droppedLineCount: emitter.droppedLineCount,
9434
+ error: error ?? result?.error ?? null,
9435
+ spans: [
9436
+ {
9437
+ spanId: "emitter-run",
9438
+ kind: "emitter.run",
9439
+ name: emitter.name,
9440
+ startedAt: start,
9441
+ endedAt,
9442
+ status
9443
+ },
9444
+ {
9445
+ spanId: emitter.emitterType === EMITTER_TYPE.PROMPT ? "prompt-dispatch" : "command-process",
9446
+ parentSpanId: "emitter-run",
9447
+ kind: emitter.emitterType === EMITTER_TYPE.PROMPT ? "prompt.dispatch" : "command.process",
9448
+ name: emitter.emitterType,
9449
+ startedAt: start,
9450
+ endedAt,
9451
+ status,
9452
+ metadata: {
9453
+ stream: emitter.stream,
9454
+ cwd: emitter.cwd ?? null
9455
+ }
9456
+ }
9457
+ ],
9458
+ metadata: {
9459
+ every: emitter.every ?? null,
9460
+ everySchedule: emitter.everySchedule ?? null,
9461
+ maxRuns: emitter.maxRuns ?? null,
9462
+ stopRequested: emitter.stopRequested === true
9463
+ }
9464
+ });
9465
+ } catch {
9466
+ }
9467
+ }
9234
9468
  var SESSION_ATTACH_RETRY_MS = 100;
9235
9469
  var DEFAULT_STOP_WAIT_TIMEOUT_MS = 1e4;
9236
9470
  var STOP_WAIT_POLL_MS = 50;
9471
+ function stableTraceComponent(value) {
9472
+ return String(value ?? "unknown").trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "unknown";
9473
+ }
9237
9474
  function errorMessage(error) {
9238
9475
  return error?.message ?? String(error ?? "unknown error");
9239
9476
  }
@@ -9569,6 +9806,8 @@ async function runScheduledIteration(emitter, context) {
9569
9806
  emitter.status = EMITTER_STATUS.RUNNING;
9570
9807
  emitter.runCount += 1;
9571
9808
  emitter.lastRunAt = nowIso();
9809
+ const traceStartedAt = emitter.lastRunAt;
9810
+ const traceRunIndex = emitter.runCount;
9572
9811
  let result;
9573
9812
  try {
9574
9813
  result = emitter.emitterType === EMITTER_TYPE.PROMPT ? await runPromptIteration(emitter, context) : await runCommandLoopIteration(emitter, context);
@@ -9586,6 +9825,12 @@ async function runScheduledIteration(emitter, context) {
9586
9825
  if (result?.consumeRun === false) {
9587
9826
  emitter.runCount = previousRunCount;
9588
9827
  emitter.lastRunAt = previousLastRunAt;
9828
+ recordScheduledTrace(emitter, context, {
9829
+ startedAt: traceStartedAt,
9830
+ runIndex: traceRunIndex,
9831
+ result,
9832
+ consumedRun: false
9833
+ });
9589
9834
  if (emitter.stopRequested) {
9590
9835
  applyLifecycleTransition(emitter, {
9591
9836
  type: LIFECYCLE_EVENT.ITERATION_RESULT,
@@ -9604,7 +9849,20 @@ async function runScheduledIteration(emitter, context) {
9604
9849
  result,
9605
9850
  timestamp: nowIso()
9606
9851
  }, context);
9852
+ recordScheduledTrace(emitter, context, {
9853
+ startedAt: traceStartedAt,
9854
+ runIndex: traceRunIndex,
9855
+ result,
9856
+ consumedRun: true
9857
+ });
9607
9858
  } catch (error) {
9859
+ recordScheduledTrace(emitter, context, {
9860
+ startedAt: traceStartedAt,
9861
+ runIndex: traceRunIndex,
9862
+ result: { ok: false, error: errorMessage(error) },
9863
+ error: errorMessage(error),
9864
+ consumedRun: true
9865
+ });
9608
9866
  recordScheduledTransitionFailure(emitter, error, context);
9609
9867
  } finally {
9610
9868
  emitter.inFlight = false;
@@ -9618,7 +9876,8 @@ function createLifecycle({
9618
9876
  sessionPort,
9619
9877
  timerAdapter = createDefaultTimerAdapter(),
9620
9878
  processAdapter = createDefaultProcessAdapter(),
9621
- loggerAdapter = createDefaultLoggerAdapter(sessionPort)
9879
+ loggerAdapter = createDefaultLoggerAdapter(sessionPort),
9880
+ diagnostics: diagnostics2 = null
9622
9881
  }) {
9623
9882
  function start(emitter) {
9624
9883
  if (emitter.runSchedule === RUN_SCHEDULE.CONTINUOUS) {
@@ -9628,6 +9887,7 @@ function createLifecycle({
9628
9887
  contextStartScheduled(emitter);
9629
9888
  }
9630
9889
  function contextStartScheduled(emitter) {
9890
+ const context = { lineRouter, timerAdapter, processAdapter, loggerAdapter, sessionPort, diagnostics: diagnostics2 };
9631
9891
  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;
9632
9892
  lineRouter.appendSystemMessage(
9633
9893
  emitter,
@@ -9640,17 +9900,17 @@ function createLifecycle({
9640
9900
  );
9641
9901
  if (emitter.runSchedule === RUN_SCHEDULE.IDLE) {
9642
9902
  if (sessionPort.isIdle()) {
9643
- scheduleIteration(emitter, { lineRouter, timerAdapter, processAdapter, loggerAdapter, sessionPort }, IDLE_PROMPT_DELAY_MS);
9903
+ scheduleIteration(emitter, context, IDLE_PROMPT_DELAY_MS);
9644
9904
  }
9645
9905
  return;
9646
9906
  }
9647
- scheduleIteration(emitter, { lineRouter, timerAdapter, processAdapter, loggerAdapter, sessionPort }, 0);
9907
+ scheduleIteration(emitter, context, 0);
9648
9908
  }
9649
9909
  async function stop(emitter) {
9650
9910
  applyLifecycleTransition(
9651
9911
  emitter,
9652
9912
  { type: LIFECYCLE_EVENT.STOP, timestamp: nowIso() },
9653
- { lineRouter, timerAdapter, processAdapter, loggerAdapter, sessionPort }
9913
+ { lineRouter, timerAdapter, processAdapter, loggerAdapter, sessionPort, diagnostics: diagnostics2 }
9654
9914
  );
9655
9915
  if (isTerminalEmitterStatus(emitter.status)) {
9656
9916
  return;
@@ -9826,14 +10086,14 @@ function restorePersistentStreamConfigBestEffort(configStore, snapshot) {
9826
10086
 
9827
10087
  // src/emitter/supervisor.mjs
9828
10088
  var ROLLBACK_STOP_WAIT_TIMEOUT_MS = 1e4;
9829
- function createEmitterSupervisor({ streams, configStore, notifications, sessionPort, emitterWorkspace, persist, lifecycle: lifecycleOverride }) {
10089
+ function createEmitterSupervisor({ streams, configStore, notifications, sessionPort, emitterWorkspace, persist, lifecycle: lifecycleOverride, diagnostics: diagnostics2 = null }) {
9830
10090
  const emitters = /* @__PURE__ */ new Map();
9831
10091
  const lineRouter = createLineRouter({
9832
10092
  streams,
9833
10093
  notifications,
9834
10094
  surface: (message, options) => sessionPort.log(message, options)
9835
10095
  });
9836
- const lifecycle = lifecycleOverride ?? createLifecycle({ lineRouter, sessionPort });
10096
+ const lifecycle = lifecycleOverride ?? createLifecycle({ lineRouter, sessionPort, diagnostics: diagnostics2 });
9837
10097
  function hasExplicitPolicyValue(value) {
9838
10098
  return value !== void 0 && value !== null;
9839
10099
  }
@@ -10367,6 +10627,41 @@ function createSessionPort(initialSession = null) {
10367
10627
  }
10368
10628
  return canvasApi.open(params);
10369
10629
  }
10630
+ async function getRuntimeState() {
10631
+ if (!session2) {
10632
+ throw new LifecycleError("Session is not attached; cannot inspect runtime state.");
10633
+ }
10634
+ const rpc = session2.rpc ?? {};
10635
+ const read = async (label, fn) => {
10636
+ try {
10637
+ return { ok: true, value: await fn() };
10638
+ } catch (error) {
10639
+ return { ok: false, error: error?.message ?? String(error ?? "unknown error") };
10640
+ }
10641
+ };
10642
+ return {
10643
+ sessionId: session2.sessionId ?? null,
10644
+ capabilities: session2.capabilities ?? null,
10645
+ mode: await read("mode", () => rpc.mode?.get?.()),
10646
+ model: await read("model", () => rpc.model?.getCurrent?.()),
10647
+ tasks: await read("tasks", () => rpc.tasks?.list?.()),
10648
+ schedules: await read("schedules", () => rpc.schedule?.list?.()),
10649
+ skills: await read("skills", () => rpc.skills?.list?.()),
10650
+ permissions: await read("permissions", () => rpc.permissions?.pendingRequests?.()),
10651
+ openCanvases: await read("openCanvases", () => rpc.canvas?.listOpen?.())
10652
+ };
10653
+ }
10654
+ async function setMode(mode) {
10655
+ if (!session2) {
10656
+ throw new LifecycleError("Session is not attached; cannot set mode.");
10657
+ }
10658
+ const modeApi = session2.rpc?.mode;
10659
+ if (!modeApi || typeof modeApi.set !== "function") {
10660
+ throw new LifecycleError("Session mode API is not available in this Copilot session.");
10661
+ }
10662
+ await modeApi.set({ mode });
10663
+ return modeApi.get();
10664
+ }
10370
10665
  function registerTools(tools) {
10371
10666
  if (!session2) return;
10372
10667
  try {
@@ -10403,6 +10698,8 @@ function createSessionPort(initialSession = null) {
10403
10698
  send,
10404
10699
  sendAndWait,
10405
10700
  openCanvas,
10701
+ getRuntimeState,
10702
+ setMode,
10406
10703
  registerTools,
10407
10704
  reloadExtension
10408
10705
  };
@@ -10683,7 +10980,8 @@ function createRuntimeSubsystems(options = {}) {
10683
10980
  notifications,
10684
10981
  sessionPort,
10685
10982
  emitterWorkspace,
10686
- persist
10983
+ persist,
10984
+ diagnostics: options.diagnostics
10687
10985
  });
10688
10986
  return {
10689
10987
  sessionContext,
@@ -10772,7 +11070,7 @@ function projectStream(stream) {
10772
11070
 
10773
11071
  // src/services/stream-service.mjs
10774
11072
  function createStreamService(deps) {
10775
- const { streams, configStore, sessionPort, persist } = deps;
11073
+ const { streams, configStore, sessionPort, persist, recordStore = null } = deps;
10776
11074
  function requireStreamChannel(channel, operation) {
10777
11075
  return requireNormalizedName(channel, {
10778
11076
  label: "Stream channel",
@@ -10816,6 +11114,17 @@ function createStreamService(deps) {
10816
11114
  context: { channel: stream.name }
10817
11115
  });
10818
11116
  }
11117
+ try {
11118
+ recordStore?.appendRecord?.("stream-posts", {
11119
+ channel: stream.name,
11120
+ source,
11121
+ text,
11122
+ timestamp: appended.timestamp,
11123
+ monitorName: appended.monitorName ?? null,
11124
+ metadata: appended.metadata ?? null
11125
+ });
11126
+ } catch {
11127
+ }
10819
11128
  void sessionPort.log(`Posted message to stream '${stream.name}'.`);
10820
11129
  return { stream: projectStream(stream) };
10821
11130
  }
@@ -10858,10 +11167,256 @@ function createStreamService(deps) {
10858
11167
  };
10859
11168
  }
10860
11169
 
11170
+ // src/services/goal-verification-service.mjs
11171
+ import fs2 from "node:fs";
11172
+ import path7 from "node:path";
11173
+ function normalizeText(value) {
11174
+ return String(value ?? "").trim();
11175
+ }
11176
+ function safeRegex(pattern) {
11177
+ try {
11178
+ return new RegExp(String(pattern));
11179
+ } catch {
11180
+ return null;
11181
+ }
11182
+ }
11183
+ function resolveWithinBase(baseCwd, requestedPath) {
11184
+ const raw = normalizeText(requestedPath);
11185
+ if (!raw) {
11186
+ return { ok: false, error: "path is required" };
11187
+ }
11188
+ const base = path7.resolve(baseCwd || process.cwd());
11189
+ const resolved = path7.isAbsolute(raw) ? path7.resolve(raw) : path7.resolve(base, raw);
11190
+ const relative = path7.relative(base, resolved);
11191
+ if (relative.startsWith("..") || path7.isAbsolute(relative)) {
11192
+ return { ok: false, error: `path '${raw}' is outside the session workspace` };
11193
+ }
11194
+ return { ok: true, path: resolved, displayPath: relative || "." };
11195
+ }
11196
+ function textMatches(text, check = {}) {
11197
+ const contains = normalizeText(check.contains);
11198
+ if (contains && !String(text).includes(contains)) {
11199
+ return { ok: false, error: `expected text to contain '${contains}'` };
11200
+ }
11201
+ const pattern = normalizeText(check.pattern);
11202
+ if (pattern) {
11203
+ const regex = safeRegex(pattern);
11204
+ if (!regex) {
11205
+ return { ok: false, error: `invalid regex '${pattern}'` };
11206
+ }
11207
+ if (!regex.test(String(text))) {
11208
+ return { ok: false, error: `expected text to match /${pattern}/` };
11209
+ }
11210
+ }
11211
+ return { ok: true };
11212
+ }
11213
+ function verifyFile(check, { baseCwd }) {
11214
+ const resolved = resolveWithinBase(baseCwd, check.path);
11215
+ if (!resolved.ok) {
11216
+ return { ...resolved, passed: false };
11217
+ }
11218
+ if (!fs2.existsSync(resolved.path)) {
11219
+ return { passed: false, path: resolved.displayPath, error: "file does not exist" };
11220
+ }
11221
+ const stat = fs2.statSync(resolved.path);
11222
+ if (!stat.isFile()) {
11223
+ return { passed: false, path: resolved.displayPath, error: "path is not a file" };
11224
+ }
11225
+ if (check.nonEmpty === true && stat.size === 0) {
11226
+ return { passed: false, path: resolved.displayPath, error: "file is empty" };
11227
+ }
11228
+ if (check.contains || check.pattern) {
11229
+ const content = fs2.readFileSync(resolved.path, "utf8");
11230
+ const match = textMatches(content, check);
11231
+ if (!match.ok) {
11232
+ return { passed: false, path: resolved.displayPath, error: match.error };
11233
+ }
11234
+ }
11235
+ return { passed: true, path: resolved.displayPath, size: stat.size };
11236
+ }
11237
+ function verifyStream(check, { getStreamHistory }) {
11238
+ const channel = normalizeText(check.channel);
11239
+ if (!channel) {
11240
+ return { passed: false, error: "channel is required" };
11241
+ }
11242
+ let stream;
11243
+ try {
11244
+ stream = getStreamHistory(channel, check.limit)?.stream;
11245
+ } catch (error) {
11246
+ return { passed: false, channel, error: error?.message ?? String(error) };
11247
+ }
11248
+ const entries = Array.isArray(stream?.entries) ? stream.entries : [];
11249
+ if (check.minEntries !== void 0 && entries.length < Number(check.minEntries)) {
11250
+ return { passed: false, channel, entries: entries.length, error: `expected at least ${check.minEntries} entries` };
11251
+ }
11252
+ const text = entries.map((entry) => entry.text ?? "").join("\n");
11253
+ const match = textMatches(text, check);
11254
+ if (!match.ok) {
11255
+ return { passed: false, channel, entries: entries.length, error: match.error };
11256
+ }
11257
+ return { passed: true, channel, entries: entries.length };
11258
+ }
11259
+ function verifyCommandEvidence(check) {
11260
+ const label = normalizeText(check.label ?? check.command ?? "command evidence");
11261
+ if (check.exitCode === void 0 && check.success !== true) {
11262
+ return { passed: false, label, error: "provide exitCode or success=true from an already-run command" };
11263
+ }
11264
+ const passed = check.success === true || Number(check.exitCode) === 0;
11265
+ return {
11266
+ passed,
11267
+ label,
11268
+ exitCode: check.exitCode ?? null,
11269
+ error: passed ? null : `command evidence did not indicate success`
11270
+ };
11271
+ }
11272
+ function verifyCheck(check = {}, context) {
11273
+ const type = normalizeText(check.type || check.kind);
11274
+ if (type === "file" || type === "file_exists") {
11275
+ return verifyFile(check, context);
11276
+ }
11277
+ if (type === "stream" || type === "stream_contains" || type === "channel") {
11278
+ return verifyStream(check, context);
11279
+ }
11280
+ if (type === "command" || type === "command_evidence") {
11281
+ return verifyCommandEvidence(check);
11282
+ }
11283
+ return { passed: false, error: `unsupported check type '${type || "<missing>"}'` };
11284
+ }
11285
+ function createGoalVerificationService({ getBaseCwd, getStreamHistory } = {}) {
11286
+ function context() {
11287
+ return {
11288
+ baseCwd: typeof getBaseCwd === "function" ? getBaseCwd() : process.cwd(),
11289
+ getStreamHistory
11290
+ };
11291
+ }
11292
+ function verifyGoalOutput(input = {}) {
11293
+ const checks = Array.isArray(input.checks) ? input.checks : [];
11294
+ const results = checks.map((check, index) => ({
11295
+ index,
11296
+ description: check.description ?? check.claim ?? null,
11297
+ type: check.type ?? check.kind ?? null,
11298
+ ...verifyCheck(check, context())
11299
+ }));
11300
+ return {
11301
+ passed: results.length > 0 && results.every((result) => result.passed === true),
11302
+ results
11303
+ };
11304
+ }
11305
+ function auditClaims(input = {}) {
11306
+ const claims = Array.isArray(input.claims) ? input.claims : [];
11307
+ const results = claims.map((claim, index) => {
11308
+ const evidence = claim.evidence && typeof claim.evidence === "object" ? claim.evidence : {};
11309
+ const verification = verifyCheck({
11310
+ ...evidence,
11311
+ description: evidence.description ?? claim.claim,
11312
+ claim: claim.claim
11313
+ }, context());
11314
+ return {
11315
+ index,
11316
+ claim: claim.claim ?? null,
11317
+ status: verification.passed ? "confirmed" : "blocked",
11318
+ ...verification
11319
+ };
11320
+ });
11321
+ return {
11322
+ passed: results.length > 0 && results.every((result) => result.passed === true),
11323
+ results
11324
+ };
11325
+ }
11326
+ return { verifyGoalOutput, auditClaims };
11327
+ }
11328
+
11329
+ // src/services/structured-record-store.mjs
11330
+ import fs3 from "node:fs";
11331
+ import path8 from "node:path";
11332
+ var DEFAULT_COLLECTION_LIMIT = 500;
11333
+ var COLLECTION_NAME_PATTERN = /^[a-z][a-z0-9-]{0,63}$/;
11334
+ function safeCollectionName(value) {
11335
+ const name = String(value ?? "").trim().toLowerCase();
11336
+ return COLLECTION_NAME_PATTERN.test(name) ? name : null;
11337
+ }
11338
+ function sessionWorkspacePath(sessionPort) {
11339
+ const session2 = typeof sessionPort?.current === "function" ? sessionPort.current() : null;
11340
+ return session2?.workspacePath ?? null;
11341
+ }
11342
+ function recordRoot(sessionPort) {
11343
+ const workspace = sessionWorkspacePath(sessionPort);
11344
+ if (!workspace) {
11345
+ return null;
11346
+ }
11347
+ return path8.join(workspace, "files", "tap-records");
11348
+ }
11349
+ function collectionPath(root, collection) {
11350
+ return path8.join(root, `${collection}.jsonl`);
11351
+ }
11352
+ function trimJsonlFile(filePath, maxRecords = DEFAULT_COLLECTION_LIMIT) {
11353
+ try {
11354
+ const lines = fs3.readFileSync(filePath, "utf8").split(/\r?\n/).filter(Boolean);
11355
+ if (lines.length <= maxRecords) {
11356
+ return;
11357
+ }
11358
+ fs3.writeFileSync(filePath, `${lines.slice(-maxRecords).join("\n")}
11359
+ `, "utf8");
11360
+ } catch {
11361
+ }
11362
+ }
11363
+ function createStructuredRecordStore({ sessionPort, maxRecords = DEFAULT_COLLECTION_LIMIT } = {}) {
11364
+ function appendRecord(collectionInput, record) {
11365
+ const collection = safeCollectionName(collectionInput);
11366
+ const root = recordRoot(sessionPort);
11367
+ if (!collection || !root) {
11368
+ return { stored: false, reason: !collection ? "invalid-collection" : "no-session-workspace" };
11369
+ }
11370
+ try {
11371
+ fs3.mkdirSync(root, { recursive: true });
11372
+ const filePath = collectionPath(root, collection);
11373
+ fs3.appendFileSync(filePath, `${JSON.stringify({ ...record, storedAt: (/* @__PURE__ */ new Date()).toISOString() })}
11374
+ `, "utf8");
11375
+ trimJsonlFile(filePath, maxRecords);
11376
+ return { stored: true, collection, path: filePath };
11377
+ } catch (error) {
11378
+ return { stored: false, collection, reason: error?.message ?? String(error ?? "unknown error") };
11379
+ }
11380
+ }
11381
+ function listRecords(collectionInput, options = {}) {
11382
+ const collection = safeCollectionName(collectionInput);
11383
+ const root = recordRoot(sessionPort);
11384
+ if (!collection || !root) {
11385
+ return { collection: collectionInput, records: [], available: false };
11386
+ }
11387
+ const limit = Math.max(1, Math.min(500, Math.floor(Number(options.limit ?? 50) || 50)));
11388
+ const filePath = collectionPath(root, collection);
11389
+ try {
11390
+ const records = fs3.readFileSync(filePath, "utf8").split(/\r?\n/).filter(Boolean).slice(-limit).map((line) => {
11391
+ try {
11392
+ return JSON.parse(line);
11393
+ } catch {
11394
+ return { parseError: true, raw: line };
11395
+ }
11396
+ });
11397
+ return { collection, path: filePath, available: true, records };
11398
+ } catch (error) {
11399
+ if (error?.code === "ENOENT") {
11400
+ return { collection, path: filePath, available: true, records: [] };
11401
+ }
11402
+ return { collection, path: filePath, available: false, records: [], error: error?.message ?? String(error) };
11403
+ }
11404
+ }
11405
+ function getRoot() {
11406
+ return recordRoot(sessionPort);
11407
+ }
11408
+ return {
11409
+ appendRecord,
11410
+ listRecords,
11411
+ getRoot
11412
+ };
11413
+ }
11414
+
10861
11415
  // src/diagnostics/store.mjs
10862
11416
  var DEFAULT_MAX_LOGS = 300;
10863
11417
  var DEFAULT_MAX_EVENTS = 300;
10864
11418
  var DEFAULT_MAX_RUNTIME_EVENTS = 300;
11419
+ var DEFAULT_MAX_TRACES = 300;
10865
11420
  var MAX_STRING_LENGTH = 1200;
10866
11421
  var MAX_COLLECTION_ITEMS = 40;
10867
11422
  var MAX_DEPTH = 4;
@@ -10997,12 +11552,15 @@ function createDiagnosticsStore(options = {}) {
10997
11552
  const logs = createRingBuffer(options.maxLogs ?? DEFAULT_MAX_LOGS);
10998
11553
  const sessionEvents = createRingBuffer(options.maxSessionEvents ?? DEFAULT_MAX_EVENTS);
10999
11554
  const runtimeEvents = createRingBuffer(options.maxRuntimeEvents ?? DEFAULT_MAX_RUNTIME_EVENTS);
11555
+ const traces = createRingBuffer(options.maxTraces ?? DEFAULT_MAX_TRACES);
11000
11556
  const sessionEventCounts = /* @__PURE__ */ new Map();
11001
11557
  let logCount = 0;
11002
11558
  let runtimeEventCount = 0;
11003
11559
  let sessionEventCount = 0;
11560
+ let traceCount = 0;
11004
11561
  let cleanupSessionListener = () => {
11005
11562
  };
11563
+ let recordSink = typeof options.recordSink === "function" ? options.recordSink : null;
11006
11564
  function recordLog(source, message, options2 = {}) {
11007
11565
  logCount += 1;
11008
11566
  return logs.append({
@@ -11032,6 +11590,49 @@ function createDiagnosticsStore(options = {}) {
11032
11590
  })
11033
11591
  });
11034
11592
  }
11593
+ function recordTrace(trace = {}) {
11594
+ traceCount += 1;
11595
+ const startedAt = trace.startedAt ?? trace.timestamp ?? nowIso();
11596
+ const endedAt = trace.endedAt ?? nowIso();
11597
+ const startMs = Date.parse(startedAt);
11598
+ const endMs = Date.parse(endedAt);
11599
+ const durationMs = Number.isFinite(startMs) && Number.isFinite(endMs) ? Math.max(0, endMs - startMs) : null;
11600
+ const entry = {
11601
+ id: createId("trace", traceCount),
11602
+ traceId: String(trace.traceId ?? `trace-${traceCount.toString(36)}`),
11603
+ timestamp: endedAt,
11604
+ startedAt,
11605
+ endedAt,
11606
+ durationMs,
11607
+ emitterId: trace.emitterId ?? trace.emitterName ?? null,
11608
+ emitterName: trace.emitterName ?? trace.emitterId ?? null,
11609
+ runIndex: Number.isFinite(Number(trace.runIndex)) ? Number(trace.runIndex) : null,
11610
+ emitterType: trace.emitterType ?? null,
11611
+ runSchedule: trace.runSchedule ?? null,
11612
+ status: trace.status ?? "unknown",
11613
+ ok: trace.ok === true,
11614
+ consumedRun: trace.consumedRun !== false,
11615
+ lineCount: Number.isFinite(Number(trace.lineCount)) ? Number(trace.lineCount) : null,
11616
+ droppedLineCount: Number.isFinite(Number(trace.droppedLineCount)) ? Number(trace.droppedLineCount) : null,
11617
+ error: trace.error ? truncateString(trace.error, MAX_STRING_LENGTH) : null,
11618
+ metadata: safeClone(trace.metadata ?? null, {
11619
+ maxDepth: 3,
11620
+ maxStringLength: 700,
11621
+ maxCollectionItems: 20
11622
+ }),
11623
+ spans: safeClone(Array.isArray(trace.spans) ? trace.spans : [], {
11624
+ maxDepth: 4,
11625
+ maxStringLength: 700,
11626
+ maxCollectionItems: 30
11627
+ })
11628
+ };
11629
+ const appended = traces.append(entry);
11630
+ try {
11631
+ recordSink?.("traces", entry);
11632
+ } catch {
11633
+ }
11634
+ return appended;
11635
+ }
11035
11636
  function recordSessionEvent(event) {
11036
11637
  sessionEventCount += 1;
11037
11638
  const type = String(event?.type ?? "unknown");
@@ -11074,11 +11675,13 @@ function createDiagnosticsStore(options = {}) {
11074
11675
  generatedAt: nowIso(),
11075
11676
  logs: logs.snapshot(options2.logLimit ?? 140),
11076
11677
  runtimeEvents: runtimeEvents.snapshot(options2.runtimeEventLimit ?? 140),
11678
+ traces: traces.snapshot(options2.traceLimit ?? 140),
11077
11679
  sessionEvents: sessionEvents.snapshot(options2.sessionEventLimit ?? 140),
11078
11680
  sessionEventCounts: Object.fromEntries([...sessionEventCounts.entries()].sort(([left], [right]) => left.localeCompare(right))),
11079
11681
  stats: {
11080
11682
  logs: logs.stats(),
11081
11683
  runtimeEvents: runtimeEvents.stats(),
11684
+ traces: traces.stats(),
11082
11685
  sessionEvents: sessionEvents.stats()
11083
11686
  }
11084
11687
  };
@@ -11086,6 +11689,10 @@ function createDiagnosticsStore(options = {}) {
11086
11689
  return {
11087
11690
  log: recordLog,
11088
11691
  event: recordRuntimeEvent,
11692
+ trace: recordTrace,
11693
+ setRecordSink: (nextSink) => {
11694
+ recordSink = typeof nextSink === "function" ? nextSink : null;
11695
+ },
11089
11696
  attachSession,
11090
11697
  detachSession,
11091
11698
  snapshot
@@ -11113,6 +11720,33 @@ function projectToolForDiagnostics(tool) {
11113
11720
  overridesBuiltInTool: tool.overridesBuiltInTool === true
11114
11721
  };
11115
11722
  }
11723
+ function collectEvalSummaries(baseCwd, limit = 20) {
11724
+ const root = path9.join(baseCwd, "evals", "results");
11725
+ const summaries = [];
11726
+ function walk(dir) {
11727
+ let entries = [];
11728
+ try {
11729
+ entries = fs4.readdirSync(dir, { withFileTypes: true });
11730
+ } catch {
11731
+ return;
11732
+ }
11733
+ for (const entry of entries) {
11734
+ const fullPath = path9.join(dir, entry.name);
11735
+ if (entry.isDirectory()) {
11736
+ walk(fullPath);
11737
+ } else if (entry.name === "summary.json") {
11738
+ try {
11739
+ const stat = fs4.statSync(fullPath);
11740
+ const parsed = JSON.parse(fs4.readFileSync(fullPath, "utf8"));
11741
+ summaries.push({ path: path9.relative(baseCwd, fullPath), modifiedAt: stat.mtime.toISOString(), summary: parsed });
11742
+ } catch {
11743
+ }
11744
+ }
11745
+ }
11746
+ }
11747
+ walk(root);
11748
+ return summaries.sort((left, right) => new Date(right.modifiedAt) - new Date(left.modifiedAt)).slice(0, limit);
11749
+ }
11116
11750
  function createTapRuntimeService(options = {}) {
11117
11751
  const diagnosticsStore = options.diagnostics ?? createDiagnosticsStore();
11118
11752
  const {
@@ -11125,12 +11759,18 @@ function createTapRuntimeService(options = {}) {
11125
11759
  configWorkspace,
11126
11760
  emitterWorkspace,
11127
11761
  persist
11128
- } = createRuntimeSubsystems(options);
11762
+ } = createRuntimeSubsystems({
11763
+ ...options,
11764
+ diagnostics: diagnosticsStore
11765
+ });
11766
+ const recordStore = createStructuredRecordStore({ sessionPort });
11767
+ diagnosticsStore.setRecordSink?.((collection, record) => recordStore.appendRecord(collection, record));
11129
11768
  const streamService = createStreamService({
11130
11769
  streams,
11131
11770
  configStore,
11132
11771
  sessionPort,
11133
- persist
11772
+ persist,
11773
+ recordStore
11134
11774
  });
11135
11775
  const emitterService = createEmitterService({
11136
11776
  streams,
@@ -11138,6 +11778,10 @@ function createTapRuntimeService(options = {}) {
11138
11778
  supervisor,
11139
11779
  emitterWorkspace
11140
11780
  });
11781
+ const goalVerificationService = createGoalVerificationService({
11782
+ getBaseCwd: sessionContext.getBaseCwd,
11783
+ getStreamHistory: (channel, limit) => streamService.getStreamHistory(channel, limit)
11784
+ });
11141
11785
  const configBootstrapService = createConfigBootstrapService({
11142
11786
  streams,
11143
11787
  configStore,
@@ -11145,7 +11789,8 @@ function createTapRuntimeService(options = {}) {
11145
11789
  sessionPort,
11146
11790
  configWorkspace
11147
11791
  });
11148
- const { loadPersistentConfig } = configBootstrapService;
11792
+ const { loadPersistentConfig: loadPersistentConfigRaw } = configBootstrapService;
11793
+ let persistentConfigLoadPromise = null;
11149
11794
  const sessionActivityBridge = createSessionActivityBridge({ sessionPort, supervisor });
11150
11795
  const providerPushService = createProviderPushService({
11151
11796
  streams,
@@ -11157,11 +11802,27 @@ function createTapRuntimeService(options = {}) {
11157
11802
  const session2 = sessionPort.current();
11158
11803
  return sessionContext.getSessionInfo(session2);
11159
11804
  }
11805
+ function loadPersistentConfigOnce(inputCwd) {
11806
+ if (persistentConfigLoadPromise) {
11807
+ return persistentConfigLoadPromise;
11808
+ }
11809
+ persistentConfigLoadPromise = Promise.resolve().then(() => loadPersistentConfigRaw(inputCwd)).catch((error) => {
11810
+ persistentConfigLoadPromise = null;
11811
+ throw error;
11812
+ });
11813
+ return persistentConfigLoadPromise;
11814
+ }
11160
11815
  function attachSession(session2) {
11161
11816
  sessionContext.attachSession(session2);
11162
11817
  sessionPort.attach(session2);
11163
11818
  sessionActivityBridge.attach(session2);
11164
11819
  diagnosticsStore.attachSession(session2);
11820
+ void loadPersistentConfigOnce(sessionContext.getBaseCwd()).then((summary) => {
11821
+ void sessionPort.log(summary);
11822
+ }).catch((error) => {
11823
+ const message = error?.message ?? String(error ?? "unknown error");
11824
+ void sessionPort.log(`Config load failed during extension attach: ${message}`, { level: "warning" });
11825
+ });
11165
11826
  }
11166
11827
  function clearNotificationsForLifecycle(options2 = {}) {
11167
11828
  if (options2.clearNotifications === true && typeof notifications.clear === "function") {
@@ -11216,7 +11877,7 @@ function createTapRuntimeService(options = {}) {
11216
11877
  } = createRuntimeHooks({
11217
11878
  streams,
11218
11879
  sessionPort,
11219
- loadPersistentConfig,
11880
+ loadPersistentConfig: loadPersistentConfigOnce,
11220
11881
  stopAllEmitters,
11221
11882
  stopAllEmittersAndWait,
11222
11883
  shutdownSession,
@@ -11245,8 +11906,16 @@ function createTapRuntimeService(options = {}) {
11245
11906
  void sessionPort.reloadExtension();
11246
11907
  }
11247
11908
  };
11248
- function getDiagnosticsSnapshot(options2 = {}, extra = {}) {
11909
+ async function getDiagnosticsSnapshot(options2 = {}, extra = {}) {
11249
11910
  const allTools = Array.isArray(extra.tools) ? extra.tools.map(projectToolForDiagnostics) : [];
11911
+ let sessionRuntime = null;
11912
+ try {
11913
+ sessionRuntime = await sessionPort.getRuntimeState();
11914
+ } catch (error) {
11915
+ sessionRuntime = {
11916
+ error: error?.message ?? String(error ?? "unknown error")
11917
+ };
11918
+ }
11250
11919
  return {
11251
11920
  generatedAt: nowIso(),
11252
11921
  session: getSessionInfo(),
@@ -11260,7 +11929,9 @@ function createTapRuntimeService(options = {}) {
11260
11929
  emitters: emitterCapabilities.listEmitters(),
11261
11930
  gateway: extra.gateway ?? null,
11262
11931
  tools: allTools,
11932
+ evals: collectEvalSummaries(sessionContext.getBaseCwd(), options2.evalLimit ?? 20),
11263
11933
  notifications: typeof notifications.snapshot === "function" ? notifications.snapshot({ limit: options2.notificationLimit ?? 20 }) : null,
11934
+ sessionRuntime,
11264
11935
  diagnostics: diagnosticsStore.snapshot(options2)
11265
11936
  };
11266
11937
  }
@@ -11279,6 +11950,10 @@ function createTapRuntimeService(options = {}) {
11279
11950
  input: canvasInput
11280
11951
  });
11281
11952
  },
11953
+ getSessionRuntimeState: () => sessionPort.getRuntimeState(),
11954
+ setSessionMode: (mode) => sessionPort.setMode(mode),
11955
+ queryRecords: (collection, options2 = {}) => recordStore.listRecords(collection, options2),
11956
+ getRecordRoot: () => recordStore.getRoot(),
11282
11957
  attachSession: diagnosticsStore.attachSession,
11283
11958
  detachSession: diagnosticsStore.detachSession
11284
11959
  };
@@ -11286,12 +11961,14 @@ function createTapRuntimeService(options = {}) {
11286
11961
  tools: {
11287
11962
  streams: streamCapabilities,
11288
11963
  emitters: emitterCapabilities,
11289
- diagnostics: diagnosticsCapabilities
11964
+ diagnostics: diagnosticsCapabilities,
11965
+ verification: goalVerificationService
11290
11966
  },
11291
11967
  hooks: hookCapabilities,
11292
11968
  session: sessionCapabilities,
11293
11969
  provider: providerCapabilities,
11294
11970
  diagnostics: diagnosticsCapabilities,
11971
+ verification: goalVerificationService,
11295
11972
  getBaseCwd: sessionContext.getBaseCwd,
11296
11973
  getSessionInfo,
11297
11974
  attachSession,
@@ -11300,7 +11977,7 @@ function createTapRuntimeService(options = {}) {
11300
11977
  appendStreamMessage,
11301
11978
  getSessionStartContext,
11302
11979
  getPromptContext,
11303
- loadPersistentConfig,
11980
+ loadPersistentConfig: loadPersistentConfigOnce,
11304
11981
  listEmitters: emitterCapabilities.listEmitters,
11305
11982
  listStreams: streamCapabilities.listStreams,
11306
11983
  postToStream: streamCapabilities.postToStream,
@@ -11341,6 +12018,7 @@ function sanitizeSnapshotOptions(input = {}) {
11341
12018
  return {
11342
12019
  streamEntryLimit: Number.isFinite(limit) ? Math.max(10, Math.min(200, Math.floor(limit))) : 80,
11343
12020
  logLimit: Number.isFinite(limit) ? Math.max(20, Math.min(300, Math.floor(limit))) : 160,
12021
+ traceLimit: Number.isFinite(limit) ? Math.max(20, Math.min(300, Math.floor(limit))) : 160,
11344
12022
  sessionEventLimit: Number.isFinite(limit) ? Math.max(20, Math.min(300, Math.floor(limit))) : 160,
11345
12023
  runtimeEventLimit: Number.isFinite(limit) ? Math.max(20, Math.min(300, Math.floor(limit))) : 160
11346
12024
  };
@@ -11351,8 +12029,9 @@ function summarizeSnapshot(snapshot) {
11351
12029
  const streams = Array.isArray(snapshot?.streams) ? snapshot.streams.length : 0;
11352
12030
  const providers = Array.isArray(snapshot?.gateway?.providers) ? snapshot.gateway.providers.length : 0;
11353
12031
  const logs = snapshot?.diagnostics?.stats?.logs?.retained ?? 0;
12032
+ const traces = snapshot?.diagnostics?.stats?.traces?.retained ?? 0;
11354
12033
  const sessionEvents = snapshot?.diagnostics?.stats?.sessionEvents?.retained ?? 0;
11355
- return { streams, runningEmitters, configuredEmitters, providers, logs, sessionEvents };
12034
+ return { streams, runningEmitters, configuredEmitters, providers, logs, traces, sessionEvents };
11356
12035
  }
11357
12036
  function createHtml() {
11358
12037
  return `<!doctype html>
@@ -11582,7 +12261,7 @@ function createHtml() {
11582
12261
  overflow: auto;
11583
12262
  }
11584
12263
 
11585
- .stream-grid, .emitter-grid, .provider-grid {
12264
+ .stream-grid, .emitter-grid, .provider-grid, .trace-grid, .session-grid {
11586
12265
  display: grid;
11587
12266
  gap: 12px;
11588
12267
  }
@@ -11754,6 +12433,36 @@ function createHtml() {
11754
12433
  </div>
11755
12434
  </section>
11756
12435
 
12436
+ <section class="panel">
12437
+ <div class="panel-head">
12438
+ <h2>Goals and traces</h2>
12439
+ <small id="trace-count">0 traces</small>
12440
+ </div>
12441
+ <div class="panel-body">
12442
+ <div class="trace-grid" id="traces"></div>
12443
+ </div>
12444
+ </section>
12445
+
12446
+ <section class="panel">
12447
+ <div class="panel-head">
12448
+ <h2>Session control</h2>
12449
+ <small id="session-state">unknown</small>
12450
+ </div>
12451
+ <div class="panel-body">
12452
+ <div class="session-grid" id="session-control"></div>
12453
+ </div>
12454
+ </section>
12455
+
12456
+ <section class="panel">
12457
+ <div class="panel-head">
12458
+ <h2>Eval gate</h2>
12459
+ <small id="eval-count">0 summaries</small>
12460
+ </div>
12461
+ <div class="panel-body">
12462
+ <div class="session-grid" id="evals"></div>
12463
+ </div>
12464
+ </section>
12465
+
11757
12466
  <section class="panel timeline-panel">
11758
12467
  <div class="panel-head">
11759
12468
  <h2>Evidence timeline</h2>
@@ -11790,12 +12499,18 @@ function createHtml() {
11790
12499
  const streams = snapshot.streams?.length ?? 0;
11791
12500
  const providers = snapshot.gateway?.providers?.length ?? 0;
11792
12501
  const queue = snapshot.notifications?.queueSize ?? 0;
12502
+ const traces = snapshot.diagnostics?.stats?.traces?.retained ?? 0;
12503
+ const tasks = snapshot.sessionRuntime?.tasks?.ok ? (snapshot.sessionRuntime.tasks.value?.tasks?.length ?? 0) : 0;
12504
+ const evals = snapshot.evals?.length ?? 0;
11793
12505
  const sessionEvents = snapshot.diagnostics?.stats?.sessionEvents?.retained ?? 0;
11794
12506
  el("metrics").innerHTML = [
11795
12507
  metric("streams", streams),
11796
12508
  metric("running emitters", running),
11797
12509
  metric("providers", providers),
11798
12510
  metric("queued injections", queue),
12511
+ metric("traces", traces),
12512
+ metric("tasks", tasks),
12513
+ metric("eval summaries", evals),
11799
12514
  metric("configured emitters", configured),
11800
12515
  metric("session events", sessionEvents)
11801
12516
  ].join("");
@@ -11835,6 +12550,72 @@ function createHtml() {
11835
12550
  el("providers").innerHTML = gatewayRecord + rows;
11836
12551
  }
11837
12552
 
12553
+ function extractMarkedJson(text, marker) {
12554
+ const raw = String(text ?? "");
12555
+ const start = "=== BEGIN_" + marker + " ===";
12556
+ const end = "=== END_" + marker + " ===";
12557
+ const startIndex = raw.indexOf(start);
12558
+ const endIndex = raw.indexOf(end);
12559
+ if (startIndex < 0 || endIndex < 0 || endIndex <= startIndex) return null;
12560
+ const body = raw.slice(startIndex + start.length, endIndex).trim();
12561
+ try { return JSON.parse(body); } catch { return null; }
12562
+ }
12563
+
12564
+ function collectReviewRecords(snapshot) {
12565
+ const records = [];
12566
+ for (const stream of snapshot.streams ?? []) {
12567
+ for (const entry of stream.entries ?? []) {
12568
+ const parsed = extractMarkedJson(entry.text, "REVIEW_RECORD");
12569
+ if (parsed) records.push({ stream: stream.name, timestamp: entry.timestamp, ...parsed });
12570
+ }
12571
+ }
12572
+ return records.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
12573
+ }
12574
+
12575
+ function renderTraces(snapshot) {
12576
+ const traces = snapshot.diagnostics?.traces ?? [];
12577
+ const goalStreams = (snapshot.streams ?? []).filter((stream) => String(stream.name ?? "").startsWith("goal-"));
12578
+ el("trace-count").textContent = traces.length + " traces";
12579
+ const traceRows = traces.slice(-12).reverse().map((trace) => {
12580
+ const tone = trace.ok ? "ready" : trace.status === "deferred" ? "warning" : "error";
12581
+ 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>';
12582
+ }).join("");
12583
+ const goalRows = goalStreams.map((stream) => {
12584
+ 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("");
12585
+ 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>';
12586
+ }).join("");
12587
+ const reviewRecords = collectReviewRecords(snapshot).slice(0, 8).map((record) => '<div class="record"><strong>' + escapeHtml(record.stream) + ' <span class="badge info">' + escapeHtml(record.issue_type ?? "review") + '</span></strong><div class="meta">entries=' + escapeHtml(record.entries_examined ?? "?") + ' | reviewed=' + escapeHtml(timeOnly(record.reviewed_at ?? record.timestamp)) + '</div><div class="details">' + escapeHtml(compactJson({ patterns_changed: record.patterns_changed ?? [], remaining_noise_delta: record.remaining_noise_delta ?? [] })) + '</div></div>').join("");
12588
+ 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 : "") + (reviewRecords ? '<div class="panel-head" style="margin:14px -14px 12px"><h2>Monitor reviews</h2><small>' + collectReviewRecords(snapshot).length + ' records</small></div>' + reviewRecords : "");
12589
+ }
12590
+
12591
+ function renderSessionControl(snapshot) {
12592
+ const runtime = snapshot.sessionRuntime ?? {};
12593
+ const mode = runtime.mode?.ok ? runtime.mode.value : "unknown";
12594
+ const model = runtime.model?.ok ? runtime.model.value : null;
12595
+ const tasks = runtime.tasks?.ok ? (runtime.tasks.value?.tasks ?? []) : [];
12596
+ const schedules = runtime.schedules?.ok ? (runtime.schedules.value?.entries ?? []) : [];
12597
+ el("session-state").textContent = String(mode);
12598
+ const stateRecord = '<div class="record"><strong>session <span class="badge info">' + escapeHtml(mode) + '</span></strong><div class="meta">model=' + escapeHtml(model?.modelId ?? "unknown") + ' | reasoning=' + escapeHtml(model?.reasoningEffort ?? "default") + ' | schedules=' + escapeHtml(schedules.length) + '</div></div>';
12599
+ const taskRows = tasks.slice(0, 10).map((task) => '<div class="record"><strong>' + escapeHtml(task.description ?? task.id ?? "task") + ' <span class="badge ' + escapeHtml(task.status ?? "info") + '">' + escapeHtml(task.status ?? "?") + '</span></strong><div class="meta">' + escapeHtml(task.type ?? "task") + ' | id=' + escapeHtml(task.id ?? "?") + ' | mode=' + escapeHtml(task.executionMode ?? "?") + '</div></div>').join("");
12600
+ el("session-control").innerHTML = stateRecord + (taskRows || '<div class="empty">No native Copilot tasks are currently tracked.</div>');
12601
+ }
12602
+
12603
+ function renderEvals(snapshot) {
12604
+ const evals = snapshot.evals ?? [];
12605
+ el("eval-count").textContent = evals.length + " summaries";
12606
+ if (evals.length === 0) {
12607
+ el("evals").innerHTML = '<div class="empty">No eval summaries found under evals/results.</div>';
12608
+ return;
12609
+ }
12610
+ el("evals").innerHTML = evals.slice(0, 12).map((item) => {
12611
+ const summary = item.summary ?? {};
12612
+ const caseId = summary.caseId ?? summary.mode ?? "run";
12613
+ const verdict = summary.verdict?.verdict ?? (Array.isArray(summary) ? "batch" : "unknown");
12614
+ const tone = verdict === "pass" || summary.extensionToolAvailable === true ? "ready" : verdict === "fail" ? "error" : "info";
12615
+ return '<div class="record"><strong>' + escapeHtml(caseId) + ' <span class="badge ' + tone + '">' + escapeHtml(verdict) + '</span></strong><div class="meta">' + escapeHtml(item.path) + ' | ' + escapeHtml(timeOnly(item.modifiedAt)) + '</div><div class="details">' + escapeHtml(summary.verdict?.summary ?? summary.detail ?? "") + '</div></div>';
12616
+ }).join("");
12617
+ }
12618
+
11838
12619
  function collectTimeline(snapshot) {
11839
12620
  const items = [];
11840
12621
  for (const stream of snapshot.streams ?? []) {
@@ -11851,6 +12632,9 @@ function createHtml() {
11851
12632
  for (const event of snapshot.diagnostics?.runtimeEvents ?? []) {
11852
12633
  items.push({ group: "runtime", timestamp: event.timestamp, source: event.type, level: "info", message: event.message, detail: compactJson(event.metadata) });
11853
12634
  }
12635
+ for (const trace of snapshot.diagnostics?.traces ?? []) {
12636
+ 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 }) });
12637
+ }
11854
12638
  return items.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
11855
12639
  }
11856
12640
 
@@ -11874,6 +12658,9 @@ function createHtml() {
11874
12658
  renderMetrics(snapshot);
11875
12659
  renderStreams(snapshot);
11876
12660
  renderProviders(snapshot);
12661
+ renderTraces(snapshot);
12662
+ renderSessionControl(snapshot);
12663
+ renderEvals(snapshot);
11877
12664
  renderTimeline(snapshot);
11878
12665
  }
11879
12666
 
@@ -11922,7 +12709,7 @@ function createHtml() {
11922
12709
  }
11923
12710
  function createTapDiagnosticsCanvas({ getSnapshot, diagnostics: diagnostics2 } = {}) {
11924
12711
  const instances = /* @__PURE__ */ new Map();
11925
- function snapshot(options = {}) {
12712
+ async function snapshot(options = {}) {
11926
12713
  return typeof getSnapshot === "function" ? getSnapshot(sanitizeSnapshotOptions(options)) : { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), error: "No diagnostics snapshot provider configured." };
11927
12714
  }
11928
12715
  function log(message, level = "info", metadata = {}) {
@@ -11936,7 +12723,7 @@ function createTapDiagnosticsCanvas({ getSnapshot, diagnostics: diagnostics2 } =
11936
12723
  return;
11937
12724
  }
11938
12725
  if (url.pathname === "/api/snapshot") {
11939
- jsonResponse(res, 200, snapshot({ limit: url.searchParams.get("limit") }));
12726
+ void snapshot({ limit: url.searchParams.get("limit") }).then((data) => jsonResponse(res, 200, data));
11940
12727
  return;
11941
12728
  }
11942
12729
  if (url.pathname === "/events") {
@@ -11947,10 +12734,17 @@ function createTapDiagnosticsCanvas({ getSnapshot, diagnostics: diagnostics2 } =
11947
12734
  "X-Accel-Buffering": "no"
11948
12735
  });
11949
12736
  const send = () => {
11950
- res.write(`event: snapshot
11951
- data: ${JSON.stringify(snapshot())}
12737
+ void snapshot().then((data) => {
12738
+ res.write(`event: snapshot
12739
+ data: ${JSON.stringify(data)}
12740
+
12741
+ `);
12742
+ }).catch((error) => {
12743
+ res.write(`event: snapshot
12744
+ data: ${JSON.stringify({ error: error?.message ?? String(error) })}
11952
12745
 
11953
12746
  `);
12747
+ });
11954
12748
  };
11955
12749
  send();
11956
12750
  const interval = setInterval(send, DEFAULT_REFRESH_MS);
@@ -12001,7 +12795,7 @@ data: ${JSON.stringify(snapshot())}
12001
12795
  },
12002
12796
  handler: async ({ input }) => ({
12003
12797
  ok: true,
12004
- summary: summarizeSnapshot(snapshot(input))
12798
+ summary: summarizeSnapshot(await snapshot(input))
12005
12799
  })
12006
12800
  },
12007
12801
  {
@@ -12077,7 +12871,7 @@ function createCopilotChannelsRuntime(options = {}) {
12077
12871
  log: runtimeService.provider.log
12078
12872
  });
12079
12873
  logRuntime("gateway created");
12080
- function getDiagnosticSnapshot(options2 = {}) {
12874
+ async function getDiagnosticSnapshot(options2 = {}) {
12081
12875
  return runtimeService.diagnostics.snapshot(options2, {
12082
12876
  gateway: typeof gateway.getDiagnosticState === "function" ? gateway.getDiagnosticState() : null,
12083
12877
  tools: gateway.isRunning() ? gateway.getAllTools(tools) : tools