@nextclaw/kernel 0.10.0 → 0.10.2

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
@@ -2,7 +2,7 @@ import { AUTOMATIC_UPDATE_CHECK_INTERVAL_MS, getAutomaticUpdateCheckDelay, resol
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
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
- import { NCP_AI_EXECUTION_METADATA_KEY, NCP_INTERNAL_VISIBILITY_METADATA_KEY, NcpEventType, OBSERVATION_EVENT_EXTENSION_TYPE, createUnavailableNcpAiExecutionMetadata, isHiddenNcpMessage, normalizeAssistantText, readNcpAiExecutionMetadata, sanitizeAssistantReplyTags } from "@nextclaw/ncp";
5
+ import { NCP_AI_EXECUTION_METADATA_KEY, NCP_INTERNAL_VISIBILITY_METADATA_KEY, NCP_RUN_TRIGGER_METADATA_KEY, NcpEventType, OBSERVATION_EVENT_EXTENSION_TYPE, createUnavailableNcpAiExecutionMetadata, isHiddenNcpMessage, normalizeAssistantText, parseNcpRunTriggerInput, 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, Contribution as Contribution$1, EventBus, EventBus as EventBus$1, Ingress, Ingress as Ingress$1, 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, createTypedKey, eventKeys, eventKeys as eventKeys$1, ingressKeys, ingressKeys as ingressKeys$1, 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";
8
8
  import { createHash, createHmac, randomBytes, randomUUID, scryptSync, timingSafeEqual } from "node:crypto";
@@ -1514,26 +1514,26 @@ const SESSION_ACTIVITY_PREVIEW_STATUS_KINDS = new Set([
1514
1514
  "run-failed",
1515
1515
  "run-interrupted"
1516
1516
  ]);
1517
- function isRecord$18(value) {
1517
+ function isRecord$19(value) {
1518
1518
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1519
1519
  }
1520
- function readOptionalString$12(value) {
1520
+ function readOptionalString$13(value) {
1521
1521
  if (typeof value !== "string") return;
1522
1522
  const trimmed = value.trim();
1523
1523
  return trimmed.length > 0 ? trimmed : void 0;
1524
1524
  }
1525
1525
  function readSessionActivityPreviewMetadata(value) {
1526
- if (!isRecord$18(value)) return null;
1526
+ if (!isRecord$19(value)) return null;
1527
1527
  const state = value.state;
1528
- const timestamp = readOptionalString$12(value.timestamp);
1528
+ const timestamp = readOptionalString$13(value.timestamp);
1529
1529
  if (!SESSION_ACTIVITY_PREVIEW_STATES.has(state) || !timestamp) return null;
1530
- const statusKind = readOptionalString$12(value.statusKind);
1530
+ const statusKind = readOptionalString$13(value.statusKind);
1531
1531
  return {
1532
1532
  state,
1533
1533
  timestamp,
1534
1534
  statusKind: SESSION_ACTIVITY_PREVIEW_STATUS_KINDS.has(statusKind) ? statusKind : void 0,
1535
- statusText: readOptionalString$12(value.statusText),
1536
- replyText: readOptionalString$12(value.replyText)
1535
+ statusText: readOptionalString$13(value.statusText),
1536
+ replyText: readOptionalString$13(value.replyText)
1537
1537
  };
1538
1538
  }
1539
1539
  function compareIsoTimestamp(left, right) {
@@ -1617,7 +1617,7 @@ var SessionActivityPreviewEventService = class {
1617
1617
  //#endregion
1618
1618
  //#region src/managers/agent-run-session-command.manager.ts
1619
1619
  const CONTINUATION_PROMPT = "Continue from where you stopped. Preserve completed work and avoid repeating it.";
1620
- function isRecord$17(value) {
1620
+ function isRecord$18(value) {
1621
1621
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1622
1622
  }
1623
1623
  function hasSendableMessageContent(message) {
@@ -1625,7 +1625,7 @@ function hasSendableMessageContent(message) {
1625
1625
  }
1626
1626
  function readSessionActivityState(metadata) {
1627
1627
  const preview = metadata?.[SESSION_ACTIVITY_PREVIEW_METADATA_KEY];
1628
- return isRecord$17(preview) && typeof preview.state === "string" ? preview.state : null;
1628
+ return isRecord$18(preview) && typeof preview.state === "string" ? preview.state : null;
1629
1629
  }
1630
1630
  var AgentRunSessionCommandManager = class {
1631
1631
  pendingCommands = /* @__PURE__ */ new Map();
@@ -1676,6 +1676,13 @@ var AgentRunSessionCommandManager = class {
1676
1676
  role: "user",
1677
1677
  status: "final",
1678
1678
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1679
+ },
1680
+ trigger: {
1681
+ actor: "human",
1682
+ source: "message-edit",
1683
+ triggeredAt: (/* @__PURE__ */ new Date()).toISOString(),
1684
+ sourceSessionId: request.sessionId,
1685
+ sourceMessageId: request.messageId
1679
1686
  }
1680
1687
  });
1681
1688
  };
@@ -1685,6 +1692,7 @@ var AgentRunSessionCommandManager = class {
1685
1692
  const activityState = readSessionActivityState(sourceRecord.metadata);
1686
1693
  if (activityState !== "cancelled" && activityState !== "failed") throw new Error("Only a cancelled or failed session can continue running.");
1687
1694
  const latestVisibleConversationMessage = [...sourceRecord.messages].reverse().find((message) => (message.role === "user" || message.role === "assistant") && !isHiddenNcpMessage(message) && !isSilentReplyNcpMessage(message));
1695
+ const triggeredAt = (/* @__PURE__ */ new Date()).toISOString();
1688
1696
  return await this.send({
1689
1697
  correlationId: request.correlationId,
1690
1698
  sessionId: request.sessionId,
@@ -1693,7 +1701,7 @@ var AgentRunSessionCommandManager = class {
1693
1701
  sessionId: request.sessionId,
1694
1702
  role: "user",
1695
1703
  status: "final",
1696
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1704
+ timestamp: triggeredAt,
1697
1705
  parts: [{
1698
1706
  type: "text",
1699
1707
  text: CONTINUATION_PROMPT
@@ -1702,6 +1710,13 @@ var AgentRunSessionCommandManager = class {
1702
1710
  [NCP_INTERNAL_VISIBILITY_METADATA_KEY]: "hidden",
1703
1711
  [CHAT_CONTINUATION_TARGET_MESSAGE_METADATA_KEY]: latestVisibleConversationMessage?.role === "assistant" ? latestVisibleConversationMessage.id : void 0
1704
1712
  }
1713
+ },
1714
+ trigger: {
1715
+ actor: "human",
1716
+ source: "continue-run",
1717
+ triggeredAt,
1718
+ sourceSessionId: request.sessionId,
1719
+ ...latestVisibleConversationMessage ? { sourceMessageId: latestVisibleConversationMessage.id } : {}
1705
1720
  }
1706
1721
  });
1707
1722
  };
@@ -1772,13 +1787,14 @@ var AgentRunInputDeliveryService = class {
1772
1787
  input: this.toPendingInput(moved)
1773
1788
  };
1774
1789
  };
1775
- enqueuePreferredSteeringInput = (sessionRun, request, session) => {
1790
+ promotePreferredSteeringInput = (sessionRun, queuedInputId, session) => {
1776
1791
  try {
1777
- return this.agentRuntimeManager.getOrCreate({
1792
+ if (this.agentRuntimeManager.getOrCreate({
1778
1793
  agentRuntimeId: session.agentRuntimeId,
1779
1794
  session,
1780
1795
  sessionRun
1781
- }).capabilities?.nextStepInput === true ? sessionRun.enqueueNextStepRequest(request, session) : null;
1796
+ }).capabilities?.nextStepInput !== true) return null;
1797
+ return sessionRun.moveQueuedRequestToNextStep(queuedInputId);
1782
1798
  } catch {
1783
1799
  return null;
1784
1800
  }
@@ -1950,13 +1966,13 @@ function createUnavailableAiExecutionMetadataEvent(params) {
1950
1966
  }
1951
1967
  //#endregion
1952
1968
  //#region src/utils/agent-run-request.utils.ts
1953
- function readOptionalString$11(value) {
1969
+ function readOptionalString$12(value) {
1954
1970
  if (typeof value !== "string") return;
1955
1971
  return value.trim() || void 0;
1956
1972
  }
1957
1973
  function toAgentRunRequest(envelope) {
1958
1974
  const metadata = envelope.metadata ?? {};
1959
- const peerId = readOptionalString$11(envelope.peerId);
1975
+ const peerId = readOptionalString$12(envelope.peerId);
1960
1976
  const requestMetadata = {
1961
1977
  agentRuntimeId: metadata.agentRuntimeId,
1962
1978
  agentId: metadata.agentId,
@@ -1968,10 +1984,10 @@ function toAgentRunRequest(envelope) {
1968
1984
  maxTokens: metadata.maxTokens,
1969
1985
  thinkingEffort: metadata.thinkingEffort,
1970
1986
  delivery: envelope.delivery,
1971
- idempotencyKey: readOptionalString$11(envelope.idempotencyKey)
1987
+ idempotencyKey: readOptionalString$12(envelope.idempotencyKey)
1972
1988
  };
1973
1989
  if (Array.isArray(envelope.content)) {
1974
- const sessionId = readOptionalString$11(envelope.sessionId);
1990
+ const sessionId = readOptionalString$12(envelope.sessionId);
1975
1991
  if (sessionId && peerId) throw new Error("agent-run.send cannot accept both sessionId and peerId.");
1976
1992
  return {
1977
1993
  ...requestMetadata,
@@ -1989,8 +2005,8 @@ function toAgentRunRequest(envelope) {
1989
2005
  }
1990
2006
  const sourceMessage = envelope.message;
1991
2007
  if (!sourceMessage) throw new Error("Invalid agent run send request.");
1992
- const envelopeSessionId = readOptionalString$11(envelope.sessionId);
1993
- const messageSessionId = readOptionalString$11(sourceMessage.sessionId);
2008
+ const envelopeSessionId = readOptionalString$12(envelope.sessionId);
2009
+ const messageSessionId = readOptionalString$12(sourceMessage.sessionId);
1994
2010
  const sessionId = envelopeSessionId ?? messageSessionId;
1995
2011
  if (sessionId && peerId) throw new Error("agent-run.send cannot accept both sessionId and peerId.");
1996
2012
  return {
@@ -2051,7 +2067,7 @@ function readSessionMaterialization(metadata) {
2051
2067
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
2052
2068
  const materialization = value;
2053
2069
  if (materialization.kind !== "child") throw new Error("session_materialization.kind must be \"child\".");
2054
- const parentSessionId = readOptionalString$11(materialization.parentSessionId);
2070
+ const parentSessionId = readOptionalString$12(materialization.parentSessionId);
2055
2071
  if (!parentSessionId) throw new Error("session_materialization.parentSessionId is required.");
2056
2072
  if (materialization.inheritContext !== true) throw new Error("session_materialization.inheritContext must be true.");
2057
2073
  return {
@@ -2093,7 +2109,7 @@ function resolveRunSpec(params) {
2093
2109
  };
2094
2110
  }
2095
2111
  function attachRunSpecMetadata(params) {
2096
- const { message, modelSource, request, session, spec, startedAt } = params;
2112
+ const { message, modelSource, request, session, spec, startedAt, trigger } = params;
2097
2113
  const metadata = structuredClone(message.metadata ?? {});
2098
2114
  metadata[AGENT_RUN_MESSAGE_RUN_SPEC_METADATA_KEY] = {
2099
2115
  version: 1,
@@ -2112,6 +2128,7 @@ function attachRunSpecMetadata(params) {
2112
2128
  correlationId: spec.correlationId ?? null,
2113
2129
  execution: structuredClone(AGENT_RUN_EXECUTION_METADATA)
2114
2130
  };
2131
+ metadata[NCP_RUN_TRIGGER_METADATA_KEY] = structuredClone(trigger);
2115
2132
  return {
2116
2133
  ...message,
2117
2134
  metadata
@@ -2164,7 +2181,7 @@ var AgentRuntimeRunObserverService = class {
2164
2181
  }));
2165
2182
  messageCompletedSeen = true;
2166
2183
  }
2167
- eventsToPublish.push(event);
2184
+ eventsToPublish.push(this.projectCompletedMessage(event, sessionRun));
2168
2185
  eventsToPublish.forEach(this.publishNcpEvent);
2169
2186
  if (consumedSteeringInput) this.publishRunQueueUpdated(session.sessionId);
2170
2187
  }), catchError(async (error) => {
@@ -2235,6 +2252,17 @@ var AgentRuntimeRunObserverService = class {
2235
2252
  source: "agent-run-request"
2236
2253
  });
2237
2254
  };
2255
+ projectCompletedMessage = (event, sessionRun) => {
2256
+ if (event.type !== NcpEventType.MessageCompleted) return event;
2257
+ const message = sessionRun.getSnapshot().messages.find((item) => item.id === event.payload.message.id);
2258
+ return message ? {
2259
+ ...event,
2260
+ payload: {
2261
+ ...event.payload,
2262
+ message: structuredClone(message)
2263
+ }
2264
+ } : event;
2265
+ };
2238
2266
  publishNcpEvent = (event) => {
2239
2267
  this.options.eventBus.emit(eventKeys$1.ncpEvent, event, {
2240
2268
  emittedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -2243,6 +2271,125 @@ var AgentRuntimeRunObserverService = class {
2243
2271
  };
2244
2272
  };
2245
2273
  //#endregion
2274
+ //#region src/utils/agent-run-trigger.utils.ts
2275
+ function isRecord$17(value) {
2276
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2277
+ }
2278
+ function readOptionalString$11(value) {
2279
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
2280
+ }
2281
+ function readSourceRunSpec(message) {
2282
+ const value = message.metadata?.[AGENT_RUN_MESSAGE_RUN_SPEC_METADATA_KEY];
2283
+ return isRecord$17(value) ? value : {};
2284
+ }
2285
+ const SOURCE_CONTEXT_KEYS = [
2286
+ "channel",
2287
+ "chatId",
2288
+ "accountId",
2289
+ "senderId",
2290
+ "sessionKey",
2291
+ "cron_job_id",
2292
+ "cron_job_name",
2293
+ "session_origin",
2294
+ "observation_delivery_id",
2295
+ "observation_subscription_id"
2296
+ ];
2297
+ function readSourceContext(request) {
2298
+ const candidates = {
2299
+ ...request.metadata ?? {},
2300
+ ...request.message.metadata ?? {},
2301
+ ...request.peerId ? { peerId: request.peerId } : {}
2302
+ };
2303
+ const entries = [...SOURCE_CONTEXT_KEYS, "peerId"].flatMap((key) => {
2304
+ const value = candidates[key];
2305
+ return value === null || typeof value === "string" || typeof value === "number" && Number.isFinite(value) || typeof value === "boolean" ? [[key, value]] : [];
2306
+ });
2307
+ return entries.length > 0 ? Object.fromEntries(entries) : void 0;
2308
+ }
2309
+ function createAgentToolRunTriggerInput(params) {
2310
+ const { request, source } = params;
2311
+ const runSpec = readSourceRunSpec(request.message);
2312
+ const sourceSessionId = readOptionalString$11(request.sessionId);
2313
+ const sourceRunId = readOptionalString$11(runSpec.runId);
2314
+ const sourceModel = readOptionalString$11(runSpec.model);
2315
+ const sourceContext = readSourceContext(request);
2316
+ return {
2317
+ actor: "agent",
2318
+ source,
2319
+ triggeredAt: (/* @__PURE__ */ new Date()).toISOString(),
2320
+ ...sourceSessionId ? { sourceSessionId } : {},
2321
+ sourceMessageId: request.message.id,
2322
+ ...sourceRunId ? { sourceRunId } : {},
2323
+ ...sourceModel ? { sourceModel } : {},
2324
+ ...sourceContext ? { sourceContext } : {}
2325
+ };
2326
+ }
2327
+ function createIngressRunTriggerInput(params) {
2328
+ const { request } = params;
2329
+ const source = request.channel ? `channel:${request.channel}` : params.source.trim() || "internal";
2330
+ const sourceSessionId = readOptionalString$11(request.sessionId);
2331
+ const sourceContext = readSourceContext(request);
2332
+ const cronJobId = readOptionalString$11(request.metadata?.cron_job_id);
2333
+ const isCron = request.metadata?.session_origin === "cron" || Boolean(cronJobId);
2334
+ return {
2335
+ actor: isCron ? "automation" : request.message.role === "user" ? "human" : request.message.role === "service" ? "automation" : "system",
2336
+ source: isCron ? cronJobId ? `cron:${cronJobId}` : "cron" : source,
2337
+ triggeredAt: request.message.timestamp,
2338
+ ...sourceSessionId ? { sourceSessionId } : {},
2339
+ sourceMessageId: request.message.id,
2340
+ ...sourceContext ? { sourceContext } : {}
2341
+ };
2342
+ }
2343
+ function resolveRunTriggerMetadata(params) {
2344
+ const { request, spec, startedAt } = params;
2345
+ return resolveTargetRunTriggerMetadata({
2346
+ request,
2347
+ targetRunId: spec.runId,
2348
+ fallbackTriggeredAt: startedAt
2349
+ });
2350
+ }
2351
+ function resolveSteeringRunTriggerMetadata(params) {
2352
+ return resolveTargetRunTriggerMetadata({
2353
+ request: params.request,
2354
+ targetRunId: params.targetRunId,
2355
+ fallbackTriggeredAt: params.acceptedAt
2356
+ });
2357
+ }
2358
+ function resolveTargetRunTriggerMetadata(params) {
2359
+ const { request } = params;
2360
+ const input = request.trigger ?? createIngressRunTriggerInput({
2361
+ request,
2362
+ source: "internal"
2363
+ });
2364
+ const triggeredAt = Number.isFinite(Date.parse(input.triggeredAt)) ? new Date(input.triggeredAt).toISOString() : params.fallbackTriggeredAt;
2365
+ return {
2366
+ version: 1,
2367
+ ...structuredClone(input),
2368
+ triggeredAt,
2369
+ targetRunId: params.targetRunId
2370
+ };
2371
+ }
2372
+ function createRunTriggerMetadataEvent(params) {
2373
+ const { sessionId, spec, trigger } = params;
2374
+ return {
2375
+ occurredAt: trigger.triggeredAt,
2376
+ type: NcpEventType.RunMetadata,
2377
+ payload: {
2378
+ sessionId,
2379
+ runId: spec.runId,
2380
+ correlationId: spec.correlationId,
2381
+ metadata: { [NCP_RUN_TRIGGER_METADATA_KEY]: structuredClone(trigger) }
2382
+ }
2383
+ };
2384
+ }
2385
+ function attachSourceToolCall(trigger, sourceToolCallId) {
2386
+ const normalizedToolCallId = readOptionalString$11(sourceToolCallId);
2387
+ return {
2388
+ ...structuredClone(trigger),
2389
+ ...normalizedToolCallId ? { sourceToolCallId: normalizedToolCallId } : {}
2390
+ };
2391
+ }
2392
+ //#endregion
2246
2393
  //#region src/managers/agent-run-request.manager.ts
2247
2394
  var AgentRunRequestManager = class {
2248
2395
  cleanups = [];
@@ -2283,9 +2430,14 @@ var AgentRunRequestManager = class {
2283
2430
  this.sessionCommandManager.dispose();
2284
2431
  this.started = false;
2285
2432
  };
2286
- handleSendRequest = async (envelope) => {
2433
+ handleSendRequest = async (envelope, context) => {
2287
2434
  if (!envelope.payload) throw new Error("Invalid agent run send request.");
2288
- return toRunHandle(await this.send(toAgentRunRequest(envelope.payload)));
2435
+ const request = toAgentRunRequest(envelope.payload);
2436
+ request.trigger = createIngressRunTriggerInput({
2437
+ request,
2438
+ source: context.source
2439
+ });
2440
+ return toRunHandle(await this.send(request));
2289
2441
  };
2290
2442
  handleAbortRequest = async (envelope) => {
2291
2443
  if (!envelope.payload?.sessionId) throw new Error("Invalid agent run abort request.");
@@ -2312,7 +2464,8 @@ var AgentRunRequestManager = class {
2312
2464
  ...envelope.payload.message,
2313
2465
  sessionId: envelope.payload.sessionId
2314
2466
  },
2315
- correlationId: envelope.payload.requestId
2467
+ correlationId: envelope.payload.requestId,
2468
+ trigger: structuredClone(envelope.payload.trigger)
2316
2469
  }));
2317
2470
  };
2318
2471
  send = async (request) => await this.idempotency.accept(request, this.sendOnce);
@@ -2329,7 +2482,8 @@ var AgentRunRequestManager = class {
2329
2482
  sessionId: session.sessionId,
2330
2483
  message: baseMessage
2331
2484
  };
2332
- const steeringRequest = request.delivery === "prefer-steer" && sessionRun.isRunning() ? this.pendingInputs.enqueuePreferredSteeringInput(sessionRun, normalizedRequest, session) : null;
2485
+ const queuedRequest = sessionRun.enqueueRequest(normalizedRequest, session);
2486
+ const steeringRequest = request.delivery === "prefer-steer" && sessionRun.isRunning() ? this.pendingInputs.promotePreferredSteeringInput(sessionRun, queuedRequest.id, session) : null;
2333
2487
  if (steeringRequest) {
2334
2488
  this.publishRunQueueUpdated(session.sessionId);
2335
2489
  return {
@@ -2340,7 +2494,6 @@ var AgentRunRequestManager = class {
2340
2494
  delivery: "steered"
2341
2495
  };
2342
2496
  }
2343
- const queuedRequest = sessionRun.enqueueRequest(normalizedRequest, session);
2344
2497
  const activeRequest = sessionRun.beginNextRun();
2345
2498
  this.publishRunQueueUpdated(session.sessionId);
2346
2499
  if (activeRequest?.id === queuedRequest.id) await this.startQueuedRun(sessionRun, activeRequest);
@@ -2365,6 +2518,11 @@ var AgentRunRequestManager = class {
2365
2518
  runId: activeRequest.runId,
2366
2519
  session
2367
2520
  });
2521
+ const trigger = resolveRunTriggerMetadata({
2522
+ request,
2523
+ spec,
2524
+ startedAt: requestRunStartedAt
2525
+ });
2368
2526
  const message = attachRunSpecMetadata({
2369
2527
  message: {
2370
2528
  ...request.message,
@@ -2375,7 +2533,8 @@ var AgentRunRequestManager = class {
2375
2533
  request,
2376
2534
  session,
2377
2535
  spec,
2378
- startedAt: requestRunStartedAt
2536
+ startedAt: requestRunStartedAt,
2537
+ trigger
2379
2538
  });
2380
2539
  const providerRequest = {
2381
2540
  ...request,
@@ -2402,8 +2561,14 @@ var AgentRunRequestManager = class {
2402
2561
  message,
2403
2562
  correlationId: request.correlationId
2404
2563
  });
2405
- await sessionRun.applyEvents([messageSentEvent]);
2564
+ const triggerEvent = createRunTriggerMetadataEvent({
2565
+ sessionId: session.sessionId,
2566
+ spec,
2567
+ trigger
2568
+ });
2569
+ await sessionRun.applyEvents([messageSentEvent, triggerEvent]);
2406
2570
  this.publishNcpEvent(messageSentEvent);
2571
+ this.publishNcpEvent(triggerEvent);
2407
2572
  try {
2408
2573
  const runtime = this.agentRuntimeManager.getOrCreate({
2409
2574
  agentRuntimeId: session.agentRuntimeId,
@@ -9340,13 +9505,31 @@ var SessionPendingInputs = class {
9340
9505
  intendedRunId
9341
9506
  });
9342
9507
  };
9343
- moveNextRunToNextStep = (requestId, intendedRunId) => {
9508
+ moveNextRunToNextStep = (requestId, intendedRunId, activeRunSpec) => {
9344
9509
  const index = this.nextRun.findIndex(({ id }) => id === requestId);
9345
9510
  if (index < 0) return null;
9346
9511
  const [request] = this.nextRun.splice(index, 1);
9347
9512
  if (!request) return null;
9348
- this.enqueueNextStep(request, intendedRunId);
9349
- return this.toPendingRequest(request, "steering", intendedRunId);
9513
+ const steeringRequest = {
9514
+ ...request,
9515
+ request: {
9516
+ ...request.request,
9517
+ message: {
9518
+ ...request.request.message,
9519
+ metadata: {
9520
+ ...request.request.message.metadata ?? {},
9521
+ ...activeRunSpec ? { [AGENT_RUN_MESSAGE_RUN_SPEC_METADATA_KEY]: structuredClone(activeRunSpec) } : {},
9522
+ [NCP_RUN_TRIGGER_METADATA_KEY]: resolveSteeringRunTriggerMetadata({
9523
+ request: request.request,
9524
+ targetRunId: intendedRunId,
9525
+ acceptedAt: (/* @__PURE__ */ new Date()).toISOString()
9526
+ })
9527
+ }
9528
+ }
9529
+ }
9530
+ };
9531
+ this.enqueueNextStep(steeringRequest, intendedRunId);
9532
+ return this.toPendingRequest(steeringRequest, "steering", intendedRunId);
9350
9533
  };
9351
9534
  list = () => [...this.nextStep.map((request) => this.toPendingRequest(request, "steering", request.intendedRunId)), ...this.nextRun.map((request) => this.toPendingRequest(request, "queued", null))];
9352
9535
  listNextRun = () => structuredClone(this.nextRun);
@@ -9398,6 +9581,13 @@ var SessionPendingInputs = class {
9398
9581
  function isConversationStateEvent(event) {
9399
9582
  return event.type !== NcpEventType.ContextWindowUpdated;
9400
9583
  }
9584
+ function findRunSpec(messages, runId) {
9585
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
9586
+ const runSpec = messages[index]?.metadata?.[AGENT_RUN_MESSAGE_RUN_SPEC_METADATA_KEY];
9587
+ if (runSpec && typeof runSpec === "object" && !Array.isArray(runSpec) && runSpec.runId === runId) return runSpec;
9588
+ }
9589
+ return null;
9590
+ }
9401
9591
  var SessionRun = class {
9402
9592
  sessionId;
9403
9593
  statusListeners = /* @__PURE__ */ new Set();
@@ -9459,23 +9649,7 @@ var SessionRun = class {
9459
9649
  };
9460
9650
  moveQueuedRequestToNextStep = (queuedRequestId) => {
9461
9651
  if (!this.activeRunId) return null;
9462
- return this.pendingInputs.moveNextRunToNextStep(queuedRequestId, this.activeRunId);
9463
- };
9464
- enqueueNextStepRequest = (request, session) => {
9465
- if (!this.activeRunId) return null;
9466
- const pendingRequest = {
9467
- id: `pending-input-${randomUUID()}`,
9468
- runId: `agent-run-${randomUUID()}`,
9469
- enqueuedAt: (/* @__PURE__ */ new Date()).toISOString(),
9470
- request: structuredClone(request),
9471
- session: structuredClone(session)
9472
- };
9473
- this.pendingInputs.enqueueNextStep(pendingRequest, this.activeRunId);
9474
- return structuredClone({
9475
- ...pendingRequest,
9476
- placement: "steering",
9477
- intendedRunId: this.activeRunId
9478
- });
9652
+ return this.pendingInputs.moveNextRunToNextStep(queuedRequestId, this.activeRunId, findRunSpec(this.getSnapshot().messages, this.activeRunId));
9479
9653
  };
9480
9654
  claimNextStepRequests = (runId) => this.pendingInputs.claimNextStep(runId).map((request) => ({
9481
9655
  ...request,
@@ -10237,20 +10411,39 @@ function readMessageFromSummaryEvent(event) {
10237
10411
  function readRecordLabel(record) {
10238
10412
  return readOptionalString(record.metadata?.label) ?? void 0;
10239
10413
  }
10414
+ function resolveRequestTrigger(params) {
10415
+ return {
10416
+ ...params.trigger ?? {
10417
+ actor: "system",
10418
+ source: "session-request",
10419
+ triggeredAt: (/* @__PURE__ */ new Date()).toISOString(),
10420
+ sourceSessionId: params.sourceSessionId
10421
+ },
10422
+ sourceRequestId: params.requestId
10423
+ };
10424
+ }
10240
10425
  var SessionRequestManager = class {
10241
10426
  constructor(options) {
10242
10427
  this.options = options;
10243
10428
  }
10244
10429
  spawnSessionAndRequest = async (params) => {
10245
- const { sourceSessionId, sourceToolCallId, updateToolCallResult, sourceSessionMetadata, metadataOverrides, contextInheritance, task, title, model, runtime, handoffDepth, sessionType, thinkingLevel, projectRoot, agentId, parentSessionId, notify } = params;
10430
+ const { sourceSessionId, sourceToolCallId, updateToolCallResult, sourceSessionMetadata, metadataOverrides, contextInheritance, task, title, model, runtime, handoffDepth, sessionType, thinkingLevel, projectRoot, agentId, parentSessionId, notify, trigger: requestedTrigger } = params;
10246
10431
  const requestId = randomUUID();
10432
+ const trigger = resolveRequestTrigger({
10433
+ trigger: requestedTrigger,
10434
+ sourceSessionId,
10435
+ requestId
10436
+ });
10247
10437
  const createdSession = await this.options.sessionManager.createSession({
10248
10438
  sourceSessionId,
10249
10439
  ...parentSessionId ? { parentSessionId } : {},
10250
10440
  task,
10251
10441
  title,
10252
10442
  sourceSessionMetadata,
10253
- ...metadataOverrides ? { metadataOverrides } : {},
10443
+ metadataOverrides: {
10444
+ ...metadataOverrides ?? {},
10445
+ session_creation_trigger: structuredClone(trigger)
10446
+ },
10254
10447
  contextInheritance,
10255
10448
  agentId,
10256
10449
  model,
@@ -10273,18 +10466,25 @@ var SessionRequestManager = class {
10273
10466
  agentId: createdSession.agentId,
10274
10467
  isChildSession: Boolean(parentSessionId),
10275
10468
  ...parentSessionId ? { parentSessionId } : {},
10276
- spawnedByRequestId: requestId
10469
+ spawnedByRequestId: requestId,
10470
+ trigger
10277
10471
  });
10278
10472
  };
10279
10473
  requestSession = async (params) => {
10280
- const { sourceSessionId, sourceToolCallId, updateToolCallResult, targetSessionId, task, title, notify, handoffDepth } = params;
10474
+ const { sourceSessionId, sourceToolCallId, updateToolCallResult, targetSessionId, task, title, notify, handoffDepth, trigger: requestedTrigger } = params;
10281
10475
  const normalizedTargetSessionId = targetSessionId.trim();
10282
10476
  if (normalizedTargetSessionId === sourceSessionId.trim()) throw new Error("sessions_request cannot target the current session.");
10283
10477
  const targetSession = await this.options.sessionManager.getSessionRecord(normalizedTargetSessionId);
10284
10478
  if (!targetSession) throw new Error(`Target session not found: ${targetSessionId}`);
10285
10479
  const parentSessionId = readParentSessionId(targetSession.metadata);
10480
+ const requestId = randomUUID();
10481
+ const trigger = resolveRequestTrigger({
10482
+ trigger: requestedTrigger,
10483
+ sourceSessionId,
10484
+ requestId
10485
+ });
10286
10486
  return this.dispatchRequest({
10287
- requestId: randomUUID(),
10487
+ requestId,
10288
10488
  sourceSessionId,
10289
10489
  sourceToolCallId,
10290
10490
  updateToolCallResult,
@@ -10296,11 +10496,12 @@ var SessionRequestManager = class {
10296
10496
  agentId: targetSession.agentId,
10297
10497
  isChildSession: Boolean(parentSessionId),
10298
10498
  parentSessionId: parentSessionId ?? void 0,
10299
- spawnedByRequestId: void 0
10499
+ spawnedByRequestId: void 0,
10500
+ trigger
10300
10501
  });
10301
10502
  };
10302
10503
  dispatchRequest = async (params) => {
10303
- const { requestId, sourceSessionId, sourceToolCallId, updateToolCallResult, targetSessionId, task, title, handoffDepth, notify, agentId, isChildSession, parentSessionId, spawnedByRequestId } = params;
10504
+ const { requestId, sourceSessionId, sourceToolCallId, updateToolCallResult, targetSessionId, task, title, handoffDepth, notify, agentId, isChildSession, parentSessionId, spawnedByRequestId, trigger } = params;
10304
10505
  const request = createRunningSessionRequest({
10305
10506
  requestId,
10306
10507
  sourceSessionId,
@@ -10311,7 +10512,8 @@ var SessionRequestManager = class {
10311
10512
  title,
10312
10513
  task,
10313
10514
  isChildSession,
10314
- parentSessionId
10515
+ parentSessionId,
10516
+ trigger
10315
10517
  });
10316
10518
  const resultContext = {
10317
10519
  task,
@@ -10469,6 +10671,13 @@ function waitForAgentRuntimeSessionReply(input) {
10469
10671
  };
10470
10672
  }
10471
10673
  async function dispatchAgentRuntimeSessionRequest(input) {
10674
+ const trigger = parseNcpRunTriggerInput(input.request.metadata?.run_trigger) ?? {
10675
+ actor: "system",
10676
+ source: "session-request-recovery",
10677
+ triggeredAt: input.request.createdAt,
10678
+ sourceSessionId: input.request.sourceSessionId,
10679
+ sourceRequestId: input.request.requestId
10680
+ };
10472
10681
  await input.ingress.handle({
10473
10682
  type: ingressKeys$1.agentRun.sessionMessageRequest,
10474
10683
  payload: {
@@ -10485,7 +10694,8 @@ async function dispatchAgentRuntimeSessionRequest(input) {
10485
10694
  metadata: { session_request_id: input.request.requestId }
10486
10695
  },
10487
10696
  requestId: input.request.requestId,
10488
- sessionId: input.request.targetSessionId
10697
+ sessionId: input.request.targetSessionId,
10698
+ trigger: structuredClone(trigger)
10489
10699
  }
10490
10700
  }, { source: "session-request" });
10491
10701
  }
@@ -12826,6 +13036,7 @@ const createSelfManagementContextProvider = () => ({ provide: () => {
12826
13036
  } });
12827
13037
  const createSessionOrchestrationContextProvider = () => staticBlock([
12828
13038
  "## Session Orchestration",
13039
+ "- Only top-level sessions can create new sessions. Child sessions must complete their delegated task directly and return further delegation needs to the parent session.",
12829
13040
  "- Before passing a non-default `runtime` to `sessions_spawn` or agent creation/update flows, inspect the installed runtime kinds with `nextclaw agents runtimes --json`.",
12830
13041
  "- `sessions_spawn` is the unified session-creation tool. Omit `scope` or use `scope=\"standalone\"` for a regular session, and use `scope=\"child\"` when the new session should be a child session of the current flow.",
12831
13042
  "- `sessions_spawn` only creates the session by default. Add top-level `notify: \"none\" | \"final_reply\"` when the new session should start working immediately.",
@@ -12977,6 +13188,9 @@ function renderWebSearchReadiness(params) {
12977
13188
  if (!readSearchApiKey(searchConfig, provider)) return `web_search is not ready: provider ${provider} has no API key configured.`;
12978
13189
  return `web_search is ready with provider ${provider}.`;
12979
13190
  }
13191
+ function renderDelegationGuidance(toolNames) {
13192
+ return toolNames.includes("sessions_spawn") ? "If a task is more complex or takes longer, spawn a sub-agent. Completion is push-based: it will auto-announce when done." : "Sub-agent spawning is unavailable in this delegated session. Complete the assigned task directly and return any further delegation needs to the parent session.";
13193
+ }
12980
13194
  var ToolingContextProvider = class {
12981
13195
  constructor(context) {
12982
13196
  this.context = context;
@@ -12998,7 +13212,7 @@ var ToolingContextProvider = class {
12998
13212
  "TOOLS.md does not control tool availability; it is user guidance for how to use external tools.",
12999
13213
  "For long waits, avoid rapid poll loops: use exec with enough yieldMs.",
13000
13214
  "For relative time/date scheduling requests (for example 'in 5 minutes' / '1分钟后'), first check the current local time with an available tool such as exec/date, then convert it to an absolute ISO time with timezone. Do not guess.",
13001
- "If a task is more complex or takes longer, spawn a sub-agent. Completion is push-based: it will auto-announce when done.",
13215
+ renderDelegationGuidance(toolCatalog.map((tool) => tool.name)),
13002
13216
  "Do not poll `subagents list` / `sessions_list` in a loop; only check status on-demand (for intervention, debugging, or when explicitly asked)."
13003
13217
  ].join("\n")];
13004
13218
  };
@@ -13692,6 +13906,7 @@ var LearningLoopContribution = class extends Contribution$1 {
13692
13906
  if (toolCallsSinceReview < runtimeConfig.toolCallThreshold) return;
13693
13907
  this.inFlightSessionIds.add(sessionId);
13694
13908
  try {
13909
+ const triggeredAt = (/* @__PURE__ */ new Date()).toISOString();
13695
13910
  const reviewSession = await this.sessionRequester.spawnSessionAndRequest({
13696
13911
  sourceSessionId: sessionId,
13697
13912
  sourceSessionMetadata: metadata,
@@ -13707,11 +13922,18 @@ var LearningLoopContribution = class extends Contribution$1 {
13707
13922
  sessionId,
13708
13923
  toolCallsSinceReview,
13709
13924
  currentToolCallCount: totalToolCalls
13710
- })
13925
+ }),
13926
+ trigger: {
13927
+ actor: "automation",
13928
+ source: "learning-loop",
13929
+ triggeredAt,
13930
+ sourceSessionId: sessionId,
13931
+ ...typeof metadata.preferred_model === "string" && metadata.preferred_model.trim() ? { sourceModel: metadata.preferred_model.trim() } : {}
13932
+ }
13711
13933
  });
13712
13934
  await this.sessionStore.updateSessionMetadata(sessionId, {
13713
13935
  [LEARNING_LOOP_LAST_TOOL_CALL_COUNT_METADATA_KEY]: totalToolCalls,
13714
- [LEARNING_LOOP_LAST_REQUESTED_AT_METADATA_KEY]: (/* @__PURE__ */ new Date()).toISOString(),
13936
+ [LEARNING_LOOP_LAST_REQUESTED_AT_METADATA_KEY]: triggeredAt,
13715
13937
  [LEARNING_LOOP_LAST_REVIEW_SESSION_ID_METADATA_KEY]: reviewSession.sessionId
13716
13938
  });
13717
13939
  } finally {
@@ -14076,12 +14298,14 @@ var SessionRequestTool = class {
14076
14298
  };
14077
14299
  sourceSessionId = "";
14078
14300
  handoffDepth = 0;
14301
+ trigger = null;
14079
14302
  constructor(manager) {
14080
14303
  this.manager = manager;
14081
14304
  }
14082
14305
  setContext = (params) => {
14083
14306
  this.sourceSessionId = params.sourceSessionId;
14084
14307
  this.handoffDepth = params.handoffDepth ?? 0;
14308
+ this.trigger = structuredClone(params.trigger);
14085
14309
  };
14086
14310
  execute = async (args, context) => {
14087
14311
  const params = normalizeToolParams(args);
@@ -14098,9 +14322,14 @@ var SessionRequestTool = class {
14098
14322
  task,
14099
14323
  title: readOptionalString$6(params, "title"),
14100
14324
  notify: notifyMode,
14101
- handoffDepth: this.handoffDepth
14325
+ handoffDepth: this.handoffDepth,
14326
+ trigger: attachSourceToolCall(this.readTriggerOrThrow(), context?.toolCallId)
14102
14327
  });
14103
14328
  };
14329
+ readTriggerOrThrow = () => {
14330
+ if (!this.trigger) throw new Error("sessions_request requires an active run trigger context.");
14331
+ return structuredClone(this.trigger);
14332
+ };
14104
14333
  };
14105
14334
  //#endregion
14106
14335
  //#region src/tools/session-search.tools.ts
@@ -14240,16 +14469,20 @@ var SessionSpawnTool = class {
14240
14469
  sourceSessionId = "";
14241
14470
  sourceSessionMetadata = {};
14242
14471
  handoffDepth = 0;
14472
+ trigger = null;
14243
14473
  constructor(sessionManager, sessionRequestManager) {
14244
14474
  this.sessionManager = sessionManager;
14245
14475
  this.sessionRequestManager = sessionRequestManager;
14246
14476
  }
14247
14477
  setContext = (params) => {
14248
- this.sourceSessionId = params.sourceSessionId;
14249
- this.sourceSessionMetadata = structuredClone(params.sourceSessionMetadata);
14250
- this.handoffDepth = params.handoffDepth ?? 0;
14478
+ const { handoffDepth, sourceSessionId, sourceSessionMetadata, trigger } = params;
14479
+ this.sourceSessionId = sourceSessionId;
14480
+ this.sourceSessionMetadata = structuredClone(sourceSessionMetadata);
14481
+ this.handoffDepth = handoffDepth ?? 0;
14482
+ this.trigger = structuredClone(trigger);
14251
14483
  };
14252
14484
  execute = async (args, context) => {
14485
+ const { toolCallId, updateToolCallResult } = context ?? {};
14253
14486
  const { agentId: rawAgentId, model: rawModel, notify: rawNotify, runtime: rawRuntime, scope: rawScope, task: rawTask, title: rawTitle, inheritContext: rawInheritContext } = normalizeToolParams(args);
14254
14487
  const task = readRequiredString$4(rawTask, "task");
14255
14488
  const scope = readSpawnScope(rawScope);
@@ -14257,11 +14490,12 @@ var SessionSpawnTool = class {
14257
14490
  const inheritContext = readInheritContext(rawInheritContext);
14258
14491
  if (inheritContext && scope !== "child") throw new Error("inheritContext=true requires scope=\"child\".");
14259
14492
  const parentSessionId = scope === "child" ? this.readParentSessionIdOrThrow() : void 0;
14260
- const contextInheritance = inheritContext ? { anchorToolCallId: context?.toolCallId } : void 0;
14493
+ const contextInheritance = inheritContext ? { anchorToolCallId: toolCallId } : void 0;
14494
+ const trigger = attachSourceToolCall(this.readTriggerOrThrow(), toolCallId);
14261
14495
  if (notify) return this.sessionRequestManager.spawnSessionAndRequest({
14262
14496
  sourceSessionId: this.sourceSessionId,
14263
- sourceToolCallId: context?.toolCallId,
14264
- updateToolCallResult: context?.updateToolCallResult,
14497
+ sourceToolCallId: toolCallId,
14498
+ updateToolCallResult,
14265
14499
  sourceSessionMetadata: this.sourceSessionMetadata,
14266
14500
  task,
14267
14501
  title: readOptionalString$5(rawTitle),
@@ -14271,7 +14505,8 @@ var SessionSpawnTool = class {
14271
14505
  contextInheritance,
14272
14506
  handoffDepth: this.handoffDepth,
14273
14507
  parentSessionId,
14274
- notify
14508
+ notify,
14509
+ trigger
14275
14510
  });
14276
14511
  const session = await this.sessionManager.createSession({
14277
14512
  sourceSessionId: this.sourceSessionId,
@@ -14282,7 +14517,8 @@ var SessionSpawnTool = class {
14282
14517
  model: readOptionalString$5(rawModel),
14283
14518
  runtime: readOptionalString$5(rawRuntime),
14284
14519
  contextInheritance,
14285
- parentSessionId
14520
+ parentSessionId,
14521
+ metadataOverrides: { session_creation_trigger: structuredClone(trigger) }
14286
14522
  });
14287
14523
  return {
14288
14524
  kind: "nextclaw.session",
@@ -14301,6 +14537,10 @@ var SessionSpawnTool = class {
14301
14537
  if (!sourceSessionId) throw new Error("scope=\"child\" requires an active source session.");
14302
14538
  return sourceSessionId;
14303
14539
  };
14540
+ readTriggerOrThrow = () => {
14541
+ if (!this.trigger) throw new Error("sessions_spawn requires an active run trigger context.");
14542
+ return structuredClone(this.trigger);
14543
+ };
14304
14544
  };
14305
14545
  //#endregion
14306
14546
  //#region src/tools/session-update.tools.ts
@@ -14355,26 +14595,38 @@ var SessionToolProvider = class {
14355
14595
  this.sessionSearch = sessionSearch;
14356
14596
  }
14357
14597
  provide = async (request) => {
14358
- const { toolRunContext } = await this.runContextService.resolve(request);
14598
+ const { session, toolRunContext } = await this.runContextService.resolve(request);
14359
14599
  const { handoffDepth, metadata, sessionId } = toolRunContext;
14360
- const sessionsSpawnTool = new SessionSpawnTool(this.sessionManager, this.sessionRequests);
14361
- sessionsSpawnTool.setContext({
14362
- sourceSessionId: sessionId,
14363
- sourceSessionMetadata: metadata,
14364
- handoffDepth
14600
+ const isChildSession = Boolean(readParentSessionId(session?.metadata ?? metadata));
14601
+ const requestTrigger = createAgentToolRunTriggerInput({
14602
+ request,
14603
+ source: "sessions_request"
14365
14604
  });
14366
14605
  const sessionsRequestTool = new SessionRequestTool(this.sessionRequests);
14367
14606
  sessionsRequestTool.setContext({
14368
14607
  sourceSessionId: sessionId,
14369
- handoffDepth
14608
+ handoffDepth,
14609
+ trigger: requestTrigger
14370
14610
  });
14371
14611
  const tools = [
14372
- sessionsSpawnTool,
14373
14612
  sessionsRequestTool,
14374
14613
  new SessionsListTool(this.sessionManager),
14375
14614
  new SessionsHistoryTool(this.sessionManager),
14376
14615
  new SessionsUpdateTool(this.sessionManager)
14377
14616
  ];
14617
+ if (!isChildSession) {
14618
+ const sessionsSpawnTool = new SessionSpawnTool(this.sessionManager, this.sessionRequests);
14619
+ sessionsSpawnTool.setContext({
14620
+ sourceSessionId: sessionId,
14621
+ sourceSessionMetadata: metadata,
14622
+ handoffDepth,
14623
+ trigger: createAgentToolRunTriggerInput({
14624
+ request,
14625
+ source: "sessions_spawn"
14626
+ })
14627
+ });
14628
+ tools.unshift(sessionsSpawnTool);
14629
+ }
14378
14630
  if (!this.sessionSearch.isReady()) return tools;
14379
14631
  tools.push(new SessionSearchTool({ search: this.sessionSearch.search }, { currentSessionId: sessionId }));
14380
14632
  return tools;
@@ -15242,6 +15494,14 @@ const SESSION_METADATA_LABEL_KEY = "label";
15242
15494
  const CHILD_SESSION_PARENT_METADATA_KEY = "parent_session_id";
15243
15495
  const CHILD_SESSION_REQUEST_METADATA_KEY = "spawned_by_request_id";
15244
15496
  const CHILD_SESSION_LIFECYCLE_METADATA_KEY = "session_lifecycle";
15497
+ async function assertCanCreateSessionFromLineage(parentSessionId, sourceRecord, metadataOverrides, getSessionRecord) {
15498
+ if (Object.prototype.hasOwnProperty.call(metadataOverrides ?? {}, CHILD_SESSION_PARENT_METADATA_KEY)) throw new Error("Session parent must be set through parentSessionId.");
15499
+ if (readOptionalMetadataString(sourceRecord?.metadata?.[CHILD_SESSION_PARENT_METADATA_KEY])) throw new Error(`Child sessions cannot create additional sessions. Source session: ${sourceRecord?.sessionId}.`);
15500
+ if (!parentSessionId) return;
15501
+ const parentRecord = sourceRecord?.sessionId === parentSessionId ? sourceRecord : await getSessionRecord(parentSessionId);
15502
+ if (!parentRecord) throw new Error(`Parent session not found: ${parentSessionId}`);
15503
+ if (readOptionalMetadataString(parentRecord.metadata?.[CHILD_SESSION_PARENT_METADATA_KEY])) throw new Error(`Child sessions cannot create additional sessions. Parent session: ${parentSessionId}.`);
15504
+ }
15245
15505
  function readThinkingEffort(metadata) {
15246
15506
  return readOptionalMetadataString(metadata?.thinkingEffort) ?? readOptionalMetadataString(metadata?.preferred_thinking) ?? readOptionalMetadataString(metadata?.thinking) ?? null;
15247
15507
  }
@@ -17294,6 +17554,7 @@ var SessionManager = class {
17294
17554
  const metadata = cloneInheritedMetadata(sourceSessionMetadata);
17295
17555
  const title = readOptionalString$2(requestedTitle) ?? summarizeTask(task);
17296
17556
  const parentSessionId = readOptionalString$2(rawParentSessionId);
17557
+ await assertCanCreateSessionFromLineage(parentSessionId, sourceRecord, metadataOverrides, this.getSessionRecord);
17297
17558
  const requestId = readOptionalString$2(rawRequestId);
17298
17559
  const sessionType = resolveSessionType({
17299
17560
  runtime,
@@ -17453,23 +17714,9 @@ var SessionManager = class {
17453
17714
  return (liveRecord ? await this.summaryProjection.createWithContextWindow(liveRecord) : await this.getSession(sessionId))?.contextWindow ?? null;
17454
17715
  };
17455
17716
  getAgentRunSession = async (sessionId) => {
17456
- const record = await this.getSessionRecord(sessionId);
17457
- if (!record) throw new Error(`Session not found: ${sessionId}`);
17458
- const agentRuntimeId = readOptionalMetadataString(record.metadata?.agentRuntimeId) ?? "native";
17459
- const model = readOptionalMetadataString(record.metadata?.model);
17460
- return {
17461
- sessionId: record.sessionId,
17462
- agentId: record.agentId,
17463
- agentRuntimeId,
17464
- metadata: structuredClone(record.metadata ?? {}),
17465
- model,
17466
- projectRoot: readProjectRoot(record.metadata),
17467
- workingDir: this.workingDirResolver.resolve({
17468
- agentId: record.agentId,
17469
- metadata: record.metadata
17470
- }),
17471
- thinkingEffort: readThinkingEffort(record.metadata)
17472
- };
17717
+ const summary = await this.options.journalStore.getSessionSummary(sessionId);
17718
+ if (!summary) throw new Error(`Session not found: ${sessionId}`);
17719
+ return this.toAgentRunSession(summary);
17473
17720
  };
17474
17721
  createAgentRunSession = async (params) => {
17475
17722
  const { agentId, agentRuntimeId: requestedAgentRuntimeId, channel, contextInheritance, metadata, model, parentSessionId: rawParentSessionId, peerId: rawPeerId, projectRoot, sessionId, sourceSessionId: rawSourceSessionId, sourceSessionMetadata: requestedSourceSessionMetadata, task, thinkingEffort } = params;
@@ -17520,7 +17767,24 @@ var SessionManager = class {
17520
17767
  };
17521
17768
  getOrCreateAgentRunSession = async (params) => {
17522
17769
  if (!params.sessionId) return await this.createAgentRunSession(params);
17523
- return await this.getSessionRecord(params.sessionId) ? await this.getAgentRunSession(params.sessionId) : await this.createAgentRunSession(params);
17770
+ const summary = await this.options.journalStore.getSessionSummary(params.sessionId);
17771
+ return summary ? this.toAgentRunSession(summary) : await this.createAgentRunSession(params);
17772
+ };
17773
+ toAgentRunSession = (summary) => {
17774
+ const metadata = summary.metadata ?? {};
17775
+ return {
17776
+ sessionId: summary.sessionId,
17777
+ agentId: summary.agentId,
17778
+ agentRuntimeId: readOptionalMetadataString(metadata.agentRuntimeId) ?? "native",
17779
+ metadata: structuredClone(metadata),
17780
+ model: readOptionalMetadataString(metadata.model) ?? readOptionalMetadataString(metadata.preferred_model),
17781
+ projectRoot: readProjectRoot(metadata),
17782
+ workingDir: this.workingDirResolver.resolve({
17783
+ agentId: summary.agentId,
17784
+ metadata
17785
+ }),
17786
+ thinkingEffort: readThinkingEffort(metadata)
17787
+ };
17524
17788
  };
17525
17789
  patchSessionMetadata = async (sessionId, patch) => {
17526
17790
  if (!await this.updateSessionMetadata(sessionId, patch)) throw new Error(`Session metadata was not updated: ${sessionId}`);