@nextclaw/server 0.17.2 → 0.18.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -64,13 +64,13 @@ async function readJson(req) {
64
64
  return { ok: false };
65
65
  }
66
66
  }
67
- function isRecord$2(value) {
67
+ function isRecord$3(value) {
68
68
  return typeof value === "object" && value !== null && !Array.isArray(value);
69
69
  }
70
70
  function readErrorMessage(value, fallback) {
71
- if (!isRecord$2(value)) return fallback;
71
+ if (!isRecord$3(value)) return fallback;
72
72
  const maybeError = value.error;
73
- if (!isRecord$2(maybeError)) return fallback;
73
+ if (!isRecord$3(maybeError)) return fallback;
74
74
  return typeof maybeError.message === "string" && maybeError.message.trim().length > 0 ? maybeError.message : fallback;
75
75
  }
76
76
  function readNonEmptyString(value) {
@@ -477,7 +477,8 @@ const ingressKeys = {
477
477
  channelCommandList: createTypedKey("extension.channel.command.list"),
478
478
  channelCommandExecute: createTypedKey("extension.channel.command.execute"),
479
479
  runtimeReady: createTypedKey("extension.runtime.ready"),
480
- response: createTypedKey("extension.response")
480
+ response: createTypedKey("extension.response"),
481
+ observationEvent: createTypedKey("extension.observation.event")
481
482
  },
482
483
  agentRun: {
483
484
  send: createTypedKey("agent-run.send"),
@@ -1456,7 +1457,7 @@ var ServerPathRoutesController = class {
1456
1457
  watch = async (c) => {
1457
1458
  if (!this.watchService) return c.json(err("SERVER_PATH_WATCH_UNAVAILABLE", "server path watch is unavailable"), 503);
1458
1459
  const body = await readJson(c.req.raw);
1459
- if (!body.ok || !isRecord$2(body.data) || !Array.isArray(body.data.directories) || !body.data.directories.every((path) => typeof path === "string")) return c.json(err("INVALID_SERVER_PATH_WATCH", "directories are required"), 400);
1460
+ if (!body.ok || !isRecord$3(body.data) || !Array.isArray(body.data.directories) || !body.data.directories.every((path) => typeof path === "string")) return c.json(err("INVALID_SERVER_PATH_WATCH", "directories are required"), 400);
1460
1461
  try {
1461
1462
  const request = {
1462
1463
  directories: body.data.directories,
@@ -1502,7 +1503,7 @@ var ServerPathRoutesController = class {
1502
1503
  };
1503
1504
  createDirectory = async (c) => {
1504
1505
  const body = await readJson(c.req.raw);
1505
- if (!body.ok || !isRecord$2(body.data)) return c.json(err("INVALID_SERVER_PATH_DIRECTORY", "directory input is required"), 400);
1506
+ if (!body.ok || !isRecord$3(body.data)) return c.json(err("INVALID_SERVER_PATH_DIRECTORY", "directory input is required"), 400);
1506
1507
  try {
1507
1508
  return c.json(ok(await createServerPathDirectory({
1508
1509
  basePath: body.data.basePath,
@@ -1539,7 +1540,7 @@ var ServerPathRoutesController = class {
1539
1540
  };
1540
1541
  createFile = async (c) => {
1541
1542
  const body = await readJson(c.req.raw);
1542
- if (!body.ok || !isRecord$2(body.data)) return c.json(err("INVALID_SERVER_PATH_FILE", "file input is required"), 400);
1543
+ if (!body.ok || !isRecord$3(body.data)) return c.json(err("INVALID_SERVER_PATH_FILE", "file input is required"), 400);
1543
1544
  try {
1544
1545
  return c.json(ok(await createServerPathFile({
1545
1546
  basePath: body.data.basePath,
@@ -1553,7 +1554,7 @@ var ServerPathRoutesController = class {
1553
1554
  };
1554
1555
  renameEntry = async (c) => {
1555
1556
  const body = await readJson(c.req.raw);
1556
- if (!body.ok || !isRecord$2(body.data)) return c.json(err("INVALID_SERVER_PATH_RENAME", "rename input is required"), 400);
1557
+ if (!body.ok || !isRecord$3(body.data)) return c.json(err("INVALID_SERVER_PATH_RENAME", "rename input is required"), 400);
1557
1558
  try {
1558
1559
  return c.json(ok(await renameServerPathEntry({
1559
1560
  basePath: body.data.basePath,
@@ -1905,6 +1906,44 @@ function buildFallbackBootstrapStatus() {
1905
1906
  remote: { state: "pending" }
1906
1907
  };
1907
1908
  }
1909
+ function buildExtensionsView(options) {
1910
+ const manifests = options.kernel.extensions.getManifests();
1911
+ const statuses = options.kernel.extensions.getRuntimeStatus();
1912
+ const statusById = new Map(statuses.map((status) => [status.extensionId, status]));
1913
+ const extensions = manifests.map((manifest) => {
1914
+ const status = statusById.get(manifest.id);
1915
+ const observations = manifest.contributes?.observations;
1916
+ const channels = (manifest.contributes?.channels ?? []).map((channel) => ({
1917
+ id: channel.id,
1918
+ ...channel.name ? { name: channel.name } : {},
1919
+ ...typeof channel.meta?.description === "string" ? { description: channel.meta.description } : {}
1920
+ }));
1921
+ return {
1922
+ id: manifest.id,
1923
+ name: manifest.name?.trim() || manifest.id,
1924
+ ...manifest.version ? { version: manifest.version } : {},
1925
+ state: status?.state ?? "stopped",
1926
+ ...status?.generation ? { generation: status.generation } : {},
1927
+ ...status?.pid ? { pid: status.pid } : {},
1928
+ ...status?.startedAt ? { startedAt: status.startedAt } : {},
1929
+ leaseCount: status?.leaseReasons.length ?? 0,
1930
+ observations: {
1931
+ context: Boolean(observations?.read),
1932
+ events: Boolean(observations?.events)
1933
+ },
1934
+ channels
1935
+ };
1936
+ });
1937
+ return {
1938
+ extensions,
1939
+ counts: {
1940
+ total: extensions.length,
1941
+ running: extensions.filter((extension) => extension.state === "running").length,
1942
+ withObservations: extensions.filter((extension) => extension.observations.context || extension.observations.events).length,
1943
+ withChannels: extensions.filter((extension) => extension.channels.length > 0).length
1944
+ }
1945
+ };
1946
+ }
1908
1947
  var AppRoutesController = class {
1909
1948
  constructor(options) {
1910
1949
  this.options = options;
@@ -1918,7 +1957,8 @@ var AppRoutesController = class {
1918
1957
  }));
1919
1958
  appMeta = (c) => c.json(ok(buildAppMetaView(this.options)));
1920
1959
  bootstrapStatus = (c) => c.json(ok(this.options.bootstrapStatus?.getStatus() ?? buildFallbackBootstrapStatus()));
1921
- extensionRuntimeStatus = (c) => c.json(ok(this.options.extensions?.getRuntimeStatus?.() ?? []));
1960
+ extensionRuntimeStatus = (c) => c.json(ok(this.options.kernel.extensions.getRuntimeStatus()));
1961
+ extensionCatalog = (c) => c.json(ok(buildExtensionsView(this.options)));
1922
1962
  };
1923
1963
  //#endregion
1924
1964
  //#region src/features/app-packages/controllers/app-packages.controller.ts
@@ -1930,7 +1970,7 @@ var AppPackagesRoutesController = class {
1930
1970
  listOperations = async (c) => c.json(ok(await this.manager.listOperations()));
1931
1971
  startInstallOperation = async (c) => {
1932
1972
  const body = await readJson(c.req.raw);
1933
- if (!body.ok || !isRecord$2(body.data) || typeof body.data.source !== "string") return c.json(err("INVALID_APP_PACKAGE_INSTALL", "source is required"), 400);
1973
+ if (!body.ok || !isRecord$3(body.data) || typeof body.data.source !== "string") return c.json(err("INVALID_APP_PACKAGE_INSTALL", "source is required"), 400);
1934
1974
  try {
1935
1975
  return c.json(ok(await this.manager.startOperation({
1936
1976
  action: "install",
@@ -1943,7 +1983,7 @@ var AppPackagesRoutesController = class {
1943
1983
  };
1944
1984
  startUpdateOperation = async (c) => {
1945
1985
  const body = await readJson(c.req.raw);
1946
- if (!body.ok || !isRecord$2(body.data)) return c.json(err("INVALID_APP_PACKAGE_UPDATE", "invalid update request"), 400);
1986
+ if (!body.ok || !isRecord$3(body.data)) return c.json(err("INVALID_APP_PACKAGE_UPDATE", "invalid update request"), 400);
1947
1987
  try {
1948
1988
  return c.json(ok(await this.manager.startOperation({
1949
1989
  action: "update",
@@ -1957,7 +1997,7 @@ var AppPackagesRoutesController = class {
1957
1997
  };
1958
1998
  startRollbackOperation = async (c) => {
1959
1999
  const body = await readJson(c.req.raw);
1960
- if (!body.ok || !isRecord$2(body.data) || typeof body.data.version !== "string") return c.json(err("INVALID_APP_PACKAGE_ROLLBACK", "version is required"), 400);
2000
+ if (!body.ok || !isRecord$3(body.data) || typeof body.data.version !== "string") return c.json(err("INVALID_APP_PACKAGE_ROLLBACK", "version is required"), 400);
1961
2001
  try {
1962
2002
  return c.json(ok(await this.manager.startOperation({
1963
2003
  action: "rollback",
@@ -1970,7 +2010,7 @@ var AppPackagesRoutesController = class {
1970
2010
  };
1971
2011
  startUninstallOperation = async (c) => {
1972
2012
  const body = await readJson(c.req.raw);
1973
- const purgeData = body.ok && isRecord$2(body.data) && body.data.purgeData === true;
2013
+ const purgeData = body.ok && isRecord$3(body.data) && body.data.purgeData === true;
1974
2014
  try {
1975
2015
  return c.json(ok(await this.manager.startOperation({
1976
2016
  action: "uninstall",
@@ -1990,7 +2030,7 @@ var AppPackagesRoutesController = class {
1990
2030
  };
1991
2031
  install = async (c) => {
1992
2032
  const body = await readJson(c.req.raw);
1993
- if (!body.ok || !isRecord$2(body.data) || typeof body.data.source !== "string") return c.json(err("INVALID_APP_PACKAGE_INSTALL", "source is required"), 400);
2033
+ if (!body.ok || !isRecord$3(body.data) || typeof body.data.source !== "string") return c.json(err("INVALID_APP_PACKAGE_INSTALL", "source is required"), 400);
1994
2034
  try {
1995
2035
  return c.json(ok(await this.manager.install(body.data.source, typeof body.data.registryUrl === "string" ? body.data.registryUrl : void 0)));
1996
2036
  } catch (error) {
@@ -2013,7 +2053,7 @@ var AppPackagesRoutesController = class {
2013
2053
  };
2014
2054
  update = async (c) => {
2015
2055
  const body = await readJson(c.req.raw);
2016
- if (!body.ok || !isRecord$2(body.data)) return c.json(err("INVALID_APP_PACKAGE_UPDATE", "invalid update request"), 400);
2056
+ if (!body.ok || !isRecord$3(body.data)) return c.json(err("INVALID_APP_PACKAGE_UPDATE", "invalid update request"), 400);
2017
2057
  try {
2018
2058
  return c.json(ok(await this.manager.update(c.req.param("appId"), {
2019
2059
  version: typeof body.data.version === "string" ? body.data.version : void 0,
@@ -2025,7 +2065,7 @@ var AppPackagesRoutesController = class {
2025
2065
  };
2026
2066
  rollback = async (c) => {
2027
2067
  const body = await readJson(c.req.raw);
2028
- if (!body.ok || !isRecord$2(body.data) || typeof body.data.version !== "string") return c.json(err("INVALID_APP_PACKAGE_ROLLBACK", "version is required"), 400);
2068
+ if (!body.ok || !isRecord$3(body.data) || typeof body.data.version !== "string") return c.json(err("INVALID_APP_PACKAGE_ROLLBACK", "version is required"), 400);
2029
2069
  try {
2030
2070
  return c.json(ok(await this.manager.rollback(c.req.param("appId"), body.data.version)));
2031
2071
  } catch (error) {
@@ -2034,7 +2074,7 @@ var AppPackagesRoutesController = class {
2034
2074
  };
2035
2075
  uninstall = async (c) => {
2036
2076
  const body = await readJson(c.req.raw);
2037
- const purgeData = body.ok && isRecord$2(body.data) && body.data.purgeData === true;
2077
+ const purgeData = body.ok && isRecord$3(body.data) && body.data.purgeData === true;
2038
2078
  try {
2039
2079
  return c.json(ok(await this.manager.uninstall(c.req.param("appId"), purgeData)));
2040
2080
  } catch (error) {
@@ -2060,7 +2100,7 @@ var AppDataRoutesController = class {
2060
2100
  };
2061
2101
  deleteRetained = async (c) => {
2062
2102
  const body = await readJson(c.req.raw);
2063
- if (!body.ok || !isRecord$2(body.data) || typeof body.data.confirmAppId !== "string" || !body.data.confirmAppId.trim()) return c.json(err("INVALID_APP_DATA_DELETE_REQUEST", "confirmAppId is required."), 400);
2103
+ if (!body.ok || !isRecord$3(body.data) || typeof body.data.confirmAppId !== "string" || !body.data.confirmAppId.trim()) return c.json(err("INVALID_APP_DATA_DELETE_REQUEST", "confirmAppId is required."), 400);
2064
2104
  try {
2065
2105
  return c.json(ok(await this.manager.deleteRetained(c.req.param("dataId"), body.data.confirmAppId.trim())));
2066
2106
  } catch (error) {
@@ -2097,7 +2137,7 @@ var SystemObjectReferencesRoutesController = class {
2097
2137
  };
2098
2138
  resolve = async (c) => {
2099
2139
  const body = await readJson(c.req.raw);
2100
- if (!body.ok || !isRecord$2(body.data) || typeof body.data.uri !== "string" || !body.data.uri.trim()) return c.json(err("INVALID_SYSTEM_OBJECT_REFERENCE", "uri must be a non-empty string"), 400);
2140
+ if (!body.ok || !isRecord$3(body.data) || typeof body.data.uri !== "string" || !body.data.uri.trim()) return c.json(err("INVALID_SYSTEM_OBJECT_REFERENCE", "uri must be a non-empty string"), 400);
2101
2141
  try {
2102
2142
  return c.json(ok(await this.manager.resolveReference(body.data.uri)));
2103
2143
  } catch (error) {
@@ -4492,7 +4532,7 @@ var InboxDeliveriesRoutesController = class {
4492
4532
  };
4493
4533
  updateState = async (c) => {
4494
4534
  const body = await readJson(c.req.raw);
4495
- if (!body.ok || !isRecord$2(body.data) || !isStateAction(body.data.action)) return c.json(err("INVALID_INBOX_DELIVERY_ACTION", "invalid inbox delivery action"), 400);
4535
+ if (!body.ok || !isRecord$3(body.data) || !isStateAction(body.data.action)) return c.json(err("INVALID_INBOX_DELIVERY_ACTION", "invalid inbox delivery action"), 400);
4496
4536
  return await this.handleManagerAction(c, () => this.manager.updateDeliveryState(c.req.param("deliveryId"), body.data.action));
4497
4537
  };
4498
4538
  delete = async (c) => {
@@ -5472,21 +5512,19 @@ function isDeferrableMessage(message) {
5472
5512
  function deferMessageToolPayload(message) {
5473
5513
  const tools = message.parts.filter((part) => part.type === "tool-invocation");
5474
5514
  const toolNames = [...new Set(tools.map((part) => part.toolName.trim()).filter(Boolean))].slice(0, SUMMARY_TOOL_NAME_LIMIT);
5475
- const parts = [];
5476
5515
  let keptRepresentative = false;
5477
- for (const part of message.parts) {
5478
- if (part.type !== "tool-invocation") {
5479
- parts.push(part);
5480
- continue;
5481
- }
5482
- if (keptRepresentative) continue;
5516
+ const parts = message.parts.map((part) => {
5517
+ if (part.type !== "tool-invocation") return part;
5518
+ const isRepresentative = !keptRepresentative;
5483
5519
  keptRepresentative = true;
5484
- parts.push({
5520
+ return {
5485
5521
  ...part,
5522
+ ...isRepresentative ? {} : { payloadDeferred: true },
5486
5523
  args: void 0,
5487
- result: void 0
5488
- });
5489
- }
5524
+ result: void 0,
5525
+ resultContentItems: void 0
5526
+ };
5527
+ });
5490
5528
  return {
5491
5529
  ...message,
5492
5530
  metadata: {
@@ -5499,6 +5537,20 @@ function deferMessageToolPayload(message) {
5499
5537
  parts
5500
5538
  };
5501
5539
  }
5540
+ function sanitizeContextCompactionHistoryMessage(message) {
5541
+ const metadata = message.metadata;
5542
+ if (metadata?.nextclaw_timeline_kind !== "context_compaction") return message;
5543
+ const checkpoint = metadata.checkpoint;
5544
+ if (!checkpoint || typeof checkpoint !== "object" || Array.isArray(checkpoint)) return message;
5545
+ const { summary: _summary, summaryDiagnostics: _summaryDiagnostics, ...uiCheckpoint } = checkpoint;
5546
+ return {
5547
+ ...message,
5548
+ metadata: {
5549
+ ...metadata,
5550
+ checkpoint: uiCheckpoint
5551
+ }
5552
+ };
5553
+ }
5502
5554
  function buildSessionMessageHistoryPayloadView(params) {
5503
5555
  const { messageBudgetBytes: requestedMessageBudgetBytes, messageDetailCursors, messages, messageToolCallBudget: requestedMessageToolCallBudget, pageBudgetBytes: requestedPageBudgetBytes, pageToolCallBudget: requestedPageToolCallBudget } = params;
5504
5556
  const messageBudgetBytes = requestedMessageBudgetBytes ?? 262144;
@@ -5530,7 +5582,10 @@ function buildSessionMessageHistoryPayloadView(params) {
5530
5582
  }
5531
5583
  const deferredToolPayloads = Object.fromEntries([...deferredIds].map((messageId) => [messageId, { cursor: messageDetailCursors[messageId] }]));
5532
5584
  return {
5533
- messages: messages.map((message) => deferredIds.has(message.id) ? deferMessageToolPayload(message) : message),
5585
+ messages: messages.map((message) => {
5586
+ const sanitized = sanitizeContextCompactionHistoryMessage(message);
5587
+ return deferredIds.has(message.id) ? deferMessageToolPayload(sanitized) : sanitized;
5588
+ }),
5534
5589
  deferredToolPayloads
5535
5590
  };
5536
5591
  }
@@ -5538,7 +5593,14 @@ function compactSessionMessageHistoryPayloadView(params) {
5538
5593
  const { view } = params;
5539
5594
  const budgetBytes = params.budgetBytes ?? 24576;
5540
5595
  const minimumMessages = Math.max(1, Math.trunc(params.minimumMessages ?? 5));
5541
- let startIndex = Math.max(0, view.messages.length - minimumMessages);
5596
+ let startIndex = view.messages.length;
5597
+ let conversationMessageCount = 0;
5598
+ while (startIndex > 0 && conversationMessageCount < minimumMessages) {
5599
+ startIndex -= 1;
5600
+ const message = view.messages[startIndex];
5601
+ if (message?.role === "user" || message?.role === "assistant") conversationMessageCount += 1;
5602
+ }
5603
+ if (startIndex === view.messages.length) startIndex = Math.max(0, view.messages.length - minimumMessages);
5542
5604
  let bytes = view.messages.slice(startIndex).reduce((total, message) => total + serializedBytes(message), 0);
5543
5605
  while (startIndex > 0) {
5544
5606
  const previousBytes = serializedBytes(view.messages[startIndex - 1]);
@@ -5555,6 +5617,115 @@ function compactSessionMessageHistoryPayloadView(params) {
5555
5617
  };
5556
5618
  }
5557
5619
  //#endregion
5620
+ //#region src/features/sessions/utils/session-observation-view.utils.ts
5621
+ const SENSITIVE_KEY_PATTERN = /(token|secret|password|cookie|api[-_]?key|credential|authorization)/i;
5622
+ const MAX_PREVIEW_ENTRIES = 6;
5623
+ const MAX_PREVIEW_VALUE_LENGTH = 80;
5624
+ function isRecord$2(value) {
5625
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
5626
+ }
5627
+ function previewValue(key, value) {
5628
+ if (SENSITIVE_KEY_PATTERN.test(key)) return "••••••";
5629
+ if (value === null) return "null";
5630
+ if (typeof value === "string") {
5631
+ const normalized = value.trim();
5632
+ if (normalized.length <= MAX_PREVIEW_VALUE_LENGTH) return normalized || "空字符串";
5633
+ return `${normalized.slice(0, MAX_PREVIEW_VALUE_LENGTH - 1)}…`;
5634
+ }
5635
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
5636
+ if (Array.isArray(value)) return `[${value.length} 项]`;
5637
+ if (isRecord$2(value)) return "已配置";
5638
+ return "已配置";
5639
+ }
5640
+ function buildSafeConfigPreview(config) {
5641
+ if (!isRecord$2(config)) return config === void 0 ? void 0 : "已配置";
5642
+ const entries = Object.entries(config).slice(0, MAX_PREVIEW_ENTRIES);
5643
+ if (entries.length === 0) return void 0;
5644
+ const preview = entries.map(([key, value]) => `${key}: ${previewValue(key, value)}`);
5645
+ if (Object.keys(config).length > MAX_PREVIEW_ENTRIES) preview.push("…");
5646
+ return preview.join(" · ");
5647
+ }
5648
+ function buildDescriptorMap(descriptors) {
5649
+ return new Map(descriptors.map((descriptor) => [`${descriptor.kind}:${descriptor.extensionId}`, descriptor]));
5650
+ }
5651
+ function buildDeliveryCounts(deliveries) {
5652
+ const counts = /* @__PURE__ */ new Map();
5653
+ for (const delivery of deliveries) {
5654
+ const current = counts.get(delivery.subscriptionId) ?? {
5655
+ pending: 0,
5656
+ failures: 0
5657
+ };
5658
+ if (delivery.status === "pending" || delivery.status === "submitted") current.pending += 1;
5659
+ if (delivery.status === "failed") current.failures += 1;
5660
+ counts.set(delivery.subscriptionId, current);
5661
+ }
5662
+ return counts;
5663
+ }
5664
+ function buildContextView(binding, descriptors) {
5665
+ const descriptor = descriptors.get(`context:${binding.extensionId}`);
5666
+ const safeConfigPreview = buildSafeConfigPreview(binding.config);
5667
+ return {
5668
+ id: binding.bindingId,
5669
+ kind: "context",
5670
+ extensionId: binding.extensionId,
5671
+ title: descriptor?.title ?? binding.extensionId,
5672
+ ...descriptor?.description ? { description: descriptor.description } : {},
5673
+ status: binding.status,
5674
+ ...binding.statusReason ? { statusReason: binding.statusReason } : {},
5675
+ createdAt: binding.createdAt,
5676
+ ...binding.expiresAt ? { expiresAt: binding.expiresAt } : {},
5677
+ ...binding.lastReadAt ? { lastReadAt: binding.lastReadAt } : {},
5678
+ ...safeConfigPreview ? { safeConfigPreview } : {}
5679
+ };
5680
+ }
5681
+ function buildSubscriptionView(subscription, descriptors, deliveryCounts) {
5682
+ const descriptor = descriptors.get(`events:${subscription.extensionId}`);
5683
+ const counts = deliveryCounts.get(subscription.subscriptionId) ?? {
5684
+ pending: 0,
5685
+ failures: 0
5686
+ };
5687
+ const safeConfigPreview = buildSafeConfigPreview(subscription.config);
5688
+ return {
5689
+ id: subscription.subscriptionId,
5690
+ kind: "events",
5691
+ extensionId: subscription.extensionId,
5692
+ title: descriptor?.title ?? subscription.extensionId,
5693
+ ...descriptor?.description ? { description: descriptor.description } : {},
5694
+ status: subscription.status,
5695
+ ...subscription.statusReason ? { statusReason: subscription.statusReason } : {},
5696
+ createdAt: subscription.createdAt,
5697
+ ...subscription.expiresAt ? { expiresAt: subscription.expiresAt } : {},
5698
+ ...safeConfigPreview ? { safeConfigPreview } : {},
5699
+ pendingCount: counts.pending,
5700
+ ...subscription.suppressedCount ? { suppressedCount: subscription.suppressedCount } : {},
5701
+ ...counts.failures > 0 ? { deliveryFailureCount: counts.failures } : {},
5702
+ ...subscription.lastSuppressionReason ? { lastSuppressionReason: subscription.lastSuppressionReason } : {},
5703
+ ...subscription.lastGapAt ? { lastGapAt: subscription.lastGapAt } : {},
5704
+ ...subscription.gapReason ? { gapReason: subscription.gapReason } : {},
5705
+ delivery: subscription.delivery
5706
+ };
5707
+ }
5708
+ function needsAttention(observation) {
5709
+ return observation.status !== "active" || (observation.deliveryFailureCount ?? 0) > 0 || Boolean(observation.lastGapAt);
5710
+ }
5711
+ function buildSessionObservationsView(input) {
5712
+ const descriptors = buildDescriptorMap(input.descriptors);
5713
+ const deliveryCounts = buildDeliveryCounts(input.deliveries);
5714
+ const bindings = input.bindings.map((binding) => buildContextView(binding, descriptors));
5715
+ const subscriptions = input.subscriptions.map((subscription) => buildSubscriptionView(subscription, descriptors, deliveryCounts));
5716
+ return {
5717
+ sessionId: input.sessionId,
5718
+ bindings,
5719
+ subscriptions,
5720
+ counts: {
5721
+ total: bindings.length + subscriptions.length,
5722
+ context: bindings.length,
5723
+ events: subscriptions.length,
5724
+ needsAttention: [...bindings, ...subscriptions].filter(needsAttention).length
5725
+ }
5726
+ };
5727
+ }
5728
+ //#endregion
5558
5729
  //#region src/features/sessions/controllers/sessions.controller.ts
5559
5730
  const DEFAULT_SESSION_MESSAGE_PAGE_SIZE = 40;
5560
5731
  const MAX_SESSION_MESSAGE_PAGE_SIZE = 200;
@@ -5689,6 +5860,42 @@ var NcpSessionRoutesController = class {
5689
5860
  if (!payload) return c.json(err("NOT_FOUND", `ncp session not found: ${sessionId}`), 404);
5690
5861
  return c.json(ok(payload));
5691
5862
  };
5863
+ listSessionObservations = async (c) => {
5864
+ const sessionId = decodeURIComponent(c.req.param("sessionId"));
5865
+ if (!await this.options.kernel.sessionManager.getSession(sessionId)) return c.json(err("NOT_FOUND", `ncp session not found: ${sessionId}`), 404);
5866
+ const payload = buildSessionObservationsView({
5867
+ sessionId,
5868
+ ...await this.options.kernel.observations.listObservations(sessionId),
5869
+ descriptors: this.options.kernel.observations.discoverObservations()
5870
+ });
5871
+ return c.json(ok(payload));
5872
+ };
5873
+ updateSessionObservation = async (c) => {
5874
+ const sessionId = decodeURIComponent(c.req.param("sessionId"));
5875
+ const kind = c.req.param("kind");
5876
+ const id = decodeURIComponent(c.req.param("id"));
5877
+ if (!await this.options.kernel.sessionManager.getSession(sessionId)) return c.json(err("NOT_FOUND", `ncp session not found: ${sessionId}`), 404);
5878
+ const body = await readJson(c.req.raw);
5879
+ const action = body.ok && body.data?.action;
5880
+ if (action !== "pause" && action !== "resume" && action !== "remove") return c.json(err("INVALID_BODY", "action must be pause, resume, or remove"), 400);
5881
+ if (kind !== "context" && kind !== "events") return c.json(err("NOT_FOUND", `observation kind not found: ${kind}`), 404);
5882
+ const ref = kind === "context" ? {
5883
+ kind: "context_binding",
5884
+ id
5885
+ } : {
5886
+ kind: "event_subscription",
5887
+ id
5888
+ };
5889
+ const observation = await this.options.kernel.observations.getObservation(ref);
5890
+ if (!observation || observation.target.sessionId !== sessionId) return c.json(err("NOT_FOUND", `observation not found in session: ${sessionId}`), 404);
5891
+ await this.options.kernel.observations.updateObservation(action, ref);
5892
+ const state = await this.options.kernel.observations.listObservations(sessionId);
5893
+ return c.json(ok(buildSessionObservationsView({
5894
+ sessionId,
5895
+ ...state,
5896
+ descriptors: this.options.kernel.observations.discoverObservations()
5897
+ })));
5898
+ };
5692
5899
  getSessionSkills = async (c) => {
5693
5900
  const sessionManager = this.options.kernel.sessionManager;
5694
5901
  const sessionId = decodeURIComponent(c.req.param("sessionId"));
@@ -6020,7 +6227,7 @@ var PanelAppsRoutesController = class {
6020
6227
  };
6021
6228
  updatePanelAppPreferences = async (c) => {
6022
6229
  const body = await readJson(c.req.raw);
6023
- if (!body.ok || !isRecord$2(body.data)) return c.json(err("INVALID_PANEL_APP_PREFERENCES", "invalid panel app preferences"), 400);
6230
+ if (!body.ok || !isRecord$3(body.data)) return c.json(err("INVALID_PANEL_APP_PREFERENCES", "invalid panel app preferences"), 400);
6024
6231
  if (body.data.mainSidebar !== void 0 && typeof body.data.mainSidebar !== "boolean") return c.json(err("INVALID_PANEL_APP_PREFERENCES", "mainSidebar must be boolean"), 400);
6025
6232
  try {
6026
6233
  const preferences = {
@@ -6100,7 +6307,7 @@ var PanelAppsRoutesController = class {
6100
6307
  };
6101
6308
  createBridgeSession = async (c) => {
6102
6309
  const body = await readJson(c.req.raw);
6103
- if (!body.ok || !isRecord$2(body.data) || typeof body.data.panelAppId !== "string" && typeof body.data.appId !== "string") return c.json(err("INVALID_PANEL_APP_BRIDGE_SESSION", "invalid bridge session request"), 400);
6310
+ if (!body.ok || !isRecord$3(body.data) || typeof body.data.panelAppId !== "string" && typeof body.data.appId !== "string") return c.json(err("INVALID_PANEL_APP_BRIDGE_SESSION", "invalid bridge session request"), 400);
6104
6311
  const panelAppId = typeof body.data.appId === "string" ? body.data.appId : body.data.panelAppId;
6105
6312
  if (typeof panelAppId !== "string") return c.json(err("INVALID_PANEL_APP_BRIDGE_SESSION", "invalid bridge session request"), 400);
6106
6313
  try {
@@ -6137,7 +6344,7 @@ var PanelAppsRoutesController = class {
6137
6344
  };
6138
6345
  sendAgentMessage = async (c) => {
6139
6346
  const body = await readJson(c.req.raw);
6140
- if (!body.ok || !isRecord$2(body.data) || !isRecord$2(body.data.payload)) return c.json(err("INVALID_PANEL_APP_AGENT_REQUEST", "invalid agent send request"), 400);
6347
+ if (!body.ok || !isRecord$3(body.data) || !isRecord$3(body.data.payload)) return c.json(err("INVALID_PANEL_APP_AGENT_REQUEST", "invalid agent send request"), 400);
6141
6348
  try {
6142
6349
  return c.json(ok(await this.panelAppManager.sendAgentMessage(this.requireBridgeSessionToken(c), body.data.payload)));
6143
6350
  } catch (error) {
@@ -6146,7 +6353,7 @@ var PanelAppsRoutesController = class {
6146
6353
  };
6147
6354
  generateAgentObject = async (c) => {
6148
6355
  const body = await readJson(c.req.raw);
6149
- if (!body.ok || !isRecord$2(body.data) || !isRecord$2(body.data.input)) return c.json(err("INVALID_PANEL_APP_AGENT_REQUEST", "invalid generateObject request"), 400);
6356
+ if (!body.ok || !isRecord$3(body.data) || !isRecord$3(body.data.input)) return c.json(err("INVALID_PANEL_APP_AGENT_REQUEST", "invalid generateObject request"), 400);
6150
6357
  try {
6151
6358
  return c.json(ok(await this.panelAppManager.generateAgentObject(this.requireBridgeSessionToken(c), body.data.input)));
6152
6359
  } catch (error) {
@@ -6219,7 +6426,7 @@ var PreferencesRoutesController = class {
6219
6426
  };
6220
6427
  update = async (c) => {
6221
6428
  const body = await readJson(c.req.raw);
6222
- if (!body.ok || !isRecord$2(body.data) || !Object.hasOwn(body.data, "value")) return c.json(err("INVALID_PREFERENCE", "preference value is required"), 400);
6429
+ if (!body.ok || !isRecord$3(body.data) || !Object.hasOwn(body.data, "value")) return c.json(err("INVALID_PREFERENCE", "preference value is required"), 400);
6223
6430
  try {
6224
6431
  const entry = await this.preferenceManager.setPreference(c.req.param("key"), body.data.value);
6225
6432
  return c.json(ok({
@@ -6262,7 +6469,7 @@ var ProjectsRoutesController = class {
6262
6469
  };
6263
6470
  create = async (c) => {
6264
6471
  const body = await readJson(c.req.raw);
6265
- if (!body.ok || !isRecord$2(body.data) || typeof body.data.name !== "string") return c.json(err("INVALID_PROJECT", "project name is required"), 400);
6472
+ if (!body.ok || !isRecord$3(body.data) || typeof body.data.name !== "string") return c.json(err("INVALID_PROJECT", "project name is required"), 400);
6266
6473
  try {
6267
6474
  return c.json(ok(await this.projectManager.createProject(body.data)), 201);
6268
6475
  } catch (error) {
@@ -6272,7 +6479,7 @@ var ProjectsRoutesController = class {
6272
6479
  };
6273
6480
  addExisting = async (c) => {
6274
6481
  const body = await readJson(c.req.raw);
6275
- if (!body.ok || !isRecord$2(body.data) || typeof body.data.rootPath !== "string") return c.json(err("INVALID_PROJECT", "project directory is required"), 400);
6482
+ if (!body.ok || !isRecord$3(body.data) || typeof body.data.rootPath !== "string") return c.json(err("INVALID_PROJECT", "project directory is required"), 400);
6276
6483
  try {
6277
6484
  const project = await this.projectManager.registerExistingProject(body.data.rootPath);
6278
6485
  if (!project) return c.json(err("PROJECT_PATH_IS_DEFAULT_WORKSPACE", "the default workspace cannot be registered as a project"), 400);
@@ -6334,13 +6541,13 @@ var ServiceAppsRoutesController = class {
6334
6541
  };
6335
6542
  invokeServiceAction = async (c) => {
6336
6543
  const body = await readJson(c.req.raw);
6337
- if (!body.ok || body.data !== void 0 && !isRecord$2(body.data)) return c.json(err("INVALID_SERVICE_ACTION_REQUEST", "invalid service action request"), 400);
6544
+ if (!body.ok || body.data !== void 0 && !isRecord$3(body.data)) return c.json(err("INVALID_SERVICE_ACTION_REQUEST", "invalid service action request"), 400);
6338
6545
  try {
6339
6546
  const bridgeSession = this.requireBridgeSession(c);
6340
6547
  const payload = await this.params.serviceAppManager.invokeServiceAction(c.req.param("actionId"), {
6341
6548
  caller: bridgeSession.caller,
6342
6549
  declaredActions: bridgeSession.declaredActions,
6343
- input: isRecord$2(body.data.input) ? body.data.input : {}
6550
+ input: isRecord$3(body.data.input) ? body.data.input : {}
6344
6551
  });
6345
6552
  return c.json(ok(payload));
6346
6553
  } catch (error) {
@@ -6404,7 +6611,7 @@ var ServiceAppsRoutesController = class {
6404
6611
  };
6405
6612
  deleteServiceApp = async (c) => {
6406
6613
  const body = await readJson(c.req.raw);
6407
- const purgeData = body.ok && isRecord$2(body.data) && body.data.purgeData === true;
6614
+ const purgeData = body.ok && isRecord$3(body.data) && body.data.purgeData === true;
6408
6615
  try {
6409
6616
  return c.json(ok(await this.params.serviceAppManager.deleteServiceApp(c.req.param("appId"), purgeData)));
6410
6617
  } catch (error) {
@@ -6658,6 +6865,16 @@ var UiRouteRegistry = class {
6658
6865
  "/api/ncp/sessions/:sessionId/context/compact",
6659
6866
  ncpSession.compactSessionContext
6660
6867
  ],
6868
+ [
6869
+ "get",
6870
+ "/api/ncp/sessions/:sessionId/observations",
6871
+ ncpSession.listSessionObservations
6872
+ ],
6873
+ [
6874
+ "patch",
6875
+ "/api/ncp/sessions/:sessionId/observations/:kind/:id",
6876
+ ncpSession.updateSessionObservation
6877
+ ],
6661
6878
  [
6662
6879
  "get",
6663
6880
  "/api/ncp/sessions/:sessionId/usage",
@@ -7058,6 +7275,11 @@ var UiRouteRegistry = class {
7058
7275
  "/api/runtime/extensions",
7059
7276
  app.extensionRuntimeStatus
7060
7277
  ],
7278
+ [
7279
+ "get",
7280
+ "/api/runtime/extensions/catalog",
7281
+ app.extensionCatalog
7282
+ ],
7061
7283
  [
7062
7284
  "get",
7063
7285
  "/api/auth/status",