copilot-tap-extension 2.0.5 → 2.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/dist/copilot-instructions.md +5 -0
- package/dist/extension.mjs +1179 -21
- package/dist/version.json +1 -1
- package/package.json +2 -2
package/dist/extension.mjs
CHANGED
|
@@ -2284,7 +2284,7 @@ var require_websocket = __commonJS({
|
|
|
2284
2284
|
var tls = __require("tls");
|
|
2285
2285
|
var { randomBytes: randomBytes3, createHash } = __require("crypto");
|
|
2286
2286
|
var { Duplex, Readable } = __require("stream");
|
|
2287
|
-
var { URL } = __require("url");
|
|
2287
|
+
var { URL: URL2 } = __require("url");
|
|
2288
2288
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
2289
2289
|
var Receiver2 = require_receiver();
|
|
2290
2290
|
var Sender2 = require_sender();
|
|
@@ -2785,11 +2785,11 @@ var require_websocket = __commonJS({
|
|
|
2785
2785
|
);
|
|
2786
2786
|
}
|
|
2787
2787
|
let parsedUrl;
|
|
2788
|
-
if (address instanceof
|
|
2788
|
+
if (address instanceof URL2) {
|
|
2789
2789
|
parsedUrl = address;
|
|
2790
2790
|
} else {
|
|
2791
2791
|
try {
|
|
2792
|
-
parsedUrl = new
|
|
2792
|
+
parsedUrl = new URL2(address);
|
|
2793
2793
|
} catch {
|
|
2794
2794
|
throw new SyntaxError(`Invalid URL: ${address}`);
|
|
2795
2795
|
}
|
|
@@ -2926,7 +2926,7 @@ var require_websocket = __commonJS({
|
|
|
2926
2926
|
req.abort();
|
|
2927
2927
|
let addr;
|
|
2928
2928
|
try {
|
|
2929
|
-
addr = new
|
|
2929
|
+
addr = new URL2(location, address);
|
|
2930
2930
|
} catch (e) {
|
|
2931
2931
|
const err = new SyntaxError(`Invalid URL: ${location}`);
|
|
2932
2932
|
emitErrorAndClose(websocket, err);
|
|
@@ -4643,14 +4643,59 @@ function createEmitterTools(deps) {
|
|
|
4643
4643
|
];
|
|
4644
4644
|
}
|
|
4645
4645
|
|
|
4646
|
+
// src/tools/diagnostics.mjs
|
|
4647
|
+
function renderCanvasOpenResult(result) {
|
|
4648
|
+
return [
|
|
4649
|
+
"Opened tap diagnostics canvas.",
|
|
4650
|
+
result?.title ? `title=${result.title}` : null,
|
|
4651
|
+
result?.status ? `status=${result.status}` : null,
|
|
4652
|
+
result?.url ? `url=${result.url}` : null,
|
|
4653
|
+
result?.instanceId ? `instanceId=${result.instanceId}` : null,
|
|
4654
|
+
result?.availability ? `availability=${result.availability}` : null
|
|
4655
|
+
].filter(Boolean).join("\n");
|
|
4656
|
+
}
|
|
4657
|
+
function createDiagnosticsTools(deps) {
|
|
4658
|
+
const diagnostics2 = deps.diagnostics ?? deps.runtime;
|
|
4659
|
+
if (!diagnostics2 || typeof diagnostics2.openCanvas !== "function") {
|
|
4660
|
+
return [];
|
|
4661
|
+
}
|
|
4662
|
+
return [
|
|
4663
|
+
{
|
|
4664
|
+
name: "tap_open_diagnostics_canvas",
|
|
4665
|
+
description: "Opens or focuses the Tap diagnostics canvas, a live flight recorder for streams, emitters, providers, logs, injection queues, and session events.",
|
|
4666
|
+
parameters: {
|
|
4667
|
+
type: "object",
|
|
4668
|
+
properties: {
|
|
4669
|
+
instanceId: {
|
|
4670
|
+
type: "string",
|
|
4671
|
+
description: "Stable canvas instance id. Reusing the same id focuses the existing diagnostics canvas."
|
|
4672
|
+
},
|
|
4673
|
+
limit: {
|
|
4674
|
+
type: "integer",
|
|
4675
|
+
minimum: 10,
|
|
4676
|
+
maximum: 300,
|
|
4677
|
+
description: "Maximum retained rows to show per diagnostics section."
|
|
4678
|
+
}
|
|
4679
|
+
}
|
|
4680
|
+
},
|
|
4681
|
+
handler: wrapToolHandler("tap_open_diagnostics_canvas", {}, async (args) => {
|
|
4682
|
+
const result = await diagnostics2.openCanvas(args ?? {});
|
|
4683
|
+
return renderCanvasOpenResult(result);
|
|
4684
|
+
})
|
|
4685
|
+
}
|
|
4686
|
+
];
|
|
4687
|
+
}
|
|
4688
|
+
|
|
4646
4689
|
// src/tools/index.mjs
|
|
4647
4690
|
function createTools(deps) {
|
|
4648
4691
|
const source = deps?.tools ?? deps?.runtime?.tools ?? {};
|
|
4649
4692
|
const streams = deps?.streams ?? source.streams ?? deps?.runtime;
|
|
4650
4693
|
const emitters = deps?.emitters ?? source.emitters ?? deps?.runtime;
|
|
4694
|
+
const diagnostics2 = deps?.diagnostics ?? source.diagnostics ?? deps?.runtime;
|
|
4651
4695
|
return [
|
|
4652
4696
|
...createStreamTools({ streams }),
|
|
4653
|
-
...createEmitterTools({ emitters })
|
|
4697
|
+
...createEmitterTools({ emitters }),
|
|
4698
|
+
...createDiagnosticsTools({ diagnostics: diagnostics2 })
|
|
4654
4699
|
];
|
|
4655
4700
|
}
|
|
4656
4701
|
|
|
@@ -6199,6 +6244,9 @@ function createProviderConnection(ws, options, adapters = {}) {
|
|
|
6199
6244
|
get tools() {
|
|
6200
6245
|
return tools;
|
|
6201
6246
|
},
|
|
6247
|
+
get pendingCallCount() {
|
|
6248
|
+
return pendingCalls.size;
|
|
6249
|
+
},
|
|
6202
6250
|
sendToolCall: sendToolCallMsg,
|
|
6203
6251
|
sendToolCancel: sendToolCancelMsg,
|
|
6204
6252
|
sendLifecycle,
|
|
@@ -6300,6 +6348,20 @@ function createDefaultPathAdapter() {
|
|
|
6300
6348
|
join: (...parts) => path2.join(...parts)
|
|
6301
6349
|
};
|
|
6302
6350
|
}
|
|
6351
|
+
function formatGatewayBindMessage(err, { host, port }) {
|
|
6352
|
+
const message = String(err?.message ?? err ?? "unknown error");
|
|
6353
|
+
const code = String(err?.code ?? "");
|
|
6354
|
+
const isPortInUse = code === "EADDRINUSE" || /\bEADDRINUSE\b|address already in use/i.test(message);
|
|
6355
|
+
if (!isPortInUse) {
|
|
6356
|
+
return `Provider gateway could not start on ${host}:${port}: ${message}`;
|
|
6357
|
+
}
|
|
6358
|
+
return [
|
|
6359
|
+
`Provider gateway mesh already has an owner at ${host}:${port}; this tap session is joining without binding another listener.`,
|
|
6360
|
+
"Core tap tools remain available.",
|
|
6361
|
+
"External providers should keep using the existing gateway.",
|
|
6362
|
+
"No action is needed unless provider tools are missing."
|
|
6363
|
+
].join(" ");
|
|
6364
|
+
}
|
|
6303
6365
|
function createProviderGateway(options = {}, adapters = {}) {
|
|
6304
6366
|
const {
|
|
6305
6367
|
tapTools,
|
|
@@ -6592,7 +6654,7 @@ function createProviderGateway(options = {}, adapters = {}) {
|
|
|
6592
6654
|
}
|
|
6593
6655
|
}
|
|
6594
6656
|
function resetFailedStart(server, err) {
|
|
6595
|
-
log(
|
|
6657
|
+
log(formatGatewayBindMessage(err, { host, port: GATEWAY_PORT }));
|
|
6596
6658
|
starting = false;
|
|
6597
6659
|
if (wss === server) {
|
|
6598
6660
|
wss = null;
|
|
@@ -6660,6 +6722,41 @@ function createProviderGateway(options = {}, adapters = {}) {
|
|
|
6660
6722
|
const tap = currentTapTools || (typeof tapTools === "function" ? tapTools() : []);
|
|
6661
6723
|
return registry.buildSessionTools(tap, dispatchToolCall);
|
|
6662
6724
|
}
|
|
6725
|
+
function projectProviderTool(tool) {
|
|
6726
|
+
return {
|
|
6727
|
+
name: tool.name,
|
|
6728
|
+
description: tool.description ?? "",
|
|
6729
|
+
timeout: tool.timeout ?? null
|
|
6730
|
+
};
|
|
6731
|
+
}
|
|
6732
|
+
function getDiagnosticState() {
|
|
6733
|
+
const connections = [...connectionsByProviderId.values()];
|
|
6734
|
+
return {
|
|
6735
|
+
running,
|
|
6736
|
+
starting,
|
|
6737
|
+
host,
|
|
6738
|
+
port: GATEWAY_PORT,
|
|
6739
|
+
reloadPending,
|
|
6740
|
+
reloadTimerActive: Boolean(reloadTimer),
|
|
6741
|
+
tokenPresent: Boolean(token),
|
|
6742
|
+
providerTokenFilePath: providerTokenFilePath ? "[redacted]" : null,
|
|
6743
|
+
gracefulShutdownActive,
|
|
6744
|
+
connectionCount: connectionsByWs.size,
|
|
6745
|
+
boundConnectionCount: connections.filter((conn) => conn.state === CONNECTION_STATE.BOUND).length,
|
|
6746
|
+
providers: registry.listProviders().map((provider) => {
|
|
6747
|
+
const conn = connectionsByProviderId.get(provider.id);
|
|
6748
|
+
return {
|
|
6749
|
+
id: provider.id,
|
|
6750
|
+
name: provider.name,
|
|
6751
|
+
sessionId: provider.sessionId,
|
|
6752
|
+
state: conn?.state ?? "unknown",
|
|
6753
|
+
pendingCallCount: conn?.pendingCallCount ?? 0,
|
|
6754
|
+
toolCount: provider.tools.length,
|
|
6755
|
+
tools: provider.tools.map(projectProviderTool)
|
|
6756
|
+
};
|
|
6757
|
+
})
|
|
6758
|
+
};
|
|
6759
|
+
}
|
|
6663
6760
|
function dispatchToolCall(providerId, toolName, callId, args) {
|
|
6664
6761
|
const conn = connectionsByProviderId.get(providerId);
|
|
6665
6762
|
if (!conn || conn.state === CONNECTION_STATE.DISCONNECTED) {
|
|
@@ -6696,6 +6793,7 @@ function createProviderGateway(options = {}, adapters = {}) {
|
|
|
6696
6793
|
getToken,
|
|
6697
6794
|
getRegistry,
|
|
6698
6795
|
getAllTools,
|
|
6796
|
+
getDiagnosticState,
|
|
6699
6797
|
dispatchToolCall,
|
|
6700
6798
|
broadcastLifecycle,
|
|
6701
6799
|
onToolsChanged,
|
|
@@ -10258,6 +10356,16 @@ function createSessionPort(initialSession = null) {
|
|
|
10258
10356
|
}
|
|
10259
10357
|
return session2.sendAndWait({ prompt });
|
|
10260
10358
|
}
|
|
10359
|
+
async function openCanvas(params) {
|
|
10360
|
+
if (!session2) {
|
|
10361
|
+
throw new LifecycleError("Session is not attached; cannot open canvas.");
|
|
10362
|
+
}
|
|
10363
|
+
const canvasApi = session2.rpc?.canvas;
|
|
10364
|
+
if (!canvasApi || typeof canvasApi.open !== "function") {
|
|
10365
|
+
throw new LifecycleError("Canvas renderer API is not available in this Copilot session.");
|
|
10366
|
+
}
|
|
10367
|
+
return canvasApi.open(params);
|
|
10368
|
+
}
|
|
10261
10369
|
function registerTools(tools) {
|
|
10262
10370
|
if (!session2) return;
|
|
10263
10371
|
try {
|
|
@@ -10293,6 +10401,7 @@ function createSessionPort(initialSession = null) {
|
|
|
10293
10401
|
log,
|
|
10294
10402
|
send,
|
|
10295
10403
|
sendAndWait,
|
|
10404
|
+
openCanvas,
|
|
10296
10405
|
registerTools,
|
|
10297
10406
|
reloadExtension
|
|
10298
10407
|
};
|
|
@@ -10536,7 +10645,23 @@ function createNotificationDispatcher({
|
|
|
10536
10645
|
}
|
|
10537
10646
|
return { cleared, generation };
|
|
10538
10647
|
}
|
|
10539
|
-
|
|
10648
|
+
function snapshot(options = {}) {
|
|
10649
|
+
const limit = Math.max(0, Math.min(Number(options.limit ?? 20) || 20, queueLimit));
|
|
10650
|
+
return {
|
|
10651
|
+
queueSize: queue.length,
|
|
10652
|
+
maxQueueSize: queueLimit,
|
|
10653
|
+
inFlight,
|
|
10654
|
+
retryScheduled: retryTimer !== null,
|
|
10655
|
+
generation,
|
|
10656
|
+
queued: queue.slice(0, limit).map((entry) => ({
|
|
10657
|
+
channel: entry.channel,
|
|
10658
|
+
monitorName: entry.monitorName,
|
|
10659
|
+
stream: entry.stream,
|
|
10660
|
+
text: String(entry.text ?? "").slice(0, 500)
|
|
10661
|
+
}))
|
|
10662
|
+
};
|
|
10663
|
+
}
|
|
10664
|
+
return { enqueue, clear, dispose: clear, snapshot };
|
|
10540
10665
|
}
|
|
10541
10666
|
|
|
10542
10667
|
// src/services/runtime-subsystems.mjs
|
|
@@ -10732,8 +10857,263 @@ function createStreamService(deps) {
|
|
|
10732
10857
|
};
|
|
10733
10858
|
}
|
|
10734
10859
|
|
|
10860
|
+
// src/diagnostics/store.mjs
|
|
10861
|
+
var DEFAULT_MAX_LOGS = 300;
|
|
10862
|
+
var DEFAULT_MAX_EVENTS = 300;
|
|
10863
|
+
var DEFAULT_MAX_RUNTIME_EVENTS = 300;
|
|
10864
|
+
var MAX_STRING_LENGTH = 1200;
|
|
10865
|
+
var MAX_COLLECTION_ITEMS = 40;
|
|
10866
|
+
var MAX_DEPTH = 4;
|
|
10867
|
+
var SECRET_KEY_PATTERN = /(?:token|secret|password|credential|authorization|api[-_]?key|reconnectToken|expectedToken)/i;
|
|
10868
|
+
function normalizePositiveInteger(value, fallback) {
|
|
10869
|
+
const number = Number(value);
|
|
10870
|
+
if (!Number.isFinite(number)) {
|
|
10871
|
+
return fallback;
|
|
10872
|
+
}
|
|
10873
|
+
return Math.max(0, Math.floor(number));
|
|
10874
|
+
}
|
|
10875
|
+
function createRingBuffer(maxEntries) {
|
|
10876
|
+
const entries = [];
|
|
10877
|
+
const limit = normalizePositiveInteger(maxEntries, 0);
|
|
10878
|
+
let total = 0;
|
|
10879
|
+
let dropped = 0;
|
|
10880
|
+
function append(entry) {
|
|
10881
|
+
total += 1;
|
|
10882
|
+
if (limit === 0) {
|
|
10883
|
+
dropped += 1;
|
|
10884
|
+
return null;
|
|
10885
|
+
}
|
|
10886
|
+
entries.push(entry);
|
|
10887
|
+
if (entries.length > limit) {
|
|
10888
|
+
const overflow = entries.length - limit;
|
|
10889
|
+
entries.splice(0, overflow);
|
|
10890
|
+
dropped += overflow;
|
|
10891
|
+
}
|
|
10892
|
+
return entry;
|
|
10893
|
+
}
|
|
10894
|
+
function snapshot(limitOverride) {
|
|
10895
|
+
const requested = normalizePositiveInteger(limitOverride, entries.length);
|
|
10896
|
+
return entries.slice(Math.max(0, entries.length - requested)).map((entry) => safeClone(entry));
|
|
10897
|
+
}
|
|
10898
|
+
function stats() {
|
|
10899
|
+
return {
|
|
10900
|
+
retained: entries.length,
|
|
10901
|
+
total,
|
|
10902
|
+
dropped,
|
|
10903
|
+
capacity: limit
|
|
10904
|
+
};
|
|
10905
|
+
}
|
|
10906
|
+
return { append, snapshot, stats };
|
|
10907
|
+
}
|
|
10908
|
+
function truncateString(value, maxLength = MAX_STRING_LENGTH) {
|
|
10909
|
+
const text = String(value ?? "");
|
|
10910
|
+
if (text.length <= maxLength) {
|
|
10911
|
+
return text;
|
|
10912
|
+
}
|
|
10913
|
+
return `${text.slice(0, maxLength)}... (${text.length - maxLength} chars truncated)`;
|
|
10914
|
+
}
|
|
10915
|
+
function safeClone(value, options = {}, depth = 0, seen = /* @__PURE__ */ new WeakSet()) {
|
|
10916
|
+
const maxDepth = normalizePositiveInteger(options.maxDepth, MAX_DEPTH);
|
|
10917
|
+
const maxStringLength = normalizePositiveInteger(options.maxStringLength, MAX_STRING_LENGTH);
|
|
10918
|
+
const maxCollectionItems = normalizePositiveInteger(options.maxCollectionItems, MAX_COLLECTION_ITEMS);
|
|
10919
|
+
if (value === null || value === void 0) {
|
|
10920
|
+
return value ?? null;
|
|
10921
|
+
}
|
|
10922
|
+
if (typeof value === "string") {
|
|
10923
|
+
return truncateString(value, maxStringLength);
|
|
10924
|
+
}
|
|
10925
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
10926
|
+
return value;
|
|
10927
|
+
}
|
|
10928
|
+
if (typeof value === "bigint") {
|
|
10929
|
+
return String(value);
|
|
10930
|
+
}
|
|
10931
|
+
if (typeof value === "function" || typeof value === "symbol") {
|
|
10932
|
+
return `[${typeof value}]`;
|
|
10933
|
+
}
|
|
10934
|
+
if (value instanceof Error) {
|
|
10935
|
+
return {
|
|
10936
|
+
name: value.name,
|
|
10937
|
+
message: truncateString(value.message, maxStringLength),
|
|
10938
|
+
stack: truncateString(value.stack ?? "", maxStringLength)
|
|
10939
|
+
};
|
|
10940
|
+
}
|
|
10941
|
+
if (depth >= maxDepth) {
|
|
10942
|
+
return Array.isArray(value) ? `[Array(${value.length})]` : "[Object]";
|
|
10943
|
+
}
|
|
10944
|
+
if (typeof value !== "object") {
|
|
10945
|
+
return String(value);
|
|
10946
|
+
}
|
|
10947
|
+
if (seen.has(value)) {
|
|
10948
|
+
return "[Circular]";
|
|
10949
|
+
}
|
|
10950
|
+
seen.add(value);
|
|
10951
|
+
if (Array.isArray(value)) {
|
|
10952
|
+
const slice = value.slice(0, maxCollectionItems).map((item) => safeClone(item, options, depth + 1, seen));
|
|
10953
|
+
if (value.length > maxCollectionItems) {
|
|
10954
|
+
slice.push(`... ${value.length - maxCollectionItems} more items`);
|
|
10955
|
+
}
|
|
10956
|
+
return slice;
|
|
10957
|
+
}
|
|
10958
|
+
const output = {};
|
|
10959
|
+
const entries = Object.entries(value).slice(0, maxCollectionItems);
|
|
10960
|
+
for (const [key, item] of entries) {
|
|
10961
|
+
output[key] = SECRET_KEY_PATTERN.test(key) ? "[redacted]" : safeClone(item, options, depth + 1, seen);
|
|
10962
|
+
}
|
|
10963
|
+
const remaining = Object.keys(value).length - entries.length;
|
|
10964
|
+
if (remaining > 0) {
|
|
10965
|
+
output.__truncatedKeys = remaining;
|
|
10966
|
+
}
|
|
10967
|
+
return output;
|
|
10968
|
+
}
|
|
10969
|
+
function normalizeLevel(value) {
|
|
10970
|
+
const level = String(value ?? "info").trim().toLowerCase();
|
|
10971
|
+
if (level === "warning" || level === "warn") return "warning";
|
|
10972
|
+
if (level === "error") return "error";
|
|
10973
|
+
if (level === "debug") return "debug";
|
|
10974
|
+
return "info";
|
|
10975
|
+
}
|
|
10976
|
+
function createId(prefix, count) {
|
|
10977
|
+
return `${prefix}-${count.toString(36)}`;
|
|
10978
|
+
}
|
|
10979
|
+
function summarizeSessionEvent(event) {
|
|
10980
|
+
const data = event?.data && typeof event.data === "object" ? event.data : {};
|
|
10981
|
+
return {
|
|
10982
|
+
id: event?.id ?? null,
|
|
10983
|
+
timestamp: event?.timestamp ?? nowIso(),
|
|
10984
|
+
type: String(event?.type ?? "unknown"),
|
|
10985
|
+
ephemeral: event?.ephemeral === true,
|
|
10986
|
+
agentId: event?.agentId ?? null,
|
|
10987
|
+
dataKeys: Object.keys(data),
|
|
10988
|
+
data: safeClone(data, {
|
|
10989
|
+
maxDepth: 3,
|
|
10990
|
+
maxStringLength: 700,
|
|
10991
|
+
maxCollectionItems: 24
|
|
10992
|
+
})
|
|
10993
|
+
};
|
|
10994
|
+
}
|
|
10995
|
+
function createDiagnosticsStore(options = {}) {
|
|
10996
|
+
const logs = createRingBuffer(options.maxLogs ?? DEFAULT_MAX_LOGS);
|
|
10997
|
+
const sessionEvents = createRingBuffer(options.maxSessionEvents ?? DEFAULT_MAX_EVENTS);
|
|
10998
|
+
const runtimeEvents = createRingBuffer(options.maxRuntimeEvents ?? DEFAULT_MAX_RUNTIME_EVENTS);
|
|
10999
|
+
const sessionEventCounts = /* @__PURE__ */ new Map();
|
|
11000
|
+
let logCount = 0;
|
|
11001
|
+
let runtimeEventCount = 0;
|
|
11002
|
+
let sessionEventCount = 0;
|
|
11003
|
+
let cleanupSessionListener = () => {
|
|
11004
|
+
};
|
|
11005
|
+
function recordLog(source, message, options2 = {}) {
|
|
11006
|
+
logCount += 1;
|
|
11007
|
+
return logs.append({
|
|
11008
|
+
id: createId("log", logCount),
|
|
11009
|
+
timestamp: nowIso(),
|
|
11010
|
+
source: String(source ?? "tap"),
|
|
11011
|
+
level: normalizeLevel(options2.level),
|
|
11012
|
+
message: truncateString(message, options2.maxStringLength ?? MAX_STRING_LENGTH),
|
|
11013
|
+
metadata: safeClone(options2.metadata ?? null, {
|
|
11014
|
+
maxDepth: 3,
|
|
11015
|
+
maxStringLength: 700,
|
|
11016
|
+
maxCollectionItems: 20
|
|
11017
|
+
})
|
|
11018
|
+
});
|
|
11019
|
+
}
|
|
11020
|
+
function recordRuntimeEvent(type, message, metadata = {}) {
|
|
11021
|
+
runtimeEventCount += 1;
|
|
11022
|
+
return runtimeEvents.append({
|
|
11023
|
+
id: createId("evt", runtimeEventCount),
|
|
11024
|
+
timestamp: nowIso(),
|
|
11025
|
+
type: String(type ?? "runtime"),
|
|
11026
|
+
message: truncateString(message, MAX_STRING_LENGTH),
|
|
11027
|
+
metadata: safeClone(metadata, {
|
|
11028
|
+
maxDepth: 3,
|
|
11029
|
+
maxStringLength: 700,
|
|
11030
|
+
maxCollectionItems: 20
|
|
11031
|
+
})
|
|
11032
|
+
});
|
|
11033
|
+
}
|
|
11034
|
+
function recordSessionEvent(event) {
|
|
11035
|
+
sessionEventCount += 1;
|
|
11036
|
+
const type = String(event?.type ?? "unknown");
|
|
11037
|
+
sessionEventCounts.set(type, (sessionEventCounts.get(type) ?? 0) + 1);
|
|
11038
|
+
return sessionEvents.append({
|
|
11039
|
+
sequence: sessionEventCount,
|
|
11040
|
+
...summarizeSessionEvent(event)
|
|
11041
|
+
});
|
|
11042
|
+
}
|
|
11043
|
+
function detachSession() {
|
|
11044
|
+
try {
|
|
11045
|
+
cleanupSessionListener();
|
|
11046
|
+
} catch {
|
|
11047
|
+
}
|
|
11048
|
+
cleanupSessionListener = () => {
|
|
11049
|
+
};
|
|
11050
|
+
}
|
|
11051
|
+
function attachSession(session2) {
|
|
11052
|
+
detachSession();
|
|
11053
|
+
if (!session2 || typeof session2.on !== "function") {
|
|
11054
|
+
recordRuntimeEvent("session-events", "Session event capture unavailable; no session listener attached.");
|
|
11055
|
+
return;
|
|
11056
|
+
}
|
|
11057
|
+
try {
|
|
11058
|
+
const unsubscribe = session2.on((event) => {
|
|
11059
|
+
recordSessionEvent(event);
|
|
11060
|
+
});
|
|
11061
|
+
cleanupSessionListener = typeof unsubscribe === "function" ? unsubscribe : () => {
|
|
11062
|
+
};
|
|
11063
|
+
recordRuntimeEvent("session-events", "Session event capture attached.");
|
|
11064
|
+
} catch (error) {
|
|
11065
|
+
recordLog("diagnostics", "Failed to attach session event capture.", {
|
|
11066
|
+
level: "warning",
|
|
11067
|
+
metadata: { error }
|
|
11068
|
+
});
|
|
11069
|
+
}
|
|
11070
|
+
}
|
|
11071
|
+
function snapshot(options2 = {}) {
|
|
11072
|
+
return {
|
|
11073
|
+
generatedAt: nowIso(),
|
|
11074
|
+
logs: logs.snapshot(options2.logLimit ?? 140),
|
|
11075
|
+
runtimeEvents: runtimeEvents.snapshot(options2.runtimeEventLimit ?? 140),
|
|
11076
|
+
sessionEvents: sessionEvents.snapshot(options2.sessionEventLimit ?? 140),
|
|
11077
|
+
sessionEventCounts: Object.fromEntries([...sessionEventCounts.entries()].sort(([left], [right]) => left.localeCompare(right))),
|
|
11078
|
+
stats: {
|
|
11079
|
+
logs: logs.stats(),
|
|
11080
|
+
runtimeEvents: runtimeEvents.stats(),
|
|
11081
|
+
sessionEvents: sessionEvents.stats()
|
|
11082
|
+
}
|
|
11083
|
+
};
|
|
11084
|
+
}
|
|
11085
|
+
return {
|
|
11086
|
+
log: recordLog,
|
|
11087
|
+
event: recordRuntimeEvent,
|
|
11088
|
+
attachSession,
|
|
11089
|
+
detachSession,
|
|
11090
|
+
snapshot
|
|
11091
|
+
};
|
|
11092
|
+
}
|
|
11093
|
+
|
|
11094
|
+
// src/canvas/consts.mjs
|
|
11095
|
+
var TAP_DIAGNOSTICS_CANVAS_ID = "tap-diagnostics";
|
|
11096
|
+
|
|
10735
11097
|
// src/services/tap-runtime-service.mjs
|
|
11098
|
+
function trimStreamEntries(stream, limit) {
|
|
11099
|
+
const entryLimit = Math.max(0, Math.floor(Number(limit ?? 80) || 80));
|
|
11100
|
+
return {
|
|
11101
|
+
...stream,
|
|
11102
|
+
entries: Array.isArray(stream.entries) ? stream.entries.slice(Math.max(0, stream.entries.length - entryLimit)) : []
|
|
11103
|
+
};
|
|
11104
|
+
}
|
|
11105
|
+
function projectToolForDiagnostics(tool) {
|
|
11106
|
+
return {
|
|
11107
|
+
name: tool.name,
|
|
11108
|
+
description: tool.description ?? "",
|
|
11109
|
+
providerId: tool.providerId ?? null,
|
|
11110
|
+
providerName: tool.providerName ?? null,
|
|
11111
|
+
skipPermission: tool.skipPermission === true,
|
|
11112
|
+
overridesBuiltInTool: tool.overridesBuiltInTool === true
|
|
11113
|
+
};
|
|
11114
|
+
}
|
|
10736
11115
|
function createTapRuntimeService(options = {}) {
|
|
11116
|
+
const diagnosticsStore = options.diagnostics ?? createDiagnosticsStore();
|
|
10737
11117
|
const {
|
|
10738
11118
|
streams,
|
|
10739
11119
|
configStore,
|
|
@@ -10780,6 +11160,7 @@ function createTapRuntimeService(options = {}) {
|
|
|
10780
11160
|
sessionContext.attachSession(session2);
|
|
10781
11161
|
sessionPort.attach(session2);
|
|
10782
11162
|
sessionActivityBridge.attach(session2);
|
|
11163
|
+
diagnosticsStore.attachSession(session2);
|
|
10783
11164
|
}
|
|
10784
11165
|
function clearNotificationsForLifecycle(options2 = {}) {
|
|
10785
11166
|
if (options2.clearNotifications === true && typeof notifications.clear === "function") {
|
|
@@ -10852,6 +11233,7 @@ function createTapRuntimeService(options = {}) {
|
|
|
10852
11233
|
const providerCapabilities = {
|
|
10853
11234
|
getSessionInfo,
|
|
10854
11235
|
log: (msg) => {
|
|
11236
|
+
diagnosticsStore.log("gateway", msg);
|
|
10855
11237
|
process.stderr.write(`[tap-gateway] ${msg}
|
|
10856
11238
|
`);
|
|
10857
11239
|
void sessionPort.log(msg);
|
|
@@ -10862,14 +11244,53 @@ function createTapRuntimeService(options = {}) {
|
|
|
10862
11244
|
void sessionPort.reloadExtension();
|
|
10863
11245
|
}
|
|
10864
11246
|
};
|
|
11247
|
+
function getDiagnosticsSnapshot(options2 = {}, extra = {}) {
|
|
11248
|
+
const allTools = Array.isArray(extra.tools) ? extra.tools.map(projectToolForDiagnostics) : [];
|
|
11249
|
+
return {
|
|
11250
|
+
generatedAt: nowIso(),
|
|
11251
|
+
session: getSessionInfo(),
|
|
11252
|
+
baseCwd: sessionContext.getBaseCwd(),
|
|
11253
|
+
process: {
|
|
11254
|
+
pid: process.pid,
|
|
11255
|
+
platform: process.platform,
|
|
11256
|
+
uptimeSeconds: Math.round(process.uptime())
|
|
11257
|
+
},
|
|
11258
|
+
streams: streamCapabilities.listStreams().map((stream) => trimStreamEntries(stream, options2.streamEntryLimit)),
|
|
11259
|
+
emitters: emitterCapabilities.listEmitters(),
|
|
11260
|
+
gateway: extra.gateway ?? null,
|
|
11261
|
+
tools: allTools,
|
|
11262
|
+
notifications: typeof notifications.snapshot === "function" ? notifications.snapshot({ limit: options2.notificationLimit ?? 20 }) : null,
|
|
11263
|
+
diagnostics: diagnosticsStore.snapshot(options2)
|
|
11264
|
+
};
|
|
11265
|
+
}
|
|
11266
|
+
const diagnosticsCapabilities = {
|
|
11267
|
+
log: (source, message, options2 = {}) => diagnosticsStore.log(source, message, options2),
|
|
11268
|
+
event: (type, message, metadata = {}) => diagnosticsStore.event(type, message, metadata),
|
|
11269
|
+
snapshot: getDiagnosticsSnapshot,
|
|
11270
|
+
openCanvas: async (input = {}) => {
|
|
11271
|
+
const instanceId = String(input.instanceId ?? TAP_DIAGNOSTICS_CANVAS_ID).trim() || TAP_DIAGNOSTICS_CANVAS_ID;
|
|
11272
|
+
const limit = Number(input.limit);
|
|
11273
|
+
const canvasInput = Number.isFinite(limit) ? { limit: Math.max(10, Math.min(300, Math.floor(limit))) } : {};
|
|
11274
|
+
diagnosticsStore.event("canvas.open.requested", "Opening tap diagnostics canvas.", { instanceId, canvasInput });
|
|
11275
|
+
return sessionPort.openCanvas({
|
|
11276
|
+
canvasId: TAP_DIAGNOSTICS_CANVAS_ID,
|
|
11277
|
+
instanceId,
|
|
11278
|
+
input: canvasInput
|
|
11279
|
+
});
|
|
11280
|
+
},
|
|
11281
|
+
attachSession: diagnosticsStore.attachSession,
|
|
11282
|
+
detachSession: diagnosticsStore.detachSession
|
|
11283
|
+
};
|
|
10865
11284
|
return {
|
|
10866
11285
|
tools: {
|
|
10867
11286
|
streams: streamCapabilities,
|
|
10868
|
-
emitters: emitterCapabilities
|
|
11287
|
+
emitters: emitterCapabilities,
|
|
11288
|
+
diagnostics: diagnosticsCapabilities
|
|
10869
11289
|
},
|
|
10870
11290
|
hooks: hookCapabilities,
|
|
10871
11291
|
session: sessionCapabilities,
|
|
10872
11292
|
provider: providerCapabilities,
|
|
11293
|
+
diagnostics: diagnosticsCapabilities,
|
|
10873
11294
|
getBaseCwd: sessionContext.getBaseCwd,
|
|
10874
11295
|
getSessionInfo,
|
|
10875
11296
|
attachSession,
|
|
@@ -10892,6 +11313,725 @@ function createTapRuntimeService(options = {}) {
|
|
|
10892
11313
|
};
|
|
10893
11314
|
}
|
|
10894
11315
|
|
|
11316
|
+
// src/canvas/diagnostics-canvas.mjs
|
|
11317
|
+
import { createServer } from "node:http";
|
|
11318
|
+
import { createCanvas } from "@github/copilot-sdk/extension";
|
|
11319
|
+
var DEFAULT_REFRESH_MS = 1200;
|
|
11320
|
+
function jsonResponse(res, status, data) {
|
|
11321
|
+
const body = JSON.stringify(data, null, 2);
|
|
11322
|
+
res.writeHead(status, {
|
|
11323
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
11324
|
+
"Cache-Control": "no-store",
|
|
11325
|
+
"X-Content-Type-Options": "nosniff"
|
|
11326
|
+
});
|
|
11327
|
+
res.end(body);
|
|
11328
|
+
}
|
|
11329
|
+
function textResponse(res, status, body, contentType = "text/plain; charset=utf-8") {
|
|
11330
|
+
res.writeHead(status, {
|
|
11331
|
+
"Content-Type": contentType,
|
|
11332
|
+
"Cache-Control": "no-store",
|
|
11333
|
+
"X-Content-Type-Options": "nosniff"
|
|
11334
|
+
});
|
|
11335
|
+
res.end(body);
|
|
11336
|
+
}
|
|
11337
|
+
function sanitizeSnapshotOptions(input = {}) {
|
|
11338
|
+
const source = input && typeof input === "object" ? input : {};
|
|
11339
|
+
const limit = Number(source.limit);
|
|
11340
|
+
return {
|
|
11341
|
+
streamEntryLimit: Number.isFinite(limit) ? Math.max(10, Math.min(200, Math.floor(limit))) : 80,
|
|
11342
|
+
logLimit: Number.isFinite(limit) ? Math.max(20, Math.min(300, Math.floor(limit))) : 160,
|
|
11343
|
+
sessionEventLimit: Number.isFinite(limit) ? Math.max(20, Math.min(300, Math.floor(limit))) : 160,
|
|
11344
|
+
runtimeEventLimit: Number.isFinite(limit) ? Math.max(20, Math.min(300, Math.floor(limit))) : 160
|
|
11345
|
+
};
|
|
11346
|
+
}
|
|
11347
|
+
function summarizeSnapshot(snapshot) {
|
|
11348
|
+
const runningEmitters = Array.isArray(snapshot?.emitters?.running) ? snapshot.emitters.running.length : 0;
|
|
11349
|
+
const configuredEmitters = Array.isArray(snapshot?.emitters?.configured) ? snapshot.emitters.configured.length : 0;
|
|
11350
|
+
const streams = Array.isArray(snapshot?.streams) ? snapshot.streams.length : 0;
|
|
11351
|
+
const providers = Array.isArray(snapshot?.gateway?.providers) ? snapshot.gateway.providers.length : 0;
|
|
11352
|
+
const logs = snapshot?.diagnostics?.stats?.logs?.retained ?? 0;
|
|
11353
|
+
const sessionEvents = snapshot?.diagnostics?.stats?.sessionEvents?.retained ?? 0;
|
|
11354
|
+
return { streams, runningEmitters, configuredEmitters, providers, logs, sessionEvents };
|
|
11355
|
+
}
|
|
11356
|
+
function createHtml() {
|
|
11357
|
+
return `<!doctype html>
|
|
11358
|
+
<html lang="en">
|
|
11359
|
+
<head>
|
|
11360
|
+
<meta charset="utf-8" />
|
|
11361
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
|
11362
|
+
<title>tap diagnostics</title>
|
|
11363
|
+
<style>
|
|
11364
|
+
:root {
|
|
11365
|
+
color-scheme: light;
|
|
11366
|
+
--paper: oklch(96% 0.018 86);
|
|
11367
|
+
--paper-2: oklch(91% 0.025 82);
|
|
11368
|
+
--ink: oklch(18% 0.026 70);
|
|
11369
|
+
--muted: oklch(43% 0.04 73);
|
|
11370
|
+
--line: oklch(72% 0.052 76);
|
|
11371
|
+
--amber: oklch(70% 0.15 72);
|
|
11372
|
+
--amber-dark: oklch(42% 0.12 67);
|
|
11373
|
+
--red: oklch(55% 0.18 30);
|
|
11374
|
+
--green: oklch(55% 0.13 145);
|
|
11375
|
+
--blue: oklch(48% 0.1 245);
|
|
11376
|
+
--surface: oklch(99% 0.012 88);
|
|
11377
|
+
--shadow: 0 24px 80px oklch(28% 0.04 70 / 0.14);
|
|
11378
|
+
--font-display: "Bahnschrift", "Aptos Display", "Segoe UI Variable Display", sans-serif;
|
|
11379
|
+
--font-body: "Aptos", "Segoe UI Variable Text", sans-serif;
|
|
11380
|
+
--font-mono: "Cascadia Code", "Consolas", monospace;
|
|
11381
|
+
}
|
|
11382
|
+
|
|
11383
|
+
* { box-sizing: border-box; }
|
|
11384
|
+
html { min-height: 100%; background: var(--paper); color: var(--ink); }
|
|
11385
|
+
body {
|
|
11386
|
+
margin: 0;
|
|
11387
|
+
min-height: 100vh;
|
|
11388
|
+
font-family: var(--font-body);
|
|
11389
|
+
background:
|
|
11390
|
+
linear-gradient(90deg, oklch(34% 0.08 68 / 0.08) 1px, transparent 1px) 0 0 / 42px 42px,
|
|
11391
|
+
linear-gradient(0deg, oklch(34% 0.08 68 / 0.055) 1px, transparent 1px) 0 0 / 42px 42px,
|
|
11392
|
+
radial-gradient(circle at 8% 12%, oklch(76% 0.13 74 / 0.35), transparent 28rem),
|
|
11393
|
+
var(--paper);
|
|
11394
|
+
}
|
|
11395
|
+
|
|
11396
|
+
button, input { font: inherit; }
|
|
11397
|
+
button:focus-visible, input:focus-visible {
|
|
11398
|
+
outline: 3px solid var(--amber-dark);
|
|
11399
|
+
outline-offset: 3px;
|
|
11400
|
+
}
|
|
11401
|
+
|
|
11402
|
+
.shell {
|
|
11403
|
+
width: min(1540px, calc(100vw - clamp(18px, 4vw, 64px)));
|
|
11404
|
+
margin: 0 auto;
|
|
11405
|
+
padding: clamp(22px, 4vw, 54px) 0;
|
|
11406
|
+
}
|
|
11407
|
+
|
|
11408
|
+
.masthead {
|
|
11409
|
+
display: grid;
|
|
11410
|
+
grid-template-columns: minmax(0, 1fr);
|
|
11411
|
+
gap: 22px;
|
|
11412
|
+
align-items: end;
|
|
11413
|
+
border-bottom: 3px solid var(--ink);
|
|
11414
|
+
padding-bottom: clamp(18px, 3vw, 34px);
|
|
11415
|
+
}
|
|
11416
|
+
|
|
11417
|
+
.mark {
|
|
11418
|
+
width: 72px;
|
|
11419
|
+
aspect-ratio: 1;
|
|
11420
|
+
display: grid;
|
|
11421
|
+
place-items: center;
|
|
11422
|
+
border: 3px solid var(--ink);
|
|
11423
|
+
background: var(--amber);
|
|
11424
|
+
box-shadow: 8px 8px 0 var(--ink);
|
|
11425
|
+
font-family: var(--font-display);
|
|
11426
|
+
font-size: 44px;
|
|
11427
|
+
line-height: 1;
|
|
11428
|
+
}
|
|
11429
|
+
|
|
11430
|
+
.title-wrap {
|
|
11431
|
+
display: grid;
|
|
11432
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
11433
|
+
gap: clamp(16px, 3vw, 28px);
|
|
11434
|
+
align-items: center;
|
|
11435
|
+
}
|
|
11436
|
+
|
|
11437
|
+
h1 {
|
|
11438
|
+
margin: 0;
|
|
11439
|
+
font-family: var(--font-display);
|
|
11440
|
+
font-size: clamp(2.4rem, 7vw, 6.8rem);
|
|
11441
|
+
line-height: 0.82;
|
|
11442
|
+
letter-spacing: -0.08em;
|
|
11443
|
+
text-transform: uppercase;
|
|
11444
|
+
max-width: 11ch;
|
|
11445
|
+
}
|
|
11446
|
+
|
|
11447
|
+
.dek {
|
|
11448
|
+
margin: 12px 0 0;
|
|
11449
|
+
max-width: 78ch;
|
|
11450
|
+
color: var(--muted);
|
|
11451
|
+
font-size: clamp(0.98rem, 1.8vw, 1.18rem);
|
|
11452
|
+
line-height: 1.45;
|
|
11453
|
+
}
|
|
11454
|
+
|
|
11455
|
+
.controls {
|
|
11456
|
+
display: flex;
|
|
11457
|
+
flex-wrap: wrap;
|
|
11458
|
+
gap: 10px;
|
|
11459
|
+
align-items: center;
|
|
11460
|
+
justify-content: space-between;
|
|
11461
|
+
}
|
|
11462
|
+
|
|
11463
|
+
.filter-row {
|
|
11464
|
+
display: flex;
|
|
11465
|
+
gap: 8px;
|
|
11466
|
+
flex-wrap: wrap;
|
|
11467
|
+
}
|
|
11468
|
+
|
|
11469
|
+
.chip, .button {
|
|
11470
|
+
border: 2px solid var(--ink);
|
|
11471
|
+
background: var(--surface);
|
|
11472
|
+
color: var(--ink);
|
|
11473
|
+
min-height: 40px;
|
|
11474
|
+
padding: 8px 13px;
|
|
11475
|
+
box-shadow: 3px 3px 0 var(--ink);
|
|
11476
|
+
cursor: pointer;
|
|
11477
|
+
transition: transform 140ms ease, box-shadow 140ms ease, background 140ms ease;
|
|
11478
|
+
}
|
|
11479
|
+
|
|
11480
|
+
.chip[aria-pressed="true"], .button.primary {
|
|
11481
|
+
background: var(--ink);
|
|
11482
|
+
color: var(--paper);
|
|
11483
|
+
}
|
|
11484
|
+
|
|
11485
|
+
@media (hover: hover) {
|
|
11486
|
+
.chip:hover, .button:hover { transform: translate(-1px, -1px); box-shadow: 5px 5px 0 var(--ink); }
|
|
11487
|
+
}
|
|
11488
|
+
|
|
11489
|
+
.search {
|
|
11490
|
+
display: flex;
|
|
11491
|
+
align-items: center;
|
|
11492
|
+
gap: 10px;
|
|
11493
|
+
min-width: min(100%, 380px);
|
|
11494
|
+
border: 2px solid var(--ink);
|
|
11495
|
+
background: var(--surface);
|
|
11496
|
+
box-shadow: 3px 3px 0 var(--ink);
|
|
11497
|
+
padding: 8px 12px;
|
|
11498
|
+
}
|
|
11499
|
+
|
|
11500
|
+
.search input {
|
|
11501
|
+
width: 100%;
|
|
11502
|
+
border: 0;
|
|
11503
|
+
background: transparent;
|
|
11504
|
+
color: var(--ink);
|
|
11505
|
+
outline: 0;
|
|
11506
|
+
}
|
|
11507
|
+
|
|
11508
|
+
.dashboard {
|
|
11509
|
+
display: grid;
|
|
11510
|
+
grid-template-columns: minmax(0, 1fr);
|
|
11511
|
+
gap: clamp(18px, 3vw, 34px);
|
|
11512
|
+
margin-top: clamp(22px, 4vw, 48px);
|
|
11513
|
+
}
|
|
11514
|
+
|
|
11515
|
+
.metrics {
|
|
11516
|
+
display: grid;
|
|
11517
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
11518
|
+
gap: 12px;
|
|
11519
|
+
}
|
|
11520
|
+
|
|
11521
|
+
.metric {
|
|
11522
|
+
min-height: 118px;
|
|
11523
|
+
border: 2px solid var(--ink);
|
|
11524
|
+
background: var(--surface);
|
|
11525
|
+
box-shadow: var(--shadow);
|
|
11526
|
+
padding: 16px;
|
|
11527
|
+
display: flex;
|
|
11528
|
+
flex-direction: column;
|
|
11529
|
+
justify-content: space-between;
|
|
11530
|
+
}
|
|
11531
|
+
|
|
11532
|
+
.metric b {
|
|
11533
|
+
font-family: var(--font-display);
|
|
11534
|
+
font-size: clamp(2rem, 5vw, 4.2rem);
|
|
11535
|
+
line-height: 0.9;
|
|
11536
|
+
letter-spacing: -0.05em;
|
|
11537
|
+
}
|
|
11538
|
+
|
|
11539
|
+
.metric span {
|
|
11540
|
+
color: var(--muted);
|
|
11541
|
+
text-transform: uppercase;
|
|
11542
|
+
font-size: 0.72rem;
|
|
11543
|
+
letter-spacing: 0.14em;
|
|
11544
|
+
font-weight: 800;
|
|
11545
|
+
}
|
|
11546
|
+
|
|
11547
|
+
.panel {
|
|
11548
|
+
border: 2px solid var(--ink);
|
|
11549
|
+
background: oklch(98% 0.014 88 / 0.96);
|
|
11550
|
+
box-shadow: 8px 8px 0 oklch(18% 0.026 70 / 0.22);
|
|
11551
|
+
min-width: 0;
|
|
11552
|
+
}
|
|
11553
|
+
|
|
11554
|
+
.panel-head {
|
|
11555
|
+
display: flex;
|
|
11556
|
+
align-items: baseline;
|
|
11557
|
+
justify-content: space-between;
|
|
11558
|
+
gap: 12px;
|
|
11559
|
+
padding: 14px 16px;
|
|
11560
|
+
border-bottom: 2px solid var(--ink);
|
|
11561
|
+
background: var(--paper-2);
|
|
11562
|
+
}
|
|
11563
|
+
|
|
11564
|
+
.panel-head h2 {
|
|
11565
|
+
margin: 0;
|
|
11566
|
+
font-family: var(--font-display);
|
|
11567
|
+
font-size: clamp(1.2rem, 2vw, 1.7rem);
|
|
11568
|
+
letter-spacing: -0.03em;
|
|
11569
|
+
text-transform: uppercase;
|
|
11570
|
+
}
|
|
11571
|
+
|
|
11572
|
+
.panel-head small {
|
|
11573
|
+
color: var(--muted);
|
|
11574
|
+
font-family: var(--font-mono);
|
|
11575
|
+
font-size: 0.75rem;
|
|
11576
|
+
}
|
|
11577
|
+
|
|
11578
|
+
.panel-body {
|
|
11579
|
+
padding: 14px;
|
|
11580
|
+
max-height: min(62vh, 720px);
|
|
11581
|
+
overflow: auto;
|
|
11582
|
+
}
|
|
11583
|
+
|
|
11584
|
+
.stream-grid, .emitter-grid, .provider-grid {
|
|
11585
|
+
display: grid;
|
|
11586
|
+
gap: 12px;
|
|
11587
|
+
}
|
|
11588
|
+
|
|
11589
|
+
.record {
|
|
11590
|
+
border-left: 4px solid var(--line);
|
|
11591
|
+
background: color-mix(in oklch, var(--surface), var(--paper) 24%);
|
|
11592
|
+
padding: 11px 12px;
|
|
11593
|
+
}
|
|
11594
|
+
|
|
11595
|
+
.record strong {
|
|
11596
|
+
display: inline-flex;
|
|
11597
|
+
gap: 8px;
|
|
11598
|
+
align-items: baseline;
|
|
11599
|
+
font-family: var(--font-display);
|
|
11600
|
+
letter-spacing: -0.01em;
|
|
11601
|
+
}
|
|
11602
|
+
|
|
11603
|
+
.meta {
|
|
11604
|
+
color: var(--muted);
|
|
11605
|
+
font-family: var(--font-mono);
|
|
11606
|
+
font-size: 0.72rem;
|
|
11607
|
+
overflow-wrap: anywhere;
|
|
11608
|
+
}
|
|
11609
|
+
|
|
11610
|
+
.entry {
|
|
11611
|
+
margin-top: 8px;
|
|
11612
|
+
padding-top: 8px;
|
|
11613
|
+
border-top: 1px dashed var(--line);
|
|
11614
|
+
font-family: var(--font-mono);
|
|
11615
|
+
font-size: 0.78rem;
|
|
11616
|
+
line-height: 1.45;
|
|
11617
|
+
white-space: pre-wrap;
|
|
11618
|
+
overflow-wrap: anywhere;
|
|
11619
|
+
}
|
|
11620
|
+
|
|
11621
|
+
.timeline {
|
|
11622
|
+
display: grid;
|
|
11623
|
+
gap: 9px;
|
|
11624
|
+
}
|
|
11625
|
+
|
|
11626
|
+
.tick {
|
|
11627
|
+
display: grid;
|
|
11628
|
+
grid-template-columns: 90px minmax(0, 1fr);
|
|
11629
|
+
gap: 12px;
|
|
11630
|
+
border-bottom: 1px dashed var(--line);
|
|
11631
|
+
padding: 9px 0;
|
|
11632
|
+
}
|
|
11633
|
+
|
|
11634
|
+
.tick time {
|
|
11635
|
+
color: var(--muted);
|
|
11636
|
+
font-family: var(--font-mono);
|
|
11637
|
+
font-size: 0.72rem;
|
|
11638
|
+
}
|
|
11639
|
+
|
|
11640
|
+
.badge {
|
|
11641
|
+
display: inline-flex;
|
|
11642
|
+
align-items: center;
|
|
11643
|
+
gap: 6px;
|
|
11644
|
+
padding: 2px 7px;
|
|
11645
|
+
border: 1px solid var(--ink);
|
|
11646
|
+
background: var(--paper);
|
|
11647
|
+
font-family: var(--font-mono);
|
|
11648
|
+
font-size: 0.68rem;
|
|
11649
|
+
text-transform: uppercase;
|
|
11650
|
+
letter-spacing: 0.08em;
|
|
11651
|
+
}
|
|
11652
|
+
|
|
11653
|
+
.badge.error { background: color-mix(in oklch, var(--red), var(--paper) 72%); }
|
|
11654
|
+
.badge.warning { background: color-mix(in oklch, var(--amber), var(--paper) 54%); }
|
|
11655
|
+
.badge.ready, .badge.running, .badge.success { background: color-mix(in oklch, var(--green), var(--paper) 70%); }
|
|
11656
|
+
.badge.info, .badge.debug { background: color-mix(in oklch, var(--blue), var(--paper) 76%); }
|
|
11657
|
+
|
|
11658
|
+
.details {
|
|
11659
|
+
margin-top: 6px;
|
|
11660
|
+
color: var(--ink);
|
|
11661
|
+
font-family: var(--font-mono);
|
|
11662
|
+
font-size: 0.78rem;
|
|
11663
|
+
white-space: pre-wrap;
|
|
11664
|
+
overflow-wrap: anywhere;
|
|
11665
|
+
}
|
|
11666
|
+
|
|
11667
|
+
.empty {
|
|
11668
|
+
border: 2px dashed var(--line);
|
|
11669
|
+
padding: 18px;
|
|
11670
|
+
color: var(--muted);
|
|
11671
|
+
background: color-mix(in oklch, var(--surface), var(--paper) 45%);
|
|
11672
|
+
}
|
|
11673
|
+
|
|
11674
|
+
.footer {
|
|
11675
|
+
margin-top: 24px;
|
|
11676
|
+
color: var(--muted);
|
|
11677
|
+
font-size: 0.86rem;
|
|
11678
|
+
}
|
|
11679
|
+
|
|
11680
|
+
@media (min-width: 780px) {
|
|
11681
|
+
.masthead { grid-template-columns: minmax(0, 1fr) auto; }
|
|
11682
|
+
.dashboard { grid-template-columns: minmax(280px, 0.62fr) minmax(0, 1fr); align-items: start; }
|
|
11683
|
+
.metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
11684
|
+
.timeline-panel { grid-column: 1 / -1; }
|
|
11685
|
+
}
|
|
11686
|
+
|
|
11687
|
+
@media (min-width: 1180px) {
|
|
11688
|
+
.dashboard { grid-template-columns: 360px minmax(0, 1fr) 420px; }
|
|
11689
|
+
.timeline-panel { grid-column: auto; }
|
|
11690
|
+
.metrics { grid-template-columns: 1fr; }
|
|
11691
|
+
}
|
|
11692
|
+
|
|
11693
|
+
@media (prefers-reduced-motion: reduce) {
|
|
11694
|
+
*, *::before, *::after {
|
|
11695
|
+
animation-duration: 0.01ms !important;
|
|
11696
|
+
animation-iteration-count: 1 !important;
|
|
11697
|
+
scroll-behavior: auto !important;
|
|
11698
|
+
transition-duration: 0.01ms !important;
|
|
11699
|
+
}
|
|
11700
|
+
}
|
|
11701
|
+
</style>
|
|
11702
|
+
</head>
|
|
11703
|
+
<body>
|
|
11704
|
+
<main class="shell">
|
|
11705
|
+
<header class="masthead">
|
|
11706
|
+
<div class="title-wrap">
|
|
11707
|
+
<div class="mark" aria-hidden="true">\u203B</div>
|
|
11708
|
+
<div>
|
|
11709
|
+
<h1>Tap flight recorder</h1>
|
|
11710
|
+
<p class="dek">A live canvas for streams, emitters, provider gateway state, injection queues, session events, and tap diagnostics. Bounded, redacted, and built for incident-speed inspection.</p>
|
|
11711
|
+
</div>
|
|
11712
|
+
</div>
|
|
11713
|
+
<div class="controls" aria-label="Diagnostics controls">
|
|
11714
|
+
<div class="filter-row" role="group" aria-label="Timeline filter">
|
|
11715
|
+
<button class="chip" data-filter="all" aria-pressed="true">All</button>
|
|
11716
|
+
<button class="chip" data-filter="streams" aria-pressed="false">Streams</button>
|
|
11717
|
+
<button class="chip" data-filter="logs" aria-pressed="false">Logs</button>
|
|
11718
|
+
<button class="chip" data-filter="session" aria-pressed="false">Session</button>
|
|
11719
|
+
<button class="chip" data-filter="runtime" aria-pressed="false">Runtime</button>
|
|
11720
|
+
</div>
|
|
11721
|
+
<label class="search">
|
|
11722
|
+
<span aria-hidden="true">\u2315</span>
|
|
11723
|
+
<input id="search" type="search" placeholder="Filter evidence..." autocomplete="off" />
|
|
11724
|
+
</label>
|
|
11725
|
+
<button id="pause" class="button" type="button">Pause</button>
|
|
11726
|
+
<button id="refresh" class="button primary" type="button">Refresh</button>
|
|
11727
|
+
</div>
|
|
11728
|
+
</header>
|
|
11729
|
+
|
|
11730
|
+
<section class="dashboard" aria-live="polite">
|
|
11731
|
+
<aside>
|
|
11732
|
+
<div class="metrics" id="metrics"></div>
|
|
11733
|
+
<p class="footer" id="heartbeat">Waiting for first snapshot...</p>
|
|
11734
|
+
</aside>
|
|
11735
|
+
|
|
11736
|
+
<section class="panel">
|
|
11737
|
+
<div class="panel-head">
|
|
11738
|
+
<h2>Streams and emitters</h2>
|
|
11739
|
+
<small id="stream-count">0 streams</small>
|
|
11740
|
+
</div>
|
|
11741
|
+
<div class="panel-body">
|
|
11742
|
+
<div class="stream-grid" id="streams"></div>
|
|
11743
|
+
</div>
|
|
11744
|
+
</section>
|
|
11745
|
+
|
|
11746
|
+
<section class="panel">
|
|
11747
|
+
<div class="panel-head">
|
|
11748
|
+
<h2>Providers</h2>
|
|
11749
|
+
<small id="provider-count">0 providers</small>
|
|
11750
|
+
</div>
|
|
11751
|
+
<div class="panel-body">
|
|
11752
|
+
<div class="provider-grid" id="providers"></div>
|
|
11753
|
+
</div>
|
|
11754
|
+
</section>
|
|
11755
|
+
|
|
11756
|
+
<section class="panel timeline-panel">
|
|
11757
|
+
<div class="panel-head">
|
|
11758
|
+
<h2>Evidence timeline</h2>
|
|
11759
|
+
<small id="timeline-count">0 events</small>
|
|
11760
|
+
</div>
|
|
11761
|
+
<div class="panel-body">
|
|
11762
|
+
<div class="timeline" id="timeline"></div>
|
|
11763
|
+
</div>
|
|
11764
|
+
</section>
|
|
11765
|
+
</section>
|
|
11766
|
+
</main>
|
|
11767
|
+
|
|
11768
|
+
<script>
|
|
11769
|
+
const state = { snapshot: null, paused: false, filter: "all", query: "" };
|
|
11770
|
+
const el = (id) => document.getElementById(id);
|
|
11771
|
+
const escapeHtml = (value) => String(value ?? "").replace(/[&<>"']/g, (ch) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[ch]));
|
|
11772
|
+
const timeOnly = (value) => {
|
|
11773
|
+
const date = new Date(value);
|
|
11774
|
+
return Number.isNaN(date.getTime()) ? "" : date.toLocaleTimeString([], { hour12: false });
|
|
11775
|
+
};
|
|
11776
|
+
const compactJson = (value) => {
|
|
11777
|
+
if (value === null || value === undefined) return "";
|
|
11778
|
+
if (typeof value === "string") return value;
|
|
11779
|
+
try { return JSON.stringify(value, null, 2); } catch { return String(value); }
|
|
11780
|
+
};
|
|
11781
|
+
|
|
11782
|
+
function metric(label, value, tone = "") {
|
|
11783
|
+
return '<article class="metric ' + tone + '"><span>' + escapeHtml(label) + '</span><b>' + escapeHtml(value) + '</b></article>';
|
|
11784
|
+
}
|
|
11785
|
+
|
|
11786
|
+
function renderMetrics(snapshot) {
|
|
11787
|
+
const running = snapshot.emitters?.running?.length ?? 0;
|
|
11788
|
+
const configured = snapshot.emitters?.configured?.length ?? 0;
|
|
11789
|
+
const streams = snapshot.streams?.length ?? 0;
|
|
11790
|
+
const providers = snapshot.gateway?.providers?.length ?? 0;
|
|
11791
|
+
const queue = snapshot.notifications?.queueSize ?? 0;
|
|
11792
|
+
const sessionEvents = snapshot.diagnostics?.stats?.sessionEvents?.retained ?? 0;
|
|
11793
|
+
el("metrics").innerHTML = [
|
|
11794
|
+
metric("streams", streams),
|
|
11795
|
+
metric("running emitters", running),
|
|
11796
|
+
metric("providers", providers),
|
|
11797
|
+
metric("queued injections", queue),
|
|
11798
|
+
metric("configured emitters", configured),
|
|
11799
|
+
metric("session events", sessionEvents)
|
|
11800
|
+
].join("");
|
|
11801
|
+
el("heartbeat").textContent = "Snapshot " + timeOnly(snapshot.generatedAt) + " | pid " + (snapshot.process?.pid ?? "?") + " | gateway " + (snapshot.gateway?.running ? "ready" : "stopped");
|
|
11802
|
+
}
|
|
11803
|
+
|
|
11804
|
+
function renderStreams(snapshot) {
|
|
11805
|
+
const streams = snapshot.streams ?? [];
|
|
11806
|
+
el("stream-count").textContent = streams.length + " streams";
|
|
11807
|
+
if (streams.length === 0) {
|
|
11808
|
+
el("streams").innerHTML = '<div class="empty">No streams are currently retained.</div>';
|
|
11809
|
+
return;
|
|
11810
|
+
}
|
|
11811
|
+
const emitterRows = [...(snapshot.emitters?.running ?? []), ...(snapshot.emitters?.configured ?? [])]
|
|
11812
|
+
.map((emitter) => '<div class="record"><strong>' + escapeHtml(emitter.name) + ' <span class="badge ' + escapeHtml(emitter.status) + '">' + escapeHtml(emitter.status) + '</span></strong><div class="meta">' + escapeHtml(emitter.emitterType) + ' | ' + escapeHtml(emitter.runSchedule) + ' | stream=' + escapeHtml(emitter.stream) + '</div><div class="meta">lines=' + escapeHtml(emitter.lineCount ?? 0) + ' dropped=' + escapeHtml(emitter.droppedLineCount ?? 0) + '</div></div>')
|
|
11813
|
+
.join("");
|
|
11814
|
+
const streamRows = streams.map((stream) => {
|
|
11815
|
+
const latest = (stream.entries ?? []).slice(-3).reverse().map((entry) => '<div class="entry"><span class="meta">' + escapeHtml(timeOnly(entry.timestamp)) + ' ' + escapeHtml(entry.source) + (entry.monitorName ? ' / ' + escapeHtml(entry.monitorName) : '') + '</span>\\n' + escapeHtml(entry.text) + '</div>').join("");
|
|
11816
|
+
return '<div class="record"><strong>' + escapeHtml(stream.name) + ' <span class="badge ' + (stream.sessionInjector?.enabled ? 'ready' : 'info') + '">' + (stream.sessionInjector?.enabled ? 'injector on' : 'kept') + '</span></strong><div class="meta">' + escapeHtml(stream.entries?.length ?? 0) + ' retained entries | delivery=' + escapeHtml(stream.sessionInjector?.delivery ?? 'surface') + '</div>' + (latest || '<div class="entry">No entries retained for this stream.</div>') + '</div>';
|
|
11817
|
+
}).join("");
|
|
11818
|
+
el("streams").innerHTML = streamRows + (emitterRows ? '<div class="panel-head" style="margin:14px -14px 12px"><h2>Emitter roll call</h2><small>' + ((snapshot.emitters?.running?.length ?? 0) + (snapshot.emitters?.configured?.length ?? 0)) + ' emitters</small></div><div class="emitter-grid">' + emitterRows + '</div>' : "");
|
|
11819
|
+
}
|
|
11820
|
+
|
|
11821
|
+
function renderProviders(snapshot) {
|
|
11822
|
+
const gateway = snapshot.gateway ?? {};
|
|
11823
|
+
const providers = gateway.providers ?? [];
|
|
11824
|
+
el("provider-count").textContent = providers.length + " providers";
|
|
11825
|
+
const gatewayRecord = '<div class="record"><strong>gateway <span class="badge ' + (gateway.running ? 'ready' : 'error') + '">' + (gateway.running ? 'running' : 'stopped') + '</span></strong><div class="meta">port=' + escapeHtml(gateway.port ?? "?") + ' connections=' + escapeHtml(gateway.connectionCount ?? 0) + ' reloadPending=' + escapeHtml(gateway.reloadPending ?? false) + ' token=' + (gateway.tokenPresent ? 'present' : 'absent') + '</div></div>';
|
|
11826
|
+
if (providers.length === 0) {
|
|
11827
|
+
el("providers").innerHTML = gatewayRecord + '<div class="empty">No external providers are bound. The canvas still shows tap-native streams and emitters.</div>';
|
|
11828
|
+
return;
|
|
11829
|
+
}
|
|
11830
|
+
const rows = providers.map((provider) => {
|
|
11831
|
+
const tools = (provider.tools ?? []).map((tool) => tool.name).join(", ") || "no tools";
|
|
11832
|
+
return '<div class="record"><strong>' + escapeHtml(provider.name) + ' <span class="badge ready">' + escapeHtml(provider.id) + '</span></strong><div class="meta">session=' + escapeHtml(provider.sessionId ?? "none") + ' | tools=' + escapeHtml(provider.toolCount ?? 0) + '</div><div class="details">' + escapeHtml(tools) + '</div></div>';
|
|
11833
|
+
}).join("");
|
|
11834
|
+
el("providers").innerHTML = gatewayRecord + rows;
|
|
11835
|
+
}
|
|
11836
|
+
|
|
11837
|
+
function collectTimeline(snapshot) {
|
|
11838
|
+
const items = [];
|
|
11839
|
+
for (const stream of snapshot.streams ?? []) {
|
|
11840
|
+
for (const entry of stream.entries ?? []) {
|
|
11841
|
+
items.push({ group: "streams", timestamp: entry.timestamp, source: "stream/" + stream.name, level: "info", message: entry.text, detail: entry.monitorName ? entry.monitorName + " " + (entry.stream ?? "") : entry.source });
|
|
11842
|
+
}
|
|
11843
|
+
}
|
|
11844
|
+
for (const log of snapshot.diagnostics?.logs ?? []) {
|
|
11845
|
+
items.push({ group: "logs", timestamp: log.timestamp, source: log.source, level: log.level, message: log.message, detail: compactJson(log.metadata) });
|
|
11846
|
+
}
|
|
11847
|
+
for (const event of snapshot.diagnostics?.sessionEvents ?? []) {
|
|
11848
|
+
items.push({ group: "session", timestamp: event.timestamp, source: "session", level: "debug", message: event.type, detail: compactJson(event.data) });
|
|
11849
|
+
}
|
|
11850
|
+
for (const event of snapshot.diagnostics?.runtimeEvents ?? []) {
|
|
11851
|
+
items.push({ group: "runtime", timestamp: event.timestamp, source: event.type, level: "info", message: event.message, detail: compactJson(event.metadata) });
|
|
11852
|
+
}
|
|
11853
|
+
return items.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
|
|
11854
|
+
}
|
|
11855
|
+
|
|
11856
|
+
function renderTimeline(snapshot) {
|
|
11857
|
+
const query = state.query.trim().toLowerCase();
|
|
11858
|
+
const items = collectTimeline(snapshot).filter((item) => {
|
|
11859
|
+
if (state.filter !== "all" && item.group !== state.filter) return false;
|
|
11860
|
+
if (!query) return true;
|
|
11861
|
+
return [item.source, item.level, item.message, item.detail].join(" ").toLowerCase().includes(query);
|
|
11862
|
+
}).slice(0, 260);
|
|
11863
|
+
el("timeline-count").textContent = items.length + " visible";
|
|
11864
|
+
if (items.length === 0) {
|
|
11865
|
+
el("timeline").innerHTML = '<div class="empty">No evidence matches the current filter.</div>';
|
|
11866
|
+
return;
|
|
11867
|
+
}
|
|
11868
|
+
el("timeline").innerHTML = items.map((item) => '<div class="tick"><time>' + escapeHtml(timeOnly(item.timestamp)) + '</time><div><span class="badge ' + escapeHtml(item.level) + '">' + escapeHtml(item.group) + '</span> <span class="meta">' + escapeHtml(item.source) + '</span><div><strong>' + escapeHtml(item.message) + '</strong></div>' + (item.detail ? '<div class="details">' + escapeHtml(item.detail) + '</div>' : '') + '</div></div>').join("");
|
|
11869
|
+
}
|
|
11870
|
+
|
|
11871
|
+
function render(snapshot) {
|
|
11872
|
+
state.snapshot = snapshot;
|
|
11873
|
+
renderMetrics(snapshot);
|
|
11874
|
+
renderStreams(snapshot);
|
|
11875
|
+
renderProviders(snapshot);
|
|
11876
|
+
renderTimeline(snapshot);
|
|
11877
|
+
}
|
|
11878
|
+
|
|
11879
|
+
async function refresh() {
|
|
11880
|
+
if (state.paused) return;
|
|
11881
|
+
const response = await fetch("/api/snapshot", { cache: "no-store" });
|
|
11882
|
+
if (response.ok) render(await response.json());
|
|
11883
|
+
}
|
|
11884
|
+
|
|
11885
|
+
function connectEvents() {
|
|
11886
|
+
if (!("EventSource" in window)) {
|
|
11887
|
+
setInterval(refresh, 1800);
|
|
11888
|
+
refresh();
|
|
11889
|
+
return;
|
|
11890
|
+
}
|
|
11891
|
+
const source = new EventSource("/events");
|
|
11892
|
+
source.addEventListener("snapshot", (event) => {
|
|
11893
|
+
if (!state.paused) render(JSON.parse(event.data));
|
|
11894
|
+
});
|
|
11895
|
+
source.onerror = () => {
|
|
11896
|
+
setTimeout(refresh, 2000);
|
|
11897
|
+
};
|
|
11898
|
+
}
|
|
11899
|
+
|
|
11900
|
+
document.querySelectorAll("[data-filter]").forEach((button) => {
|
|
11901
|
+
button.addEventListener("click", () => {
|
|
11902
|
+
state.filter = button.dataset.filter;
|
|
11903
|
+
document.querySelectorAll("[data-filter]").forEach((item) => item.setAttribute("aria-pressed", String(item === button)));
|
|
11904
|
+
if (state.snapshot) renderTimeline(state.snapshot);
|
|
11905
|
+
});
|
|
11906
|
+
});
|
|
11907
|
+
el("search").addEventListener("input", (event) => {
|
|
11908
|
+
state.query = event.target.value;
|
|
11909
|
+
if (state.snapshot) renderTimeline(state.snapshot);
|
|
11910
|
+
});
|
|
11911
|
+
el("pause").addEventListener("click", () => {
|
|
11912
|
+
state.paused = !state.paused;
|
|
11913
|
+
el("pause").textContent = state.paused ? "Resume" : "Pause";
|
|
11914
|
+
if (!state.paused) refresh();
|
|
11915
|
+
});
|
|
11916
|
+
el("refresh").addEventListener("click", () => refresh());
|
|
11917
|
+
connectEvents();
|
|
11918
|
+
</script>
|
|
11919
|
+
</body>
|
|
11920
|
+
</html>`;
|
|
11921
|
+
}
|
|
11922
|
+
function createTapDiagnosticsCanvas({ getSnapshot, diagnostics: diagnostics2 } = {}) {
|
|
11923
|
+
const instances = /* @__PURE__ */ new Map();
|
|
11924
|
+
function snapshot(options = {}) {
|
|
11925
|
+
return typeof getSnapshot === "function" ? getSnapshot(sanitizeSnapshotOptions(options)) : { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), error: "No diagnostics snapshot provider configured." };
|
|
11926
|
+
}
|
|
11927
|
+
function log(message, level = "info", metadata = {}) {
|
|
11928
|
+
diagnostics2?.log?.("canvas", message, { level, metadata });
|
|
11929
|
+
}
|
|
11930
|
+
async function startServer(instanceId) {
|
|
11931
|
+
const server = createServer((req, res) => {
|
|
11932
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
11933
|
+
if (url.pathname === "/") {
|
|
11934
|
+
textResponse(res, 200, createHtml(), "text/html; charset=utf-8");
|
|
11935
|
+
return;
|
|
11936
|
+
}
|
|
11937
|
+
if (url.pathname === "/api/snapshot") {
|
|
11938
|
+
jsonResponse(res, 200, snapshot({ limit: url.searchParams.get("limit") }));
|
|
11939
|
+
return;
|
|
11940
|
+
}
|
|
11941
|
+
if (url.pathname === "/events") {
|
|
11942
|
+
res.writeHead(200, {
|
|
11943
|
+
"Content-Type": "text/event-stream; charset=utf-8",
|
|
11944
|
+
"Cache-Control": "no-store",
|
|
11945
|
+
"Connection": "keep-alive",
|
|
11946
|
+
"X-Accel-Buffering": "no"
|
|
11947
|
+
});
|
|
11948
|
+
const send = () => {
|
|
11949
|
+
res.write(`event: snapshot
|
|
11950
|
+
data: ${JSON.stringify(snapshot())}
|
|
11951
|
+
|
|
11952
|
+
`);
|
|
11953
|
+
};
|
|
11954
|
+
send();
|
|
11955
|
+
const interval = setInterval(send, DEFAULT_REFRESH_MS);
|
|
11956
|
+
req.on("close", () => clearInterval(interval));
|
|
11957
|
+
return;
|
|
11958
|
+
}
|
|
11959
|
+
jsonResponse(res, 404, { error: "not_found" });
|
|
11960
|
+
});
|
|
11961
|
+
await new Promise((resolve, reject) => {
|
|
11962
|
+
server.once("error", reject);
|
|
11963
|
+
server.listen(0, "127.0.0.1", resolve);
|
|
11964
|
+
});
|
|
11965
|
+
const address = server.address();
|
|
11966
|
+
const port = typeof address === "object" && address ? address.port : 0;
|
|
11967
|
+
const entry = { server, url: `http://127.0.0.1:${port}/` };
|
|
11968
|
+
instances.set(instanceId, entry);
|
|
11969
|
+
log(`Diagnostics canvas server started for instance '${instanceId}'.`, "info", { url: entry.url });
|
|
11970
|
+
return entry;
|
|
11971
|
+
}
|
|
11972
|
+
async function stopServer(instanceId) {
|
|
11973
|
+
const entry = instances.get(instanceId);
|
|
11974
|
+
if (!entry) {
|
|
11975
|
+
return;
|
|
11976
|
+
}
|
|
11977
|
+
instances.delete(instanceId);
|
|
11978
|
+
await new Promise((resolve) => entry.server.close(() => resolve()));
|
|
11979
|
+
log(`Diagnostics canvas server stopped for instance '${instanceId}'.`);
|
|
11980
|
+
}
|
|
11981
|
+
return createCanvas({
|
|
11982
|
+
id: TAP_DIAGNOSTICS_CANVAS_ID,
|
|
11983
|
+
displayName: "Tap diagnostics",
|
|
11984
|
+
description: "Live tap flight recorder for streams, emitters, provider gateway state, logs, and session events.",
|
|
11985
|
+
inputSchema: {
|
|
11986
|
+
type: "object",
|
|
11987
|
+
properties: {
|
|
11988
|
+
limit: { type: "integer", minimum: 10, maximum: 300, description: "Maximum retained rows to show per diagnostics section." }
|
|
11989
|
+
}
|
|
11990
|
+
},
|
|
11991
|
+
actions: [
|
|
11992
|
+
{
|
|
11993
|
+
name: "refresh_snapshot",
|
|
11994
|
+
description: "Return a fresh summary of the tap diagnostics canvas data.",
|
|
11995
|
+
inputSchema: {
|
|
11996
|
+
type: "object",
|
|
11997
|
+
properties: {
|
|
11998
|
+
limit: { type: "integer", minimum: 10, maximum: 300 }
|
|
11999
|
+
}
|
|
12000
|
+
},
|
|
12001
|
+
handler: async ({ input }) => ({
|
|
12002
|
+
ok: true,
|
|
12003
|
+
summary: summarizeSnapshot(snapshot(input))
|
|
12004
|
+
})
|
|
12005
|
+
},
|
|
12006
|
+
{
|
|
12007
|
+
name: "export_snapshot",
|
|
12008
|
+
description: "Return the current tap diagnostics snapshot as structured JSON.",
|
|
12009
|
+
inputSchema: {
|
|
12010
|
+
type: "object",
|
|
12011
|
+
properties: {
|
|
12012
|
+
limit: { type: "integer", minimum: 10, maximum: 300 }
|
|
12013
|
+
}
|
|
12014
|
+
},
|
|
12015
|
+
handler: async ({ input }) => snapshot(input)
|
|
12016
|
+
}
|
|
12017
|
+
],
|
|
12018
|
+
open: async (ctx) => {
|
|
12019
|
+
let entry = instances.get(ctx.instanceId);
|
|
12020
|
+
if (!entry) {
|
|
12021
|
+
entry = await startServer(ctx.instanceId);
|
|
12022
|
+
}
|
|
12023
|
+
return {
|
|
12024
|
+
title: "Tap diagnostics",
|
|
12025
|
+
status: "live flight recorder",
|
|
12026
|
+
url: entry.url
|
|
12027
|
+
};
|
|
12028
|
+
},
|
|
12029
|
+
onClose: async (ctx) => {
|
|
12030
|
+
await stopServer(ctx.instanceId);
|
|
12031
|
+
}
|
|
12032
|
+
});
|
|
12033
|
+
}
|
|
12034
|
+
|
|
10895
12035
|
// src/tap-runtime/runtime-factory.mjs
|
|
10896
12036
|
var PROVIDER_SHUTDOWN_DEADLINE_MS = 1e4;
|
|
10897
12037
|
var EMITTER_SHUTDOWN_WAIT_TIMEOUT_MS = 1e4;
|
|
@@ -10917,10 +12057,15 @@ function createCopilotChannelsRuntime(options = {}) {
|
|
|
10917
12057
|
...options.runtimeServiceOptions ?? {},
|
|
10918
12058
|
cwd: options.cwd,
|
|
10919
12059
|
session: options.session,
|
|
12060
|
+
diagnostics: options.diagnostics,
|
|
10920
12061
|
shutdownSession: () => handleSessionShutdown(activeSession)
|
|
10921
12062
|
});
|
|
10922
|
-
|
|
12063
|
+
function logRuntime(message, options2 = {}) {
|
|
12064
|
+
runtimeService.diagnostics?.log?.("runtime", message, options2);
|
|
12065
|
+
process.stderr.write(`[tap-runtime] ${message}
|
|
10923
12066
|
`);
|
|
12067
|
+
}
|
|
12068
|
+
logRuntime(`init \u2014 cwd=${runtimeService.session.getBaseCwd()}`);
|
|
10924
12069
|
const tools = createTools({ tools: runtimeService.tools });
|
|
10925
12070
|
const hooks = createHooks({ runtime: runtimeService.hooks });
|
|
10926
12071
|
const tapToolsFn = () => tools;
|
|
@@ -10930,8 +12075,19 @@ function createCopilotChannelsRuntime(options = {}) {
|
|
|
10930
12075
|
deliverPush: runtimeService.provider.deliverPush,
|
|
10931
12076
|
log: runtimeService.provider.log
|
|
10932
12077
|
});
|
|
10933
|
-
|
|
10934
|
-
|
|
12078
|
+
logRuntime("gateway created");
|
|
12079
|
+
function getDiagnosticSnapshot(options2 = {}) {
|
|
12080
|
+
return runtimeService.diagnostics.snapshot(options2, {
|
|
12081
|
+
gateway: typeof gateway.getDiagnosticState === "function" ? gateway.getDiagnosticState() : null,
|
|
12082
|
+
tools: gateway.isRunning() ? gateway.getAllTools(tools) : tools
|
|
12083
|
+
});
|
|
12084
|
+
}
|
|
12085
|
+
const canvases = [
|
|
12086
|
+
createTapDiagnosticsCanvas({
|
|
12087
|
+
getSnapshot: getDiagnosticSnapshot,
|
|
12088
|
+
diagnostics: runtimeService.diagnostics
|
|
12089
|
+
})
|
|
12090
|
+
];
|
|
10935
12091
|
gateway.onToolsChanged(runtimeService.provider.replaceSessionTools);
|
|
10936
12092
|
let cleanupShutdownListener = () => {
|
|
10937
12093
|
};
|
|
@@ -10958,7 +12114,7 @@ function createCopilotChannelsRuntime(options = {}) {
|
|
|
10958
12114
|
return;
|
|
10959
12115
|
}
|
|
10960
12116
|
handled = true;
|
|
10961
|
-
|
|
12117
|
+
logRuntime("session.shutdown received \u2014 stopping runtime");
|
|
10962
12118
|
void handleSessionShutdown(session2).catch((error) => {
|
|
10963
12119
|
logShutdownFailure(session2, error);
|
|
10964
12120
|
});
|
|
@@ -11027,19 +12183,15 @@ function createCopilotChannelsRuntime(options = {}) {
|
|
|
11027
12183
|
return;
|
|
11028
12184
|
}
|
|
11029
12185
|
try {
|
|
11030
|
-
|
|
11031
|
-
`);
|
|
12186
|
+
logRuntime("starting gateway...");
|
|
11032
12187
|
gateway.start();
|
|
11033
|
-
|
|
11034
|
-
`);
|
|
12188
|
+
logRuntime("gateway start requested");
|
|
11035
12189
|
} catch (err) {
|
|
11036
|
-
|
|
11037
|
-
`);
|
|
12190
|
+
logRuntime(`gateway start request failed: ${err?.message ?? err}`, { level: "warning" });
|
|
11038
12191
|
}
|
|
11039
12192
|
}
|
|
11040
12193
|
function attachSession(nextSession) {
|
|
11041
|
-
|
|
11042
|
-
`);
|
|
12194
|
+
logRuntime(`attachSession \u2014 id=${nextSession?.id ?? "(none)"}`);
|
|
11043
12195
|
activeSession = nextSession ?? null;
|
|
11044
12196
|
if (shutdownRecord?.settled) {
|
|
11045
12197
|
shutdownRecord = null;
|
|
@@ -11052,18 +12204,22 @@ function createCopilotChannelsRuntime(options = {}) {
|
|
|
11052
12204
|
attachSession,
|
|
11053
12205
|
tools,
|
|
11054
12206
|
hooks,
|
|
12207
|
+
canvases,
|
|
11055
12208
|
stopAllEmitters,
|
|
11056
12209
|
stopAllEmittersAndWait,
|
|
11057
12210
|
handleSessionShutdown,
|
|
11058
12211
|
appendStreamMessage: runtimeService.session.appendStreamMessage,
|
|
11059
12212
|
getTools: () => gateway.isRunning() ? gateway.getAllTools(tools) : tools,
|
|
12213
|
+
getCanvases: () => canvases,
|
|
11060
12214
|
DEFAULT_STREAM
|
|
11061
12215
|
};
|
|
11062
12216
|
}
|
|
11063
12217
|
|
|
11064
12218
|
// src/extension.mjs
|
|
12219
|
+
var diagnostics = globalThis.__tapDiagnostics ??= createDiagnosticsStore();
|
|
11065
12220
|
function tapLog(msg) {
|
|
11066
12221
|
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
12222
|
+
diagnostics.log("extension", msg);
|
|
11067
12223
|
process.stderr.write(`[tap ${ts}] ${msg}
|
|
11068
12224
|
`);
|
|
11069
12225
|
}
|
|
@@ -11071,7 +12227,8 @@ tapLog(`extension.mjs loading \u2014 pid=${process.pid} cwd=${process.cwd()} SES
|
|
|
11071
12227
|
var isResume = Boolean(globalThis.__tapRuntime);
|
|
11072
12228
|
tapLog(`runtime ${isResume ? "resuming (cached)" : "creating (fresh)"}`);
|
|
11073
12229
|
var runtime = globalThis.__tapRuntime ??= createCopilotChannelsRuntime({
|
|
11074
|
-
cwd: process.cwd()
|
|
12230
|
+
cwd: process.cwd(),
|
|
12231
|
+
diagnostics
|
|
11075
12232
|
});
|
|
11076
12233
|
tapLog("runtime ready");
|
|
11077
12234
|
tapLog("calling joinSession\u2026");
|
|
@@ -11079,6 +12236,7 @@ var session;
|
|
|
11079
12236
|
try {
|
|
11080
12237
|
session = await joinSession({
|
|
11081
12238
|
tools: runtime.getTools(),
|
|
12239
|
+
canvases: runtime.getCanvases(),
|
|
11082
12240
|
hooks: runtime.hooks
|
|
11083
12241
|
});
|
|
11084
12242
|
tapLog(`joinSession OK \u2014 session.id=${session.id ?? "(none)"}`);
|