@nextclaw/kernel 0.9.0 → 0.9.1

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
@@ -1,7 +1,7 @@
1
1
  import { AUTOMATIC_UPDATE_CHECK_INTERVAL_MS, getAutomaticUpdateCheckDelay, resolveAutomaticUpdateCheckIntervalMs } from "./utils/automatic-update-check.utils.js";
2
2
  import { n as getUnsignedUpdateManifest, r as serializeUnsignedUpdateManifest, t as UpdateManifestReader } from "./update-manifest.types-C0qPrjGQ.js";
3
3
  import { createRequire } from "node:module";
4
- import { APP_NAME, AgentRouteResolver, BUILTIN_MAIN_AGENT_ID, CLEAR_THINKING_TOKENS, CONTEXT_COMPACTION_METADATA_KEY, ConfigSchema, ContextCompactionService, ContextWindowBudgetService, CronService, CronTool, DEFAULT_PANELS_DIR, DEFAULT_SERVICE_APPS_DIR, DEFAULT_SESSION_SEARCH_LIMIT, DEFAULT_WORKSPACE_REPOSITORY_IDENTITY_RESOLVER, DIAGNOSTIC_CORRELATION_METADATA_KEY, DiagnosticRuntime, EditFileTool, ExecTool, ExtensionChannelAdapter, GatewayTool, InputBudgetPruner, LLMProvider, ListDirTool, LiteLLMProvider, MAX_SESSION_SEARCH_LIMIT, MemoryGetTool, MemorySearchTool, MemoryStore, MessageBus, MessageTool, ProviderModelDiscoveryService, ProviderRegistry, ReadFileTool, RequestedSkillsMetadataReader, SILENT_REPLY_TOKEN, SessionProjectContextResolver, SessionSearchService, SkillsLoader, THINKING_LEVELS, ViewImageTool, WebFetchTool, WebSearchTool, WriteFileTool, buildCompressingCompactionCheckpoint, buildConfigSchema, buildContextWindowSnapshot, buildReloadPlan, buildSessionRequestToolResult, buildToolCatalogEntries, createAgentProfile, createAssistantStreamDeltaControlMessage, createAssistantStreamResetControlMessage, createCompletedSessionRequest, createFailedSessionRequest, createRunningSessionRequest, createRuntimeChildEnv, createTypingStopControlMessage, diffConfigPaths, ensureDir, estimateInputTokens, evaluateSilentReply, expandHome, findEffectiveAgentProfile, getConfigPath, getDataDir, getDataPath, getSessionsPath, getWorkspacePath, getWorkspacePathFromConfig, isNextclawControlMessage, loadConfig, mergeExtensionConfigView, modelSupportsVision, normalizeAgentProfileId, normalizeInlineSecretRefs, normalizeModelThinkingCapability, normalizeProviderModelConfig, normalizeToolParams, parseAgentScopedSessionKey, parseThinkingLevel, readCompressedContextCompactionCheckpoint, readOptionalString, readParentSessionId, readSessionProjectRoot, redactConfigObject, removeAgentProfile, resolveConfigSecrets, resolveDefaultAgentProfileId, resolveEffectiveAgentProfiles, resolveNextclawSelfManageGuidePaths, resolveProviderRuntime, resolveSessionProjectContext, resolveSessionWorkspacePath, resolveThinkingLevel, safeFilename, sanitizeOutboundAssistantContent, saveConfig, summarizeSessionRequestTask, toExtensionConfigView, updateAgentProfile } from "@nextclaw/core";
4
+ import { APP_NAME, AgentRouteResolver, BUILTIN_MAIN_AGENT_ID, CLEAR_THINKING_TOKENS, CONTEXT_COMPACTION_METADATA_KEY, ConfigSchema, ContextCompactionService, ContextWindowBudgetService, CronService, CronTool, DEFAULT_PANELS_DIR, DEFAULT_SERVICE_APPS_DIR, DEFAULT_SESSION_SEARCH_LIMIT, DEFAULT_WORKSPACE_REPOSITORY_IDENTITY_RESOLVER, DIAGNOSTIC_CORRELATION_METADATA_KEY, DiagnosticRuntime, EditFileTool, ExecTool, ExtensionChannelAdapter, GatewayTool, InputBudgetPruner, LLMProvider, ListDirTool, LiteLLMProvider, MAX_SESSION_SEARCH_LIMIT, MemoryGetTool, MemorySearchTool, MemoryStore, MessageBus, MessageTool, ProviderModelDiscoveryService, ProviderRegistry, ReadFileTool, RequestedSkillsMetadataReader, SILENT_REPLY_TOKEN, SessionProjectContextResolver, SessionSearchService, SkillsLoader, THINKING_LEVELS, ViewImageTool, WebFetchTool, WebSearchTool, WriteFileTool, buildCompressingCompactionCheckpoint, buildConfigSchema, buildContextWindowSnapshot, buildReloadPlan, buildSessionRequestToolResult, buildToolCatalogEntries, createAgentProfile, createAssistantStreamDeltaControlMessage, createAssistantStreamResetControlMessage, createCompletedSessionRequest, createFailedSessionRequest, createRunningSessionRequest, createRuntimeChildEnv, createTypingStopControlMessage, diffConfigPaths, ensureDir, estimateInputTokens, evaluateSilentReply, expandHome, findEffectiveAgentProfile, getConfigPath, getDataDir, getDataPath, getSessionsPath, getWorkspacePath, getWorkspacePathFromConfig, isNextclawControlMessage, loadConfig, mergeExtensionConfigView, modelSupportsVision, normalizeAgentProfileId, normalizeInlineSecretRefs, normalizeModelThinkingCapability, normalizeProviderModelConfig, normalizeToolParams, parseAgentScopedSessionKey, parseThinkingLevel, readCompressedContextCompactionCheckpoint, readOptionalString, readParentSessionId, readSessionProjectRoot, redactConfigObject, removeAgentProfile, resolveConfigSecrets, resolveDefaultAgentProfileId, resolveEffectiveAgentProfiles, resolveNextclawSelfManageGuidePaths, resolveProviderRuntime, resolveRuntimeCommandLaunch, resolveSessionProjectContext, resolveSessionWorkspacePath, resolveThinkingLevel, safeFilename, sanitizeOutboundAssistantContent, saveConfig, summarizeSessionRequestTask, toExtensionConfigView, updateAgentProfile } from "@nextclaw/core";
5
5
  import { NCP_AI_EXECUTION_METADATA_KEY, NCP_INTERNAL_VISIBILITY_METADATA_KEY, NcpEventType, createUnavailableNcpAiExecutionMetadata, isHiddenNcpMessage, normalizeAssistantText, readNcpAiExecutionMetadata, sanitizeAssistantReplyTags } from "@nextclaw/ncp";
6
6
  import { CHAT_CONTINUATION_TARGET_MESSAGE_METADATA_KEY, CHAT_CONVERSATION_EXCERPT_TOKEN_KIND, CHAT_INLINE_TOKENS_METADATA_KEY, CHAT_INLINE_TOKENS_SCHEMA_VERSION, CHAT_PROJECT_TOKEN_KIND, CHAT_SESSION_MATERIALIZATION_METADATA_KEY, CHAT_SYSTEM_OBJECT_TOKEN_KIND, CHAT_UI_RESOURCE_TOKEN_KIND, CHAT_WORKSPACE_DIRECTORY_TOKEN_KIND, CHAT_WORKSPACE_EXCERPT_TOKEN_KIND, CHAT_WORKSPACE_FILE_TOKEN_KIND, EventBus, Ingress, PANEL_APP_INLINE_HOST_CONTRACT, PANEL_APP_SCROLL_RESTORATION_CONTRACT, SYSTEM_OBJECT_REFERENCE_DEFAULT_LIMIT, SYSTEM_OBJECT_REFERENCE_MAX_LIMIT, SYSTEM_OBJECT_TYPE_CRON_JOB, SYSTEM_OBJECT_TYPE_INBOX_DELIVERY, UI_CONTENT_PARAMS_HOST_CONTRACT, classifyDiagnosticError, createSystemObjectReferenceUri, eventKeys, ingressKeys, isRuntimeDefaultModelValue, isSilentReplyNcpMessage, normalizeRuntimeModelSelectionMode, parseSystemObjectReferenceUri, readChatUiResourceReference, readInlineContentHeight, readSystemObjectResolvedReference, readUiContentParams } from "@nextclaw/shared";
7
7
  import { LocalAssetStore, buildAssetContentPath, buildNcpUserContent, buildOpenAiFunctionTool, isTextLikeAsset, ncpMessageToOpenAiMessages, validateToolArgs } from "@nextclaw/ncp-agent-runtime";
@@ -10,7 +10,7 @@ import { catchError, filter, from, lastValueFrom, tap } from "rxjs";
10
10
  import { appendFileSync, chmodSync, constants, createReadStream, existsSync, mkdirSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
11
11
  import path, { basename, delimiter, dirname, extname, isAbsolute, join, normalize, relative, resolve, sep } from "node:path";
12
12
  import { access, appendFile, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
13
- import { AppHomeService, AppInstallationService, AppInstanceInventoryService, AppInstanceStorageService, AppManifestService, AppRegistryService, AppServiceLaunchService, FileLockService, isAppComponentManifestBundle } from "@nextclaw/app-runtime";
13
+ import { AppHomeService, AppInstallationService, AppInstanceInventoryService, AppInstanceStorageService, AppManifestService, AppPlatformTargetService, AppRegistryService, AppServiceLaunchService, FileLockService, isAppComponentManifestBundle } from "@nextclaw/app-runtime";
14
14
  import { execFileSync, spawn } from "node:child_process";
15
15
  import { fileURLToPath } from "node:url";
16
16
  import { BUILTIN_PROVIDER_PLUGINS } from "@nextclaw/runtime";
@@ -1441,6 +1441,93 @@ var AgentRunSessionCommandManager = class {
1441
1441
  };
1442
1442
  };
1443
1443
  //#endregion
1444
+ //#region src/services/agent-run-input-delivery.service.ts
1445
+ /** Resolves public pending-input actions while SessionRun remains the state owner. */
1446
+ var AgentRunInputDeliveryService = class {
1447
+ constructor(agentRuntimeManager, sessionManager, sessionRunManager, publishQueueUpdated) {
1448
+ this.agentRuntimeManager = agentRuntimeManager;
1449
+ this.sessionManager = sessionManager;
1450
+ this.sessionRunManager = sessionRunManager;
1451
+ this.publishQueueUpdated = publishQueueUpdated;
1452
+ }
1453
+ listQueuedInputs = (sessionId) => {
1454
+ return this.sessionRunManager.getSessionRun(sessionId)?.listQueuedRequests().map(this.toQueuedInput) ?? [];
1455
+ };
1456
+ removeQueuedInput = (sessionId, queuedInputId) => {
1457
+ const removed = this.sessionRunManager.getSessionRun(sessionId)?.removeQueuedRequest(queuedInputId) ?? null;
1458
+ if (!removed) return null;
1459
+ this.publishQueueUpdated(sessionId);
1460
+ return this.toQueuedInput(removed);
1461
+ };
1462
+ listPendingInputs = (sessionId) => {
1463
+ return this.sessionRunManager.getSessionRun(sessionId)?.listPendingRequests().map((request) => ({
1464
+ ...this.toQueuedInput(request),
1465
+ placement: request.placement,
1466
+ intendedRunId: request.intendedRunId
1467
+ })) ?? [];
1468
+ };
1469
+ steerQueuedInput = async (sessionId, queuedInputId) => {
1470
+ const sessionRun = this.sessionRunManager.getSessionRun(sessionId);
1471
+ if (!sessionRun?.getActiveRunId()) return {
1472
+ ok: false,
1473
+ reason: "unavailable"
1474
+ };
1475
+ if (!sessionRun.listQueuedRequests().some(({ id }) => id === queuedInputId)) return {
1476
+ ok: false,
1477
+ reason: "not-found"
1478
+ };
1479
+ if (!await this.supportsNextStepInput(sessionRun)) return {
1480
+ ok: false,
1481
+ reason: "unavailable"
1482
+ };
1483
+ const moved = sessionRun.moveQueuedRequestToNextStep(queuedInputId);
1484
+ if (!moved) return {
1485
+ ok: false,
1486
+ reason: "unavailable"
1487
+ };
1488
+ this.publishQueueUpdated(sessionId);
1489
+ return {
1490
+ ok: true,
1491
+ input: this.toPendingInput(moved)
1492
+ };
1493
+ };
1494
+ enqueuePreferredSteeringInput = (sessionRun, request, session) => {
1495
+ try {
1496
+ return this.agentRuntimeManager.getOrCreate({
1497
+ agentRuntimeId: session.agentRuntimeId,
1498
+ session,
1499
+ sessionRun
1500
+ }).capabilities?.nextStepInput === true ? sessionRun.enqueueNextStepRequest(request, session) : null;
1501
+ } catch {
1502
+ return null;
1503
+ }
1504
+ };
1505
+ supportsNextStepInput = async (sessionRun) => {
1506
+ try {
1507
+ const session = await this.sessionManager.getAgentRunSession(sessionRun.sessionId);
1508
+ return this.agentRuntimeManager.getOrCreate({
1509
+ agentRuntimeId: session.agentRuntimeId,
1510
+ session,
1511
+ sessionRun
1512
+ }).capabilities?.nextStepInput === true;
1513
+ } catch {
1514
+ return false;
1515
+ }
1516
+ };
1517
+ toPendingInput = (request) => ({
1518
+ ...this.toQueuedInput(request),
1519
+ placement: "steering",
1520
+ intendedRunId: request.intendedRunId
1521
+ });
1522
+ toQueuedInput = (queuedRequest) => ({
1523
+ id: queuedRequest.id,
1524
+ sessionId: queuedRequest.request.message.sessionId,
1525
+ enqueuedAt: queuedRequest.enqueuedAt,
1526
+ message: structuredClone(queuedRequest.request.message),
1527
+ metadata: structuredClone(queuedRequest.request.metadata ?? {})
1528
+ });
1529
+ };
1530
+ //#endregion
1444
1531
  //#region src/utils/agent-run-execution-metadata.utils.ts
1445
1532
  const AGENT_RUN_EXECUTION_METADATA = {
1446
1533
  contractVersion: 1,
@@ -1515,7 +1602,8 @@ function toAgentRunRequest(envelope) {
1515
1602
  metadata: structuredClone(metadata),
1516
1603
  model: metadata.model,
1517
1604
  maxTokens: metadata.maxTokens,
1518
- thinkingEffort: metadata.thinkingEffort
1605
+ thinkingEffort: metadata.thinkingEffort,
1606
+ delivery: envelope.delivery
1519
1607
  };
1520
1608
  if (Array.isArray(envelope.content)) {
1521
1609
  const sessionId = readOptionalString$11(envelope.sessionId);
@@ -1613,7 +1701,8 @@ function toRunHandle(accepted) {
1613
1701
  userMessageId: accepted.userMessageId,
1614
1702
  assistantMessageId: null,
1615
1703
  runId: accepted.runId,
1616
- correlationId: accepted.correlationId
1704
+ correlationId: accepted.correlationId,
1705
+ delivery: accepted.delivery
1617
1706
  };
1618
1707
  }
1619
1708
  function findCompletedAssistantMessage(messages, messageId) {
@@ -1669,6 +1758,7 @@ var AgentRunRequestManager = class {
1669
1758
  cleanups = [];
1670
1759
  observedSessionRuns = /* @__PURE__ */ new Set();
1671
1760
  sessionCommandManager;
1761
+ pendingInputs;
1672
1762
  started = false;
1673
1763
  constructor(agentRuntimeManager, agentManager, configManager, agentContextWindowManager, eventBus, ingress, sessionManager, sessionRunManager, diagnostics) {
1674
1764
  this.agentRuntimeManager = agentRuntimeManager;
@@ -1680,6 +1770,7 @@ var AgentRunRequestManager = class {
1680
1770
  this.sessionManager = sessionManager;
1681
1771
  this.sessionRunManager = sessionRunManager;
1682
1772
  this.diagnostics = diagnostics;
1773
+ this.pendingInputs = new AgentRunInputDeliveryService(agentRuntimeManager, sessionManager, sessionRunManager, this.publishRunQueueUpdated);
1683
1774
  this.sessionCommandManager = new AgentRunSessionCommandManager(sessionManager, sessionRunManager, this.send);
1684
1775
  }
1685
1776
  start = () => {
@@ -1693,15 +1784,6 @@ var AgentRunRequestManager = class {
1693
1784
  this.sessionCommandManager.dispose();
1694
1785
  this.started = false;
1695
1786
  };
1696
- listQueuedInputs = (sessionId) => {
1697
- return this.sessionRunManager.getSessionRun(sessionId)?.listQueuedRequests().map(this.toQueuedInput) ?? [];
1698
- };
1699
- removeQueuedInput = (sessionId, queuedInputId) => {
1700
- const removed = this.sessionRunManager.getSessionRun(sessionId)?.removeQueuedRequest(queuedInputId) ?? null;
1701
- if (!removed) return null;
1702
- this.publishRunQueueUpdated(sessionId);
1703
- return this.toQueuedInput(removed);
1704
- };
1705
1787
  handleSendRequest = async (envelope) => {
1706
1788
  if (!envelope.payload) throw new Error("Invalid agent run send request.");
1707
1789
  return toRunHandle(await this.send(toAgentRunRequest(envelope.payload)));
@@ -1742,11 +1824,23 @@ var AgentRunRequestManager = class {
1742
1824
  ...request.message,
1743
1825
  sessionId: session.sessionId
1744
1826
  };
1745
- const queuedRequest = sessionRun.enqueueRequest({
1827
+ const normalizedRequest = {
1746
1828
  ...request,
1747
1829
  sessionId: session.sessionId,
1748
1830
  message: baseMessage
1749
- }, session);
1831
+ };
1832
+ const steeringRequest = request.delivery === "prefer-steer" && sessionRun.isRunning() ? this.pendingInputs.enqueuePreferredSteeringInput(sessionRun, normalizedRequest, session) : null;
1833
+ if (steeringRequest) {
1834
+ this.publishRunQueueUpdated(session.sessionId);
1835
+ return {
1836
+ sessionId: session.sessionId,
1837
+ userMessageId: steeringRequest.request.message.id,
1838
+ runId: steeringRequest.intendedRunId,
1839
+ correlationId: request.correlationId,
1840
+ delivery: "steered"
1841
+ };
1842
+ }
1843
+ const queuedRequest = sessionRun.enqueueRequest(normalizedRequest, session);
1750
1844
  const activeRequest = sessionRun.beginNextRun();
1751
1845
  this.publishRunQueueUpdated(session.sessionId);
1752
1846
  if (activeRequest?.id === queuedRequest.id) await this.startQueuedRun(sessionRun, activeRequest);
@@ -1755,7 +1849,8 @@ var AgentRunRequestManager = class {
1755
1849
  sessionId: session.sessionId,
1756
1850
  userMessageId: queuedRequest.request.message.id,
1757
1851
  runId: activeRequest?.id === queuedRequest.id ? activeRequest.runId : null,
1758
- correlationId: request.correlationId
1852
+ correlationId: request.correlationId,
1853
+ delivery: activeRequest?.id === queuedRequest.id ? "started" : "queued"
1759
1854
  };
1760
1855
  };
1761
1856
  startQueuedRun = async (sessionRun, activeRequest) => {
@@ -1816,10 +1911,10 @@ var AgentRunRequestManager = class {
1816
1911
  sessionRun
1817
1912
  });
1818
1913
  const { contextBlocks, tools } = await this.agentContextWindowManager.resolveRunSurface(providerRequest);
1819
- sessionRun.inbox.enqueue(message);
1820
1914
  this.startRuntimeRun({
1821
1915
  options: {
1822
1916
  contextBlocks,
1917
+ initialMessages: [message],
1823
1918
  session,
1824
1919
  sessionRun,
1825
1920
  signal: activeRequest.signal,
@@ -1858,6 +1953,7 @@ var AgentRunRequestManager = class {
1858
1953
  const { options, parentCorrelationId, requestRunStartedAt, runtime, spec } = params;
1859
1954
  const { session, sessionRun } = options;
1860
1955
  let messageCompletedSeen = false;
1956
+ const initialMessageIds = new Set(options.initialMessages.map(({ id }) => id));
1861
1957
  let executionMetadataSeen = false;
1862
1958
  let runtimeFailed = false;
1863
1959
  let runtimeCancelled = false;
@@ -1866,8 +1962,9 @@ var AgentRunRequestManager = class {
1866
1962
  let failureProviderCode;
1867
1963
  let failureFacts;
1868
1964
  let runStartedAt = requestRunStartedAt;
1869
- lastValueFrom(from(runtime.run(spec, options)).pipe(filter((event) => event.type !== NcpEventType.MessageSent || event.payload.message.role !== "user"), tap((event) => {
1965
+ lastValueFrom(from(runtime.run(spec, options)).pipe(filter((event) => event.type !== NcpEventType.MessageSent || !initialMessageIds.has(event.payload.message.id)), tap((event) => {
1870
1966
  const eventsToPublish = [];
1967
+ const consumedSteeringInput = event.type === NcpEventType.MessageSent && event.payload.message.role === "user";
1871
1968
  if (event.type === NcpEventType.RunError) {
1872
1969
  const classification = classifyDiagnosticError(event.payload.error, options.signal);
1873
1970
  runtimeFailed = true;
@@ -1895,6 +1992,7 @@ var AgentRunRequestManager = class {
1895
1992
  }
1896
1993
  eventsToPublish.push(event);
1897
1994
  eventsToPublish.forEach(this.publishNcpEvent);
1995
+ if (consumedSteeringInput) this.publishRunQueueUpdated(session.sessionId);
1898
1996
  }), catchError(async (error) => {
1899
1997
  const classification = classifyDiagnosticError(error, options.signal);
1900
1998
  runtimeFailed = true;
@@ -1995,13 +2093,6 @@ var AgentRunRequestManager = class {
1995
2093
  source: "agent-run-request"
1996
2094
  });
1997
2095
  };
1998
- toQueuedInput = (queuedRequest) => ({
1999
- id: queuedRequest.id,
2000
- sessionId: queuedRequest.request.message.sessionId,
2001
- enqueuedAt: queuedRequest.enqueuedAt,
2002
- message: structuredClone(queuedRequest.request.message),
2003
- metadata: structuredClone(queuedRequest.request.metadata ?? {})
2004
- });
2005
2096
  getOrCreateSessionForRequest = async (request) => {
2006
2097
  const sessionMaterialization = readSessionMaterialization(request.metadata ?? {});
2007
2098
  if (sessionMaterialization && (request.sessionId || request.peerId)) throw new Error("session_materialization requires a new session request.");
@@ -2925,6 +3016,7 @@ var AppPackageManager = class {
2925
3016
  installationService;
2926
3017
  manifestService = new AppManifestService();
2927
3018
  operationManager;
3019
+ platformTargetService = new AppPlatformTargetService();
2928
3020
  presentationService = new AppPackagePresentationService();
2929
3021
  registryService;
2930
3022
  runtimeHooks = EMPTY_RUNTIME_HOOKS;
@@ -2946,7 +3038,23 @@ var AppPackageManager = class {
2946
3038
  start = async () => await this.ensureBuiltInPackages();
2947
3039
  listPackages = async (options = {}) => {
2948
3040
  const records = await this.registryService.listApps();
2949
- return { entries: await Promise.all(records.map(async (record) => await this.toPackageView(await this.installationService.info(record.appId, { measureStorageUsage: options.includeStorageUsage !== false })))) };
3041
+ return {
3042
+ hostTarget: this.readHostTarget(),
3043
+ entries: await Promise.all(records.map(async (record) => await this.toPackageView(await this.installationService.info(record.appId, { measureStorageUsage: options.includeStorageUsage !== false }))))
3044
+ };
3045
+ };
3046
+ readHostTarget = () => {
3047
+ try {
3048
+ const target = this.platformTargetService.readHostTarget();
3049
+ return {
3050
+ key: this.platformTargetService.toTargetKey(target),
3051
+ operatingSystem: target.os,
3052
+ architecture: target.arch,
3053
+ ...target.os === "darwin" ? {} : { abi: target.abi }
3054
+ };
3055
+ } catch {
3056
+ return;
3057
+ }
2950
3058
  };
2951
3059
  listInstalledDataOwners = async () => (await this.registryService.listApps()).map((record) => ({
2952
3060
  id: record.appId,
@@ -3745,10 +3853,12 @@ var ExtensionLifecycleService = class {
3745
3853
  if (record.state === "running") return;
3746
3854
  const { generation, manifest, ready, token } = this.prepareStart(record);
3747
3855
  this.recordLifecycle(record, "process.spawn.started", "started");
3748
- const child = spawn(manifest.server.command === "node" || manifest.server.command === "node.exe" ? process.execPath : manifest.server.command, manifest.server.args ?? [], {
3856
+ const launch = resolveRuntimeCommandLaunch(manifest.server.command);
3857
+ const child = spawn(launch.command, manifest.server.args ?? [], {
3749
3858
  cwd: manifest.rootDir,
3750
3859
  env: createRuntimeChildEnv(process.env, {
3751
3860
  ...manifest.server.env,
3861
+ ...launch.envPatch,
3752
3862
  NEXTCLAW_EXTENSION_ID: manifest.id,
3753
3863
  NEXTCLAW_EXTENSION_ENDPOINT: record.endpoint,
3754
3864
  NEXTCLAW_EXTENSION_GENERATION: generation,
@@ -9561,28 +9671,103 @@ var ProjectManager = class {
9561
9671
  isMissingFileError = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
9562
9672
  };
9563
9673
  //#endregion
9674
+ //#region src/utils/product-activity.utils.ts
9675
+ function resolveHumanProductActivitySource(request) {
9676
+ const metadata = request.metadata ?? {};
9677
+ if (metadata.session_origin === "cron") return null;
9678
+ if (metadata[CHAT_SESSION_MATERIALIZATION_METADATA_KEY]?.kind === "child") return null;
9679
+ return request.channel ? "channel" : "direct";
9680
+ }
9681
+ function recordProductActivityBestEffort(sink, signal) {
9682
+ if (!sink) return;
9683
+ try {
9684
+ Promise.resolve(sink.record(signal)).catch(() => void 0);
9685
+ } catch {}
9686
+ }
9687
+ //#endregion
9564
9688
  //#region src/managers/session-run.manager.ts
9565
- var MessageInbox = class {
9566
- messages = [];
9567
- enqueue = (message) => {
9568
- this.messages.push(message);
9689
+ /** Single owner for inputs waiting for either the next run or the next safe step. */
9690
+ var SessionPendingInputs = class {
9691
+ nextRun = [];
9692
+ nextStep = [];
9693
+ claimedNextStep = /* @__PURE__ */ new Map();
9694
+ enqueueNextRun = (request) => {
9695
+ this.nextRun.push(request);
9696
+ };
9697
+ enqueueNextStep = (request, intendedRunId) => {
9698
+ this.nextStep.push({
9699
+ ...request,
9700
+ intendedRunId
9701
+ });
9569
9702
  };
9570
- drain = () => {
9571
- return this.messages.splice(0, this.messages.length);
9703
+ moveNextRunToNextStep = (requestId, intendedRunId) => {
9704
+ const index = this.nextRun.findIndex(({ id }) => id === requestId);
9705
+ if (index < 0) return null;
9706
+ const [request] = this.nextRun.splice(index, 1);
9707
+ if (!request) return null;
9708
+ this.enqueueNextStep(request, intendedRunId);
9709
+ return this.toPendingRequest(request, "steering", intendedRunId);
9710
+ };
9711
+ list = () => [...this.nextStep.map((request) => this.toPendingRequest(request, "steering", request.intendedRunId)), ...this.nextRun.map((request) => this.toPendingRequest(request, "queued", null))];
9712
+ listNextRun = () => structuredClone(this.nextRun);
9713
+ removeNextRun = (requestId) => {
9714
+ const index = this.nextRun.findIndex(({ id }) => id === requestId);
9715
+ if (index < 0) return null;
9716
+ const [removed] = this.nextRun.splice(index, 1);
9717
+ return removed ? structuredClone(removed) : null;
9572
9718
  };
9719
+ shiftNextRun = () => {
9720
+ const request = this.nextRun.shift();
9721
+ return request ? structuredClone(request) : null;
9722
+ };
9723
+ claimNextStep = (runId) => {
9724
+ const claimed = [];
9725
+ for (let index = this.nextStep.length - 1; index >= 0; index -= 1) {
9726
+ const request = this.nextStep[index];
9727
+ if (request?.intendedRunId !== runId) continue;
9728
+ this.nextStep.splice(index, 1);
9729
+ claimed.unshift(request);
9730
+ }
9731
+ for (const request of claimed) this.claimedNextStep.set(request.id, request);
9732
+ return structuredClone(claimed);
9733
+ };
9734
+ acknowledgeNextStep = (requestIds) => {
9735
+ for (const requestId of requestIds) this.claimedNextStep.delete(requestId);
9736
+ };
9737
+ restoreNextStep = (runId) => {
9738
+ const restored = [...this.nextStep.filter(({ intendedRunId }) => intendedRunId === runId), ...[...this.claimedNextStep.values()].filter(({ intendedRunId }) => intendedRunId === runId)].sort((left, right) => left.enqueuedAt.localeCompare(right.enqueuedAt));
9739
+ if (restored.length === 0) return;
9740
+ for (let index = this.nextStep.length - 1; index >= 0; index -= 1) if (this.nextStep[index]?.intendedRunId === runId) this.nextStep.splice(index, 1);
9741
+ for (const request of restored) this.claimedNextStep.delete(request.id);
9742
+ this.nextRun.unshift(...restored.map(({ intendedRunId: _intendedRunId, ...request }) => request));
9743
+ };
9744
+ get hasNextRun() {
9745
+ return this.nextRun.length > 0;
9746
+ }
9747
+ clear = () => {
9748
+ this.nextRun.length = 0;
9749
+ this.nextStep.length = 0;
9750
+ this.claimedNextStep.clear();
9751
+ };
9752
+ toPendingRequest = (request, placement, intendedRunId) => structuredClone({
9753
+ ...request,
9754
+ placement,
9755
+ intendedRunId
9756
+ });
9573
9757
  };
9574
9758
  function isConversationStateEvent(event) {
9575
9759
  return event.type !== NcpEventType.ContextWindowUpdated;
9576
9760
  }
9577
9761
  var SessionRun = class {
9578
- inbox = new MessageInbox();
9579
9762
  sessionId;
9580
9763
  statusListeners = /* @__PURE__ */ new Set();
9581
- queuedRequests = [];
9764
+ pendingInputs = new SessionPendingInputs();
9582
9765
  activeRunId = null;
9583
9766
  activeRunController = null;
9584
- constructor(seed, stateManager = new DefaultNcpAgentConversationStateManager()) {
9767
+ activeProductActivitySource = null;
9768
+ constructor(seed, stateManager = new DefaultNcpAgentConversationStateManager(), productActivitySink) {
9585
9769
  this.stateManager = stateManager;
9770
+ this.productActivitySink = productActivitySink;
9586
9771
  this.sessionId = seed.sessionId;
9587
9772
  this.stateManager.hydrate({
9588
9773
  sessionId: seed.sessionId,
@@ -9620,27 +9805,61 @@ var SessionRun = class {
9620
9805
  request: structuredClone(request),
9621
9806
  session: structuredClone(session)
9622
9807
  };
9623
- this.queuedRequests.push(queuedRequest);
9808
+ this.pendingInputs.enqueueNextRun(queuedRequest);
9624
9809
  this.emitStatusChangeIfNeeded(wasBusy);
9625
9810
  return structuredClone(queuedRequest);
9626
9811
  };
9627
- listQueuedRequests = () => structuredClone(this.queuedRequests);
9812
+ listQueuedRequests = () => this.pendingInputs.listNextRun();
9813
+ listPendingRequests = () => this.pendingInputs.list();
9628
9814
  removeQueuedRequest = (queuedRequestId) => {
9629
- const index = this.queuedRequests.findIndex(({ id }) => id === queuedRequestId);
9630
- if (index < 0) return null;
9631
9815
  const wasBusy = this.isBusy();
9632
- const [removed] = this.queuedRequests.splice(index, 1);
9816
+ const removed = this.pendingInputs.removeNextRun(queuedRequestId);
9633
9817
  this.emitStatusChangeIfNeeded(wasBusy);
9634
- return removed ? structuredClone(removed) : null;
9818
+ return removed;
9819
+ };
9820
+ moveQueuedRequestToNextStep = (queuedRequestId) => {
9821
+ if (!this.activeRunId) return null;
9822
+ return this.pendingInputs.moveNextRunToNextStep(queuedRequestId, this.activeRunId);
9823
+ };
9824
+ enqueueNextStepRequest = (request, session) => {
9825
+ if (!this.activeRunId) return null;
9826
+ const pendingRequest = {
9827
+ id: `pending-input-${randomUUID()}`,
9828
+ runId: `agent-run-${randomUUID()}`,
9829
+ enqueuedAt: (/* @__PURE__ */ new Date()).toISOString(),
9830
+ request: structuredClone(request),
9831
+ session: structuredClone(session)
9832
+ };
9833
+ this.pendingInputs.enqueueNextStep(pendingRequest, this.activeRunId);
9834
+ return structuredClone({
9835
+ ...pendingRequest,
9836
+ placement: "steering",
9837
+ intendedRunId: this.activeRunId
9838
+ });
9635
9839
  };
9840
+ claimNextStepRequests = (runId) => this.pendingInputs.claimNextStep(runId).map((request) => ({
9841
+ ...request,
9842
+ placement: "steering",
9843
+ intendedRunId: runId
9844
+ }));
9845
+ acknowledgeNextStepRequests = (requestIds) => {
9846
+ this.pendingInputs.acknowledgeNextStep(requestIds);
9847
+ };
9848
+ getActiveRunId = () => this.activeRunId;
9636
9849
  beginNextRun = () => {
9637
9850
  if (this.activeRunId) return null;
9638
9851
  const wasBusy = this.isBusy();
9639
- const queuedRequest = this.queuedRequests.shift();
9852
+ const queuedRequest = this.pendingInputs.shiftNextRun();
9640
9853
  if (!queuedRequest) return null;
9641
9854
  const controller = new AbortController();
9642
9855
  this.activeRunId = queuedRequest.runId;
9643
9856
  this.activeRunController = controller;
9857
+ this.activeProductActivitySource = resolveHumanProductActivitySource(queuedRequest.request);
9858
+ if (this.activeProductActivitySource) recordProductActivityBestEffort(this.productActivitySink, {
9859
+ kind: "intent_accepted",
9860
+ occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
9861
+ source: this.activeProductActivitySource
9862
+ });
9644
9863
  this.emitStatusChangeIfNeeded(wasBusy);
9645
9864
  return {
9646
9865
  ...structuredClone(queuedRequest),
@@ -9654,7 +9873,7 @@ var SessionRun = class {
9654
9873
  return true;
9655
9874
  };
9656
9875
  isRunning = () => this.activeRunId !== null;
9657
- isBusy = () => this.isRunning() || this.queuedRequests.length > 0;
9876
+ isBusy = () => this.isRunning() || this.pendingInputs.hasNextRun;
9658
9877
  dispose = () => {
9659
9878
  const wasBusy = this.isBusy();
9660
9879
  this.activeRunController?.abort({
@@ -9664,7 +9883,8 @@ var SessionRun = class {
9664
9883
  });
9665
9884
  this.activeRunController = null;
9666
9885
  this.activeRunId = null;
9667
- this.queuedRequests.length = 0;
9886
+ this.activeProductActivitySource = null;
9887
+ this.pendingInputs.clear();
9668
9888
  this.emitStatusChangeIfNeeded(wasBusy);
9669
9889
  this.statusListeners.clear();
9670
9890
  };
@@ -9673,8 +9893,15 @@ var SessionRun = class {
9673
9893
  for (const event of events) {
9674
9894
  if (event.type === NcpEventType.RunStarted && event.payload.runId) this.activeRunId = event.payload.runId;
9675
9895
  if ((event.type === NcpEventType.MessageAbort || event.type === NcpEventType.RunFinished || event.type === NcpEventType.RunError) && (!event.payload.runId || event.payload.runId === this.activeRunId)) {
9896
+ if (event.type === NcpEventType.RunFinished && this.activeProductActivitySource) recordProductActivityBestEffort(this.productActivitySink, {
9897
+ kind: "run_succeeded",
9898
+ occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
9899
+ source: this.activeProductActivitySource
9900
+ });
9901
+ if (this.activeRunId) this.pendingInputs.restoreNextStep(this.activeRunId);
9676
9902
  this.activeRunId = null;
9677
9903
  this.activeRunController = null;
9904
+ this.activeProductActivitySource = null;
9678
9905
  }
9679
9906
  }
9680
9907
  this.emitStatusChangeIfNeeded(wasBusy);
@@ -9688,8 +9915,9 @@ var SessionRun = class {
9688
9915
  var SessionRunManager = class {
9689
9916
  runs = /* @__PURE__ */ new Map();
9690
9917
  pendingCreations = /* @__PURE__ */ new Map();
9691
- constructor(sessionManager) {
9918
+ constructor(sessionManager, productActivitySink) {
9692
9919
  this.sessionManager = sessionManager;
9920
+ this.productActivitySink = productActivitySink;
9693
9921
  }
9694
9922
  getSessionRun = (sessionId) => this.runs.get(sessionId) ?? null;
9695
9923
  isSessionRunning = (sessionId) => this.runs.get(sessionId.trim())?.isBusy() ?? false;
@@ -9709,7 +9937,7 @@ var SessionRunManager = class {
9709
9937
  const run = new SessionRun({
9710
9938
  messages: await this.sessionManager.listSessionMessages(sessionId),
9711
9939
  sessionId
9712
- });
9940
+ }, void 0, this.productActivitySink);
9713
9941
  this.runs.set(sessionId, run);
9714
9942
  return run;
9715
9943
  };
@@ -12039,15 +12267,14 @@ var NcpAgentRuntimeWrapper = class {
12039
12267
  this.params = params;
12040
12268
  }
12041
12269
  run = async function* (spec, options) {
12042
- const { contextBlocks, session, sessionRun, signal, tools } = options;
12270
+ const { contextBlocks, initialMessages, session, sessionRun, signal, tools } = options;
12043
12271
  this.currentTools = tools.map(this.toOpenAiTool);
12044
- const messages = sessionRun.inbox.drain();
12045
12272
  let executionMetadataSeen = false;
12046
12273
  try {
12047
12274
  const input = {
12048
12275
  sessionId: sessionRun.sessionId,
12049
12276
  runId: spec.runId,
12050
- messages,
12277
+ messages: initialMessages,
12051
12278
  contextBlocks: this.params.injectNextclawContext ? contextBlocks : void 0,
12052
12279
  correlationId: spec.correlationId,
12053
12280
  metadata: this.buildMetadata(session, spec),
@@ -12170,6 +12397,7 @@ var AgentRunRuntimeContribution = class {
12170
12397
  runPreflight: this.runNativePreflight
12171
12398
  });
12172
12399
  return {
12400
+ capabilities: { nextStepInput: true },
12173
12401
  run: (spec, options) => runtime.run(spec, {
12174
12402
  ...options,
12175
12403
  contextBlocks: entry.injectNextclawContext === false ? [] : options.contextBlocks
@@ -15520,28 +15748,34 @@ var McpServiceAppRuntimeService = class {
15520
15748
  await this.lifecycleManager.closeAll();
15521
15749
  this.states.clear();
15522
15750
  };
15523
- toMcpServerRecord = (app, manifest) => ({
15524
- name: app.id,
15525
- definition: {
15526
- enabled: app.enabled,
15527
- transport: {
15528
- type: "stdio",
15529
- command: manifest.command,
15530
- args: manifest.args,
15531
- cwd: app.dirPath,
15532
- env: createRuntimeChildEnv(process.env, this.createAppRuntimeEnv(app)),
15533
- stderr: "pipe"
15534
- },
15535
- scope: {
15536
- allAgents: false,
15537
- agents: []
15538
- },
15539
- policy: {
15540
- trust: "explicit",
15541
- start: "eager"
15751
+ toMcpServerRecord = (app, manifest) => {
15752
+ const launch = resolveRuntimeCommandLaunch(manifest.command);
15753
+ return {
15754
+ name: app.id,
15755
+ definition: {
15756
+ enabled: app.enabled,
15757
+ transport: {
15758
+ type: "stdio",
15759
+ command: launch.command,
15760
+ args: manifest.args,
15761
+ cwd: app.dirPath,
15762
+ env: createRuntimeChildEnv(process.env, {
15763
+ ...this.createAppRuntimeEnv(app),
15764
+ ...launch.envPatch
15765
+ }),
15766
+ stderr: "pipe"
15767
+ },
15768
+ scope: {
15769
+ allAgents: false,
15770
+ agents: []
15771
+ },
15772
+ policy: {
15773
+ trust: "explicit",
15774
+ start: "eager"
15775
+ }
15542
15776
  }
15543
- }
15544
- });
15777
+ };
15778
+ };
15545
15779
  createAppRuntimeEnv = (app) => {
15546
15780
  const env = {};
15547
15781
  if (app.storage) {
@@ -16044,9 +16278,7 @@ function mergeServiceAppRuntimeActions({ record, manifest, runtimeActions }) {
16044
16278
  return [...declared, ...undeclared].sort((left, right) => left.id.localeCompare(right.id));
16045
16279
  }
16046
16280
  //#endregion
16047
- //#region src/managers/service-app.manager.ts
16048
- const SERVICE_ACTION_GRANTS_FILE_NAME = ".service-action-grants.json";
16049
- const SERVICE_APP_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
16281
+ //#region src/utils/service-app-error.utils.ts
16050
16282
  var ServiceAppError = class extends Error {
16051
16283
  constructor(code, message) {
16052
16284
  super(message);
@@ -16055,6 +16287,10 @@ var ServiceAppError = class extends Error {
16055
16287
  }
16056
16288
  };
16057
16289
  const isServiceAppError = (error) => error instanceof ServiceAppError;
16290
+ //#endregion
16291
+ //#region src/managers/service-app.manager.ts
16292
+ const SERVICE_ACTION_GRANTS_FILE_NAME = ".service-action-grants.json";
16293
+ const SERVICE_APP_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
16058
16294
  var ServiceAppManager = class {
16059
16295
  removalService = new ServiceAppRemovalService();
16060
16296
  runtimeService;
@@ -16116,15 +16352,19 @@ var ServiceAppManager = class {
16116
16352
  if (!Object.hasOwn(manifest.actions, actionName)) throw new ServiceAppError("SERVICE_APP_ACTION_NOT_FOUND", "service action not found");
16117
16353
  const declaredRisk = manifest.actions[actionName]?.risk ?? "dangerous";
16118
16354
  if (!await this.createGrantStore().isGranted(request.caller, actionId, declaredRisk)) throw new ServiceAppError("AUTHORIZATION_REQUIRED", `This panel app needs permission to call ${actionId}.`);
16119
- return {
16120
- actionId,
16121
- result: await this.runtimeService.invokeAction({
16122
- app: record,
16123
- manifest,
16124
- actionName,
16125
- input: request.input ?? {}
16126
- })
16127
- };
16355
+ try {
16356
+ return {
16357
+ actionId,
16358
+ result: await this.runtimeService.invokeAction({
16359
+ app: record,
16360
+ manifest,
16361
+ actionName,
16362
+ input: request.input ?? {}
16363
+ })
16364
+ };
16365
+ } catch (error) {
16366
+ throw new ServiceAppError("SERVICE_APP_RUNTIME_FAILED", `Service App ${record.id} action ${actionName} failed: ${error instanceof Error ? error.message : String(error)}`);
16367
+ }
16128
16368
  };
16129
16369
  grantServiceAction = async (actionId, request) => {
16130
16370
  const [grant] = await this.grantServiceActions([actionId], request);
@@ -16519,7 +16759,7 @@ var NextclawKernel = class {
16519
16759
  })
16520
16760
  });
16521
16761
  this.contextCompactionManager = new AgentRunContextCompactionManager(this.agents, this.llmProviders);
16522
- this.sessionRunManager = new SessionRunManager(this.sessionManager);
16762
+ this.sessionRunManager = new SessionRunManager(this.sessionManager, options.productActivitySink);
16523
16763
  this.sessionContextCompactionManager = new SessionContextCompactionManager(this.agentRuntimeManager, this.eventBus, this.sessionManager, this.sessionRunManager);
16524
16764
  this.agentRunRequestManager = new AgentRunRequestManager(this.agentRuntimeManager, this.agents, this.configManager, this.agentContextWindowManager, this.eventBus, this.ingress, this.sessionManager, this.sessionRunManager, this.diagnostics);
16525
16765
  this.contributions = [