@nextclaw/server 0.17.3 → 0.18.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,20 +1906,62 @@ 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;
1911
1950
  }
1912
- health = (c) => c.json(ok({
1913
- status: "ok",
1914
- services: {
1915
- ncpAgent: "ready",
1916
- cronService: this.options.cron ? "ready" : "unavailable"
1917
- }
1918
- }));
1951
+ health = (c) => {
1952
+ const bootstrapStatus = this.options.bootstrapStatus?.getStatus() ?? buildFallbackBootstrapStatus();
1953
+ return c.json(ok({
1954
+ status: "ok",
1955
+ services: {
1956
+ ncpAgent: bootstrapStatus.ncpAgent.state,
1957
+ cronService: this.options.cron ? "ready" : "unavailable"
1958
+ }
1959
+ }));
1960
+ };
1919
1961
  appMeta = (c) => c.json(ok(buildAppMetaView(this.options)));
1920
1962
  bootstrapStatus = (c) => c.json(ok(this.options.bootstrapStatus?.getStatus() ?? buildFallbackBootstrapStatus()));
1921
- extensionRuntimeStatus = (c) => c.json(ok(this.options.extensions?.getRuntimeStatus?.() ?? []));
1963
+ extensionRuntimeStatus = (c) => c.json(ok(this.options.kernel.extensions.getRuntimeStatus()));
1964
+ extensionCatalog = (c) => c.json(ok(buildExtensionsView(this.options)));
1922
1965
  };
1923
1966
  //#endregion
1924
1967
  //#region src/features/app-packages/controllers/app-packages.controller.ts
@@ -1930,7 +1973,7 @@ var AppPackagesRoutesController = class {
1930
1973
  listOperations = async (c) => c.json(ok(await this.manager.listOperations()));
1931
1974
  startInstallOperation = async (c) => {
1932
1975
  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);
1976
+ 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
1977
  try {
1935
1978
  return c.json(ok(await this.manager.startOperation({
1936
1979
  action: "install",
@@ -1943,7 +1986,7 @@ var AppPackagesRoutesController = class {
1943
1986
  };
1944
1987
  startUpdateOperation = async (c) => {
1945
1988
  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);
1989
+ if (!body.ok || !isRecord$3(body.data)) return c.json(err("INVALID_APP_PACKAGE_UPDATE", "invalid update request"), 400);
1947
1990
  try {
1948
1991
  return c.json(ok(await this.manager.startOperation({
1949
1992
  action: "update",
@@ -1957,7 +2000,7 @@ var AppPackagesRoutesController = class {
1957
2000
  };
1958
2001
  startRollbackOperation = async (c) => {
1959
2002
  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);
2003
+ 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
2004
  try {
1962
2005
  return c.json(ok(await this.manager.startOperation({
1963
2006
  action: "rollback",
@@ -1970,7 +2013,7 @@ var AppPackagesRoutesController = class {
1970
2013
  };
1971
2014
  startUninstallOperation = async (c) => {
1972
2015
  const body = await readJson(c.req.raw);
1973
- const purgeData = body.ok && isRecord$2(body.data) && body.data.purgeData === true;
2016
+ const purgeData = body.ok && isRecord$3(body.data) && body.data.purgeData === true;
1974
2017
  try {
1975
2018
  return c.json(ok(await this.manager.startOperation({
1976
2019
  action: "uninstall",
@@ -1990,7 +2033,7 @@ var AppPackagesRoutesController = class {
1990
2033
  };
1991
2034
  install = async (c) => {
1992
2035
  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);
2036
+ 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
2037
  try {
1995
2038
  return c.json(ok(await this.manager.install(body.data.source, typeof body.data.registryUrl === "string" ? body.data.registryUrl : void 0)));
1996
2039
  } catch (error) {
@@ -2013,7 +2056,7 @@ var AppPackagesRoutesController = class {
2013
2056
  };
2014
2057
  update = async (c) => {
2015
2058
  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);
2059
+ if (!body.ok || !isRecord$3(body.data)) return c.json(err("INVALID_APP_PACKAGE_UPDATE", "invalid update request"), 400);
2017
2060
  try {
2018
2061
  return c.json(ok(await this.manager.update(c.req.param("appId"), {
2019
2062
  version: typeof body.data.version === "string" ? body.data.version : void 0,
@@ -2025,7 +2068,7 @@ var AppPackagesRoutesController = class {
2025
2068
  };
2026
2069
  rollback = async (c) => {
2027
2070
  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);
2071
+ 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
2072
  try {
2030
2073
  return c.json(ok(await this.manager.rollback(c.req.param("appId"), body.data.version)));
2031
2074
  } catch (error) {
@@ -2034,7 +2077,7 @@ var AppPackagesRoutesController = class {
2034
2077
  };
2035
2078
  uninstall = async (c) => {
2036
2079
  const body = await readJson(c.req.raw);
2037
- const purgeData = body.ok && isRecord$2(body.data) && body.data.purgeData === true;
2080
+ const purgeData = body.ok && isRecord$3(body.data) && body.data.purgeData === true;
2038
2081
  try {
2039
2082
  return c.json(ok(await this.manager.uninstall(c.req.param("appId"), purgeData)));
2040
2083
  } catch (error) {
@@ -2060,7 +2103,7 @@ var AppDataRoutesController = class {
2060
2103
  };
2061
2104
  deleteRetained = async (c) => {
2062
2105
  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);
2106
+ 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
2107
  try {
2065
2108
  return c.json(ok(await this.manager.deleteRetained(c.req.param("dataId"), body.data.confirmAppId.trim())));
2066
2109
  } catch (error) {
@@ -2097,7 +2140,7 @@ var SystemObjectReferencesRoutesController = class {
2097
2140
  };
2098
2141
  resolve = async (c) => {
2099
2142
  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);
2143
+ 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
2144
  try {
2102
2145
  return c.json(ok(await this.manager.resolveReference(body.data.uri)));
2103
2146
  } catch (error) {
@@ -4492,7 +4535,7 @@ var InboxDeliveriesRoutesController = class {
4492
4535
  };
4493
4536
  updateState = async (c) => {
4494
4537
  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);
4538
+ 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
4539
  return await this.handleManagerAction(c, () => this.manager.updateDeliveryState(c.req.param("deliveryId"), body.data.action));
4497
4540
  };
4498
4541
  delete = async (c) => {
@@ -5472,21 +5515,19 @@ function isDeferrableMessage(message) {
5472
5515
  function deferMessageToolPayload(message) {
5473
5516
  const tools = message.parts.filter((part) => part.type === "tool-invocation");
5474
5517
  const toolNames = [...new Set(tools.map((part) => part.toolName.trim()).filter(Boolean))].slice(0, SUMMARY_TOOL_NAME_LIMIT);
5475
- const parts = [];
5476
5518
  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;
5519
+ const parts = message.parts.map((part) => {
5520
+ if (part.type !== "tool-invocation") return part;
5521
+ const isRepresentative = !keptRepresentative;
5483
5522
  keptRepresentative = true;
5484
- parts.push({
5523
+ return {
5485
5524
  ...part,
5525
+ ...isRepresentative ? {} : { payloadDeferred: true },
5486
5526
  args: void 0,
5487
- result: void 0
5488
- });
5489
- }
5527
+ result: void 0,
5528
+ resultContentItems: void 0
5529
+ };
5530
+ });
5490
5531
  return {
5491
5532
  ...message,
5492
5533
  metadata: {
@@ -5499,6 +5540,20 @@ function deferMessageToolPayload(message) {
5499
5540
  parts
5500
5541
  };
5501
5542
  }
5543
+ function sanitizeContextCompactionHistoryMessage(message) {
5544
+ const metadata = message.metadata;
5545
+ if (metadata?.nextclaw_timeline_kind !== "context_compaction") return message;
5546
+ const checkpoint = metadata.checkpoint;
5547
+ if (!checkpoint || typeof checkpoint !== "object" || Array.isArray(checkpoint)) return message;
5548
+ const { summary: _summary, summaryDiagnostics: _summaryDiagnostics, ...uiCheckpoint } = checkpoint;
5549
+ return {
5550
+ ...message,
5551
+ metadata: {
5552
+ ...metadata,
5553
+ checkpoint: uiCheckpoint
5554
+ }
5555
+ };
5556
+ }
5502
5557
  function buildSessionMessageHistoryPayloadView(params) {
5503
5558
  const { messageBudgetBytes: requestedMessageBudgetBytes, messageDetailCursors, messages, messageToolCallBudget: requestedMessageToolCallBudget, pageBudgetBytes: requestedPageBudgetBytes, pageToolCallBudget: requestedPageToolCallBudget } = params;
5504
5559
  const messageBudgetBytes = requestedMessageBudgetBytes ?? 262144;
@@ -5530,7 +5585,10 @@ function buildSessionMessageHistoryPayloadView(params) {
5530
5585
  }
5531
5586
  const deferredToolPayloads = Object.fromEntries([...deferredIds].map((messageId) => [messageId, { cursor: messageDetailCursors[messageId] }]));
5532
5587
  return {
5533
- messages: messages.map((message) => deferredIds.has(message.id) ? deferMessageToolPayload(message) : message),
5588
+ messages: messages.map((message) => {
5589
+ const sanitized = sanitizeContextCompactionHistoryMessage(message);
5590
+ return deferredIds.has(message.id) ? deferMessageToolPayload(sanitized) : sanitized;
5591
+ }),
5534
5592
  deferredToolPayloads
5535
5593
  };
5536
5594
  }
@@ -5538,7 +5596,14 @@ function compactSessionMessageHistoryPayloadView(params) {
5538
5596
  const { view } = params;
5539
5597
  const budgetBytes = params.budgetBytes ?? 24576;
5540
5598
  const minimumMessages = Math.max(1, Math.trunc(params.minimumMessages ?? 5));
5541
- let startIndex = Math.max(0, view.messages.length - minimumMessages);
5599
+ let startIndex = view.messages.length;
5600
+ let conversationMessageCount = 0;
5601
+ while (startIndex > 0 && conversationMessageCount < minimumMessages) {
5602
+ startIndex -= 1;
5603
+ const message = view.messages[startIndex];
5604
+ if (message?.role === "user" || message?.role === "assistant") conversationMessageCount += 1;
5605
+ }
5606
+ if (startIndex === view.messages.length) startIndex = Math.max(0, view.messages.length - minimumMessages);
5542
5607
  let bytes = view.messages.slice(startIndex).reduce((total, message) => total + serializedBytes(message), 0);
5543
5608
  while (startIndex > 0) {
5544
5609
  const previousBytes = serializedBytes(view.messages[startIndex - 1]);
@@ -5555,6 +5620,115 @@ function compactSessionMessageHistoryPayloadView(params) {
5555
5620
  };
5556
5621
  }
5557
5622
  //#endregion
5623
+ //#region src/features/sessions/utils/session-observation-view.utils.ts
5624
+ const SENSITIVE_KEY_PATTERN = /(token|secret|password|cookie|api[-_]?key|credential|authorization)/i;
5625
+ const MAX_PREVIEW_ENTRIES = 6;
5626
+ const MAX_PREVIEW_VALUE_LENGTH = 80;
5627
+ function isRecord$2(value) {
5628
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
5629
+ }
5630
+ function previewValue(key, value) {
5631
+ if (SENSITIVE_KEY_PATTERN.test(key)) return "••••••";
5632
+ if (value === null) return "null";
5633
+ if (typeof value === "string") {
5634
+ const normalized = value.trim();
5635
+ if (normalized.length <= MAX_PREVIEW_VALUE_LENGTH) return normalized || "空字符串";
5636
+ return `${normalized.slice(0, MAX_PREVIEW_VALUE_LENGTH - 1)}…`;
5637
+ }
5638
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
5639
+ if (Array.isArray(value)) return `[${value.length} 项]`;
5640
+ if (isRecord$2(value)) return "已配置";
5641
+ return "已配置";
5642
+ }
5643
+ function buildSafeConfigPreview(config) {
5644
+ if (!isRecord$2(config)) return config === void 0 ? void 0 : "已配置";
5645
+ const entries = Object.entries(config).slice(0, MAX_PREVIEW_ENTRIES);
5646
+ if (entries.length === 0) return void 0;
5647
+ const preview = entries.map(([key, value]) => `${key}: ${previewValue(key, value)}`);
5648
+ if (Object.keys(config).length > MAX_PREVIEW_ENTRIES) preview.push("…");
5649
+ return preview.join(" · ");
5650
+ }
5651
+ function buildDescriptorMap(descriptors) {
5652
+ return new Map(descriptors.map((descriptor) => [`${descriptor.kind}:${descriptor.extensionId}`, descriptor]));
5653
+ }
5654
+ function buildDeliveryCounts(deliveries) {
5655
+ const counts = /* @__PURE__ */ new Map();
5656
+ for (const delivery of deliveries) {
5657
+ const current = counts.get(delivery.subscriptionId) ?? {
5658
+ pending: 0,
5659
+ failures: 0
5660
+ };
5661
+ if (delivery.status === "pending" || delivery.status === "submitted") current.pending += 1;
5662
+ if (delivery.status === "failed") current.failures += 1;
5663
+ counts.set(delivery.subscriptionId, current);
5664
+ }
5665
+ return counts;
5666
+ }
5667
+ function buildContextView(binding, descriptors) {
5668
+ const descriptor = descriptors.get(`context:${binding.extensionId}`);
5669
+ const safeConfigPreview = buildSafeConfigPreview(binding.config);
5670
+ return {
5671
+ id: binding.bindingId,
5672
+ kind: "context",
5673
+ extensionId: binding.extensionId,
5674
+ title: descriptor?.title ?? binding.extensionId,
5675
+ ...descriptor?.description ? { description: descriptor.description } : {},
5676
+ status: binding.status,
5677
+ ...binding.statusReason ? { statusReason: binding.statusReason } : {},
5678
+ createdAt: binding.createdAt,
5679
+ ...binding.expiresAt ? { expiresAt: binding.expiresAt } : {},
5680
+ ...binding.lastReadAt ? { lastReadAt: binding.lastReadAt } : {},
5681
+ ...safeConfigPreview ? { safeConfigPreview } : {}
5682
+ };
5683
+ }
5684
+ function buildSubscriptionView(subscription, descriptors, deliveryCounts) {
5685
+ const descriptor = descriptors.get(`events:${subscription.extensionId}`);
5686
+ const counts = deliveryCounts.get(subscription.subscriptionId) ?? {
5687
+ pending: 0,
5688
+ failures: 0
5689
+ };
5690
+ const safeConfigPreview = buildSafeConfigPreview(subscription.config);
5691
+ return {
5692
+ id: subscription.subscriptionId,
5693
+ kind: "events",
5694
+ extensionId: subscription.extensionId,
5695
+ title: descriptor?.title ?? subscription.extensionId,
5696
+ ...descriptor?.description ? { description: descriptor.description } : {},
5697
+ status: subscription.status,
5698
+ ...subscription.statusReason ? { statusReason: subscription.statusReason } : {},
5699
+ createdAt: subscription.createdAt,
5700
+ ...subscription.expiresAt ? { expiresAt: subscription.expiresAt } : {},
5701
+ ...safeConfigPreview ? { safeConfigPreview } : {},
5702
+ pendingCount: counts.pending,
5703
+ ...subscription.suppressedCount ? { suppressedCount: subscription.suppressedCount } : {},
5704
+ ...counts.failures > 0 ? { deliveryFailureCount: counts.failures } : {},
5705
+ ...subscription.lastSuppressionReason ? { lastSuppressionReason: subscription.lastSuppressionReason } : {},
5706
+ ...subscription.lastGapAt ? { lastGapAt: subscription.lastGapAt } : {},
5707
+ ...subscription.gapReason ? { gapReason: subscription.gapReason } : {},
5708
+ delivery: subscription.delivery
5709
+ };
5710
+ }
5711
+ function needsAttention(observation) {
5712
+ return observation.status !== "active" || (observation.deliveryFailureCount ?? 0) > 0 || Boolean(observation.lastGapAt);
5713
+ }
5714
+ function buildSessionObservationsView(input) {
5715
+ const descriptors = buildDescriptorMap(input.descriptors);
5716
+ const deliveryCounts = buildDeliveryCounts(input.deliveries);
5717
+ const bindings = input.bindings.map((binding) => buildContextView(binding, descriptors));
5718
+ const subscriptions = input.subscriptions.map((subscription) => buildSubscriptionView(subscription, descriptors, deliveryCounts));
5719
+ return {
5720
+ sessionId: input.sessionId,
5721
+ bindings,
5722
+ subscriptions,
5723
+ counts: {
5724
+ total: bindings.length + subscriptions.length,
5725
+ context: bindings.length,
5726
+ events: subscriptions.length,
5727
+ needsAttention: [...bindings, ...subscriptions].filter(needsAttention).length
5728
+ }
5729
+ };
5730
+ }
5731
+ //#endregion
5558
5732
  //#region src/features/sessions/controllers/sessions.controller.ts
5559
5733
  const DEFAULT_SESSION_MESSAGE_PAGE_SIZE = 40;
5560
5734
  const MAX_SESSION_MESSAGE_PAGE_SIZE = 200;
@@ -5689,6 +5863,42 @@ var NcpSessionRoutesController = class {
5689
5863
  if (!payload) return c.json(err("NOT_FOUND", `ncp session not found: ${sessionId}`), 404);
5690
5864
  return c.json(ok(payload));
5691
5865
  };
5866
+ listSessionObservations = async (c) => {
5867
+ const sessionId = decodeURIComponent(c.req.param("sessionId"));
5868
+ if (!await this.options.kernel.sessionManager.getSession(sessionId)) return c.json(err("NOT_FOUND", `ncp session not found: ${sessionId}`), 404);
5869
+ const payload = buildSessionObservationsView({
5870
+ sessionId,
5871
+ ...await this.options.kernel.observations.listObservations(sessionId),
5872
+ descriptors: this.options.kernel.observations.discoverObservations()
5873
+ });
5874
+ return c.json(ok(payload));
5875
+ };
5876
+ updateSessionObservation = async (c) => {
5877
+ const sessionId = decodeURIComponent(c.req.param("sessionId"));
5878
+ const kind = c.req.param("kind");
5879
+ const id = decodeURIComponent(c.req.param("id"));
5880
+ if (!await this.options.kernel.sessionManager.getSession(sessionId)) return c.json(err("NOT_FOUND", `ncp session not found: ${sessionId}`), 404);
5881
+ const body = await readJson(c.req.raw);
5882
+ const action = body.ok && body.data?.action;
5883
+ if (action !== "pause" && action !== "resume" && action !== "remove") return c.json(err("INVALID_BODY", "action must be pause, resume, or remove"), 400);
5884
+ if (kind !== "context" && kind !== "events") return c.json(err("NOT_FOUND", `observation kind not found: ${kind}`), 404);
5885
+ const ref = kind === "context" ? {
5886
+ kind: "context_binding",
5887
+ id
5888
+ } : {
5889
+ kind: "event_subscription",
5890
+ id
5891
+ };
5892
+ const observation = await this.options.kernel.observations.getObservation(ref);
5893
+ if (!observation || observation.target.sessionId !== sessionId) return c.json(err("NOT_FOUND", `observation not found in session: ${sessionId}`), 404);
5894
+ await this.options.kernel.observations.updateObservation(action, ref);
5895
+ const state = await this.options.kernel.observations.listObservations(sessionId);
5896
+ return c.json(ok(buildSessionObservationsView({
5897
+ sessionId,
5898
+ ...state,
5899
+ descriptors: this.options.kernel.observations.discoverObservations()
5900
+ })));
5901
+ };
5692
5902
  getSessionSkills = async (c) => {
5693
5903
  const sessionManager = this.options.kernel.sessionManager;
5694
5904
  const sessionId = decodeURIComponent(c.req.param("sessionId"));
@@ -6020,7 +6230,7 @@ var PanelAppsRoutesController = class {
6020
6230
  };
6021
6231
  updatePanelAppPreferences = async (c) => {
6022
6232
  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);
6233
+ if (!body.ok || !isRecord$3(body.data)) return c.json(err("INVALID_PANEL_APP_PREFERENCES", "invalid panel app preferences"), 400);
6024
6234
  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
6235
  try {
6026
6236
  const preferences = {
@@ -6100,7 +6310,7 @@ var PanelAppsRoutesController = class {
6100
6310
  };
6101
6311
  createBridgeSession = async (c) => {
6102
6312
  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);
6313
+ 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
6314
  const panelAppId = typeof body.data.appId === "string" ? body.data.appId : body.data.panelAppId;
6105
6315
  if (typeof panelAppId !== "string") return c.json(err("INVALID_PANEL_APP_BRIDGE_SESSION", "invalid bridge session request"), 400);
6106
6316
  try {
@@ -6137,7 +6347,7 @@ var PanelAppsRoutesController = class {
6137
6347
  };
6138
6348
  sendAgentMessage = async (c) => {
6139
6349
  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);
6350
+ 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
6351
  try {
6142
6352
  return c.json(ok(await this.panelAppManager.sendAgentMessage(this.requireBridgeSessionToken(c), body.data.payload)));
6143
6353
  } catch (error) {
@@ -6146,7 +6356,7 @@ var PanelAppsRoutesController = class {
6146
6356
  };
6147
6357
  generateAgentObject = async (c) => {
6148
6358
  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);
6359
+ 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
6360
  try {
6151
6361
  return c.json(ok(await this.panelAppManager.generateAgentObject(this.requireBridgeSessionToken(c), body.data.input)));
6152
6362
  } catch (error) {
@@ -6219,7 +6429,7 @@ var PreferencesRoutesController = class {
6219
6429
  };
6220
6430
  update = async (c) => {
6221
6431
  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);
6432
+ if (!body.ok || !isRecord$3(body.data) || !Object.hasOwn(body.data, "value")) return c.json(err("INVALID_PREFERENCE", "preference value is required"), 400);
6223
6433
  try {
6224
6434
  const entry = await this.preferenceManager.setPreference(c.req.param("key"), body.data.value);
6225
6435
  return c.json(ok({
@@ -6262,7 +6472,7 @@ var ProjectsRoutesController = class {
6262
6472
  };
6263
6473
  create = async (c) => {
6264
6474
  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);
6475
+ if (!body.ok || !isRecord$3(body.data) || typeof body.data.name !== "string") return c.json(err("INVALID_PROJECT", "project name is required"), 400);
6266
6476
  try {
6267
6477
  return c.json(ok(await this.projectManager.createProject(body.data)), 201);
6268
6478
  } catch (error) {
@@ -6272,7 +6482,7 @@ var ProjectsRoutesController = class {
6272
6482
  };
6273
6483
  addExisting = async (c) => {
6274
6484
  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);
6485
+ if (!body.ok || !isRecord$3(body.data) || typeof body.data.rootPath !== "string") return c.json(err("INVALID_PROJECT", "project directory is required"), 400);
6276
6486
  try {
6277
6487
  const project = await this.projectManager.registerExistingProject(body.data.rootPath);
6278
6488
  if (!project) return c.json(err("PROJECT_PATH_IS_DEFAULT_WORKSPACE", "the default workspace cannot be registered as a project"), 400);
@@ -6334,13 +6544,13 @@ var ServiceAppsRoutesController = class {
6334
6544
  };
6335
6545
  invokeServiceAction = async (c) => {
6336
6546
  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);
6547
+ 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
6548
  try {
6339
6549
  const bridgeSession = this.requireBridgeSession(c);
6340
6550
  const payload = await this.params.serviceAppManager.invokeServiceAction(c.req.param("actionId"), {
6341
6551
  caller: bridgeSession.caller,
6342
6552
  declaredActions: bridgeSession.declaredActions,
6343
- input: isRecord$2(body.data.input) ? body.data.input : {}
6553
+ input: isRecord$3(body.data.input) ? body.data.input : {}
6344
6554
  });
6345
6555
  return c.json(ok(payload));
6346
6556
  } catch (error) {
@@ -6404,7 +6614,7 @@ var ServiceAppsRoutesController = class {
6404
6614
  };
6405
6615
  deleteServiceApp = async (c) => {
6406
6616
  const body = await readJson(c.req.raw);
6407
- const purgeData = body.ok && isRecord$2(body.data) && body.data.purgeData === true;
6617
+ const purgeData = body.ok && isRecord$3(body.data) && body.data.purgeData === true;
6408
6618
  try {
6409
6619
  return c.json(ok(await this.params.serviceAppManager.deleteServiceApp(c.req.param("appId"), purgeData)));
6410
6620
  } catch (error) {
@@ -6658,6 +6868,16 @@ var UiRouteRegistry = class {
6658
6868
  "/api/ncp/sessions/:sessionId/context/compact",
6659
6869
  ncpSession.compactSessionContext
6660
6870
  ],
6871
+ [
6872
+ "get",
6873
+ "/api/ncp/sessions/:sessionId/observations",
6874
+ ncpSession.listSessionObservations
6875
+ ],
6876
+ [
6877
+ "patch",
6878
+ "/api/ncp/sessions/:sessionId/observations/:kind/:id",
6879
+ ncpSession.updateSessionObservation
6880
+ ],
6661
6881
  [
6662
6882
  "get",
6663
6883
  "/api/ncp/sessions/:sessionId/usage",
@@ -7058,6 +7278,11 @@ var UiRouteRegistry = class {
7058
7278
  "/api/runtime/extensions",
7059
7279
  app.extensionRuntimeStatus
7060
7280
  ],
7281
+ [
7282
+ "get",
7283
+ "/api/runtime/extensions/catalog",
7284
+ app.extensionCatalog
7285
+ ],
7061
7286
  [
7062
7287
  "get",
7063
7288
  "/api/auth/status",