@nextclaw/server 0.5.30 → 0.6.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.d.ts CHANGED
@@ -55,6 +55,8 @@ type AgentProfileView = {
55
55
  default?: boolean;
56
56
  workspace?: string;
57
57
  model?: string;
58
+ engine?: string;
59
+ engineConfig?: Record<string, unknown>;
58
60
  contextTokens?: number;
59
61
  maxToolIterations?: number;
60
62
  };
@@ -171,6 +173,8 @@ type RuntimeConfigUpdate = {
171
173
  agents?: {
172
174
  defaults?: {
173
175
  contextTokens?: number;
176
+ engine?: string;
177
+ engineConfig?: Record<string, unknown>;
174
178
  };
175
179
  list?: AgentProfileView[];
176
180
  };
@@ -228,6 +232,7 @@ type ChatTurnRequest = {
228
232
  chatId?: string;
229
233
  model?: string;
230
234
  metadata?: Record<string, unknown>;
235
+ runId?: string;
231
236
  };
232
237
  type ChatTurnResult = {
233
238
  reply: string;
@@ -258,15 +263,67 @@ type ChatTurnView = {
258
263
  completedAt: string;
259
264
  durationMs: number;
260
265
  };
266
+ type ChatCapabilitiesView = {
267
+ stopSupported: boolean;
268
+ stopReason?: string;
269
+ };
270
+ type ChatTurnStopRequest = {
271
+ runId: string;
272
+ sessionKey?: string;
273
+ agentId?: string;
274
+ };
275
+ type ChatTurnStopResult = {
276
+ stopped: boolean;
277
+ runId: string;
278
+ sessionKey?: string;
279
+ reason?: string;
280
+ };
281
+ type ChatRunState = "queued" | "running" | "completed" | "failed" | "aborted";
282
+ type ChatRunView = {
283
+ runId: string;
284
+ sessionKey: string;
285
+ agentId?: string;
286
+ model?: string;
287
+ state: ChatRunState;
288
+ requestedAt: string;
289
+ startedAt?: string;
290
+ completedAt?: string;
291
+ stopSupported: boolean;
292
+ stopReason?: string;
293
+ error?: string;
294
+ reply?: string;
295
+ eventCount: number;
296
+ };
297
+ type ChatRunListView = {
298
+ runs: ChatRunView[];
299
+ total: number;
300
+ };
261
301
  type UiChatRuntime = {
262
302
  processTurn: (params: ChatTurnRequest) => Promise<ChatTurnResult>;
263
303
  processTurnStream?: (params: ChatTurnRequest) => AsyncGenerator<ChatTurnStreamEvent>;
304
+ startTurnRun?: (params: ChatTurnRequest) => Promise<ChatRunView> | ChatRunView;
305
+ streamRun?: (params: {
306
+ runId: string;
307
+ fromEventIndex?: number;
308
+ }) => AsyncGenerator<ChatTurnStreamEvent>;
309
+ getRun?: (params: {
310
+ runId: string;
311
+ }) => Promise<ChatRunView | null> | ChatRunView | null;
312
+ listRuns?: (params: {
313
+ sessionKey?: string;
314
+ states?: ChatRunState[];
315
+ limit?: number;
316
+ }) => Promise<ChatRunListView> | ChatRunListView;
317
+ getCapabilities?: (params: Pick<ChatTurnRequest, "sessionKey" | "agentId">) => Promise<ChatCapabilitiesView> | ChatCapabilitiesView;
318
+ stopTurn?: (params: ChatTurnStopRequest) => Promise<ChatTurnStopResult> | ChatTurnStopResult;
264
319
  };
265
320
  type ConfigView = {
266
321
  agents: {
267
322
  defaults: {
268
323
  model: string;
269
324
  workspace?: string;
325
+ engine?: string;
326
+ engineConfig?: Record<string, unknown>;
270
327
  contextTokens?: number;
271
328
  maxToolIterations?: number;
272
329
  };
@@ -461,6 +518,8 @@ type MarketplaceInstalledRecord = {
461
518
  id?: string;
462
519
  spec: string;
463
520
  label?: string;
521
+ description?: string;
522
+ descriptionZh?: string;
464
523
  source?: string;
465
524
  installedAt?: string;
466
525
  enabled?: boolean;
@@ -572,6 +631,11 @@ type UiServerEvent = {
572
631
  payload: {
573
632
  path: string;
574
633
  };
634
+ } | {
635
+ type: "run.updated";
636
+ payload: {
637
+ run: ChatRunView;
638
+ };
575
639
  } | {
576
640
  type: "config.reload.started";
577
641
  payload?: Record<string, unknown>;
@@ -649,4 +713,4 @@ declare function deleteSession(configPath: string, key: string): boolean;
649
713
  declare function updateRuntime(configPath: string, patch: RuntimeConfigUpdate): Pick<ConfigView, "agents" | "bindings" | "session">;
650
714
  declare function updateSecrets(configPath: string, patch: SecretsConfigUpdate): SecretsView;
651
715
 
652
- export { type AgentBindingView, type AgentProfileView, type ApiError, type ApiResponse, type BindingPeerView, type ChannelSpecView, type ChatTurnRequest, type ChatTurnResult, type ChatTurnStreamEvent, type ChatTurnView, type ConfigActionExecuteRequest, type ConfigActionExecuteResult, type ConfigActionManifest, type ConfigActionType, type ConfigMetaView, type ConfigSchemaResponse, type ConfigUiHint, type ConfigUiHints, type ConfigView, type CronActionResult, type CronEnableRequest, type CronJobStateView, type CronJobView, type CronListView, type CronPayloadView, type CronRunRequest, type CronScheduleView, type MarketplaceApiConfig, type MarketplaceInstallKind, type MarketplaceInstallSkillParams, type MarketplaceInstallSpec, type MarketplaceInstalledRecord, type MarketplaceInstalledView, type MarketplaceInstaller, type MarketplaceItemSummary, type MarketplaceItemType, type MarketplaceItemView, type MarketplaceListView, type MarketplaceLocalizedTextMap, type MarketplacePluginContentView, type MarketplacePluginInstallRequest, type MarketplacePluginInstallResult, type MarketplacePluginManageAction, type MarketplacePluginManageRequest, type MarketplacePluginManageResult, type MarketplaceRecommendationView, type MarketplaceSkillContentView, type MarketplaceSkillInstallRequest, type MarketplaceSkillInstallResult, type MarketplaceSkillManageAction, type MarketplaceSkillManageRequest, type MarketplaceSkillManageResult, type MarketplaceSort, type ProviderConfigUpdate, type ProviderConfigView, type ProviderConnectionTestRequest, type ProviderConnectionTestResult, type ProviderCreateRequest, type ProviderCreateResult, type ProviderDeleteResult, type ProviderSpecView, type RuntimeConfigUpdate, type SecretProviderEnvView, type SecretProviderExecView, type SecretProviderFileView, type SecretProviderView, type SecretRefView, type SecretSourceView, type SecretsConfigUpdate, type SecretsView, type SessionConfigView, type SessionEntryView, type SessionEventView, type SessionHistoryView, type SessionMessageView, type SessionPatchUpdate, type SessionsListView, type UiChatRuntime, type UiServerEvent, type UiServerHandle, type UiServerOptions, buildConfigMeta, buildConfigSchemaView, buildConfigView, createCustomProvider, createUiRouter, deleteCustomProvider, deleteSession, executeConfigAction, getSessionHistory, listSessions, loadConfigOrDefault, patchSession, startUiServer, testProviderConnection, updateChannel, updateModel, updateProvider, updateRuntime, updateSecrets };
716
+ export { type AgentBindingView, type AgentProfileView, type ApiError, type ApiResponse, type BindingPeerView, type ChannelSpecView, type ChatCapabilitiesView, type ChatRunListView, type ChatRunState, type ChatRunView, type ChatTurnRequest, type ChatTurnResult, type ChatTurnStopRequest, type ChatTurnStopResult, type ChatTurnStreamEvent, type ChatTurnView, type ConfigActionExecuteRequest, type ConfigActionExecuteResult, type ConfigActionManifest, type ConfigActionType, type ConfigMetaView, type ConfigSchemaResponse, type ConfigUiHint, type ConfigUiHints, type ConfigView, type CronActionResult, type CronEnableRequest, type CronJobStateView, type CronJobView, type CronListView, type CronPayloadView, type CronRunRequest, type CronScheduleView, type MarketplaceApiConfig, type MarketplaceInstallKind, type MarketplaceInstallSkillParams, type MarketplaceInstallSpec, type MarketplaceInstalledRecord, type MarketplaceInstalledView, type MarketplaceInstaller, type MarketplaceItemSummary, type MarketplaceItemType, type MarketplaceItemView, type MarketplaceListView, type MarketplaceLocalizedTextMap, type MarketplacePluginContentView, type MarketplacePluginInstallRequest, type MarketplacePluginInstallResult, type MarketplacePluginManageAction, type MarketplacePluginManageRequest, type MarketplacePluginManageResult, type MarketplaceRecommendationView, type MarketplaceSkillContentView, type MarketplaceSkillInstallRequest, type MarketplaceSkillInstallResult, type MarketplaceSkillManageAction, type MarketplaceSkillManageRequest, type MarketplaceSkillManageResult, type MarketplaceSort, type ProviderConfigUpdate, type ProviderConfigView, type ProviderConnectionTestRequest, type ProviderConnectionTestResult, type ProviderCreateRequest, type ProviderCreateResult, type ProviderDeleteResult, type ProviderSpecView, type RuntimeConfigUpdate, type SecretProviderEnvView, type SecretProviderExecView, type SecretProviderFileView, type SecretProviderView, type SecretRefView, type SecretSourceView, type SecretsConfigUpdate, type SecretsView, type SessionConfigView, type SessionEntryView, type SessionEventView, type SessionHistoryView, type SessionMessageView, type SessionPatchUpdate, type SessionsListView, type UiChatRuntime, type UiServerEvent, type UiServerHandle, type UiServerOptions, buildConfigMeta, buildConfigSchemaView, buildConfigView, createCustomProvider, createUiRouter, deleteCustomProvider, deleteSession, executeConfigAction, getSessionHistory, listSessions, loadConfigOrDefault, patchSession, startUiServer, testProviderConnection, updateChannel, updateModel, updateProvider, updateRuntime, updateSecrets };
package/dist/index.js CHANGED
@@ -34,6 +34,7 @@ import {
34
34
  var MASK_MIN_LENGTH = 8;
35
35
  var EXTRA_SENSITIVE_PATH_PATTERNS = [/authorization/i, /cookie/i, /session/i, /bearer/i];
36
36
  var PROVIDER_TEST_MODEL_FALLBACKS = {
37
+ nextclaw: "dashscope/qwen3.5-flash",
37
38
  openai: "gpt-5-mini",
38
39
  deepseek: "deepseek-chat",
39
40
  gemini: "gemini-3-flash-preview",
@@ -47,6 +48,7 @@ var PROVIDER_TEST_MODEL_FALLBACKS = {
47
48
  anthropic: "claude-opus-4-6"
48
49
  };
49
50
  var PREFERRED_PROVIDER_ORDER = [
51
+ "nextclaw",
50
52
  "openai",
51
53
  "anthropic",
52
54
  "gemini",
@@ -914,17 +916,33 @@ function deleteSession(configPath, key) {
914
916
  }
915
917
  function updateRuntime(configPath, patch) {
916
918
  const config = loadConfigOrDefault(configPath);
917
- if (patch.agents?.defaults && Object.prototype.hasOwnProperty.call(patch.agents.defaults, "contextTokens")) {
918
- const nextContextTokens = patch.agents.defaults.contextTokens;
919
+ const defaultsPatch = patch.agents?.defaults;
920
+ if (defaultsPatch && Object.prototype.hasOwnProperty.call(defaultsPatch, "contextTokens")) {
921
+ const nextContextTokens = defaultsPatch.contextTokens;
919
922
  if (typeof nextContextTokens === "number" && Number.isFinite(nextContextTokens)) {
920
923
  config.agents.defaults.contextTokens = Math.max(1e3, Math.trunc(nextContextTokens));
921
924
  }
922
925
  }
926
+ if (defaultsPatch && Object.prototype.hasOwnProperty.call(defaultsPatch, "engine")) {
927
+ config.agents.defaults.engine = normalizeOptionalString(defaultsPatch.engine) ?? "native";
928
+ }
929
+ if (defaultsPatch && Object.prototype.hasOwnProperty.call(defaultsPatch, "engineConfig")) {
930
+ const nextEngineConfig = defaultsPatch.engineConfig;
931
+ if (nextEngineConfig && typeof nextEngineConfig === "object" && !Array.isArray(nextEngineConfig)) {
932
+ config.agents.defaults.engineConfig = { ...nextEngineConfig };
933
+ }
934
+ }
923
935
  if (patch.agents && Object.prototype.hasOwnProperty.call(patch.agents, "list")) {
924
- config.agents.list = (patch.agents.list ?? []).map((entry) => ({
925
- ...entry,
926
- default: Boolean(entry.default)
927
- }));
936
+ config.agents.list = (patch.agents.list ?? []).map((entry) => {
937
+ const normalizedEngine = normalizeOptionalString(entry.engine);
938
+ const hasEngineConfig = entry.engineConfig && typeof entry.engineConfig === "object" && !Array.isArray(entry.engineConfig);
939
+ return {
940
+ ...entry,
941
+ default: Boolean(entry.default),
942
+ ...normalizedEngine ? { engine: normalizedEngine } : {},
943
+ ...hasEngineConfig ? { engineConfig: { ...entry.engineConfig } } : {}
944
+ };
945
+ });
928
946
  }
929
947
  if (Object.prototype.hasOwnProperty.call(patch, "bindings")) {
930
948
  config.bindings = patch.bindings ?? [];
@@ -1172,6 +1190,24 @@ function resolveAgentIdFromSessionKey(sessionKey) {
1172
1190
  const agentId = readNonEmptyString(parsed?.agentId);
1173
1191
  return agentId;
1174
1192
  }
1193
+ function createChatRunId() {
1194
+ const now = Date.now().toString(36);
1195
+ const rand = Math.random().toString(36).slice(2, 10);
1196
+ return `run-${now}-${rand}`;
1197
+ }
1198
+ function isChatRunState(value) {
1199
+ return value === "queued" || value === "running" || value === "completed" || value === "failed" || value === "aborted";
1200
+ }
1201
+ function readChatRunStates(value) {
1202
+ if (typeof value !== "string") {
1203
+ return void 0;
1204
+ }
1205
+ const values = value.split(",").map((item) => item.trim().toLowerCase()).filter((item) => Boolean(item) && isChatRunState(item));
1206
+ if (values.length === 0) {
1207
+ return void 0;
1208
+ }
1209
+ return Array.from(new Set(values));
1210
+ }
1175
1211
  function buildChatTurnView(params) {
1176
1212
  const completedAt = /* @__PURE__ */ new Date();
1177
1213
  return {
@@ -1184,6 +1220,21 @@ function buildChatTurnView(params) {
1184
1220
  durationMs: Math.max(0, completedAt.getTime() - params.startedAtMs)
1185
1221
  };
1186
1222
  }
1223
+ function buildChatTurnViewFromRun(params) {
1224
+ const requestedAt = readNonEmptyString(params.run.requestedAt) ?? (/* @__PURE__ */ new Date()).toISOString();
1225
+ const completedAt = readNonEmptyString(params.run.completedAt) ?? (/* @__PURE__ */ new Date()).toISOString();
1226
+ const requestedAtMs = Date.parse(requestedAt);
1227
+ const completedAtMs = Date.parse(completedAt);
1228
+ return {
1229
+ reply: readNonEmptyString(params.run.reply) ?? params.fallbackReply ?? "",
1230
+ sessionKey: readNonEmptyString(params.run.sessionKey) ?? params.fallbackSessionKey,
1231
+ ...readNonEmptyString(params.run.agentId) || params.fallbackAgentId ? { agentId: readNonEmptyString(params.run.agentId) ?? params.fallbackAgentId } : {},
1232
+ ...readNonEmptyString(params.run.model) || params.fallbackModel ? { model: readNonEmptyString(params.run.model) ?? params.fallbackModel } : {},
1233
+ requestedAt,
1234
+ completedAt,
1235
+ durationMs: Number.isFinite(requestedAtMs) && Number.isFinite(completedAtMs) ? Math.max(0, completedAtMs - requestedAtMs) : 0
1236
+ };
1237
+ }
1187
1238
  function toSseFrame(event, data) {
1188
1239
  return `event: ${event}
1189
1240
  data: ${JSON.stringify(data)}
@@ -1361,11 +1412,16 @@ function collectInstalledSkillRecords(options) {
1361
1412
  const listedSkills = skillsLoader?.listSkills(false) ?? [];
1362
1413
  const records = listedSkills.map((skill) => {
1363
1414
  const enabled = availableSkillSet.has(skill.name);
1415
+ const metadata = skillsLoader?.getSkillMetadata?.(skill.name);
1416
+ const description = readNonEmptyString(metadata?.description);
1417
+ const descriptionZh = readNonEmptyString(metadata?.description_zh) ?? readNonEmptyString(metadata?.descriptionZh) ?? readNonEmptyString(MARKETPLACE_ZH_COPY_BY_SLUG[skill.name]?.description);
1364
1418
  return {
1365
1419
  type: "skill",
1366
1420
  id: skill.name,
1367
1421
  spec: skill.name,
1368
1422
  label: skill.name,
1423
+ ...description ? { description } : {},
1424
+ ...descriptionZh ? { descriptionZh } : {},
1369
1425
  source: skill.source,
1370
1426
  enabled,
1371
1427
  runtimeStatus: enabled ? "enabled" : "disabled"
@@ -2336,6 +2392,23 @@ function createUiRouter(options) {
2336
2392
  options.publish({ type: "config.updated", payload: { path: "secrets" } });
2337
2393
  return c.json(ok(result));
2338
2394
  });
2395
+ app.get("/api/chat/capabilities", async (c) => {
2396
+ const chatRuntime = options.chatRuntime;
2397
+ if (!chatRuntime) {
2398
+ return c.json(err("NOT_AVAILABLE", "chat runtime unavailable"), 503);
2399
+ }
2400
+ const query = c.req.query();
2401
+ const params = {
2402
+ sessionKey: readNonEmptyString(query.sessionKey),
2403
+ agentId: readNonEmptyString(query.agentId)
2404
+ };
2405
+ try {
2406
+ const capabilities = chatRuntime.getCapabilities ? await chatRuntime.getCapabilities(params) : { stopSupported: Boolean(chatRuntime.stopTurn) };
2407
+ return c.json(ok(capabilities));
2408
+ } catch (error) {
2409
+ return c.json(err("CHAT_RUNTIME_FAILED", String(error)), 500);
2410
+ }
2411
+ });
2339
2412
  app.post("/api/chat/turn", async (c) => {
2340
2413
  if (!options.chatRuntime) {
2341
2414
  return c.json(err("NOT_AVAILABLE", "chat runtime unavailable"), 503);
@@ -2379,6 +2452,31 @@ function createUiRouter(options) {
2379
2452
  return c.json(err("CHAT_TURN_FAILED", String(error)), 500);
2380
2453
  }
2381
2454
  });
2455
+ app.post("/api/chat/turn/stop", async (c) => {
2456
+ const chatRuntime = options.chatRuntime;
2457
+ if (!chatRuntime?.stopTurn) {
2458
+ return c.json(err("NOT_AVAILABLE", "chat turn stop is not supported by runtime"), 503);
2459
+ }
2460
+ const body = await readJson(c.req.raw);
2461
+ if (!body.ok || !body.data || typeof body.data !== "object") {
2462
+ return c.json(err("INVALID_BODY", "invalid json body"), 400);
2463
+ }
2464
+ const runId = readNonEmptyString(body.data.runId);
2465
+ if (!runId) {
2466
+ return c.json(err("INVALID_BODY", "runId is required"), 400);
2467
+ }
2468
+ const request = {
2469
+ runId,
2470
+ ...readNonEmptyString(body.data.sessionKey) ? { sessionKey: readNonEmptyString(body.data.sessionKey) } : {},
2471
+ ...readNonEmptyString(body.data.agentId) ? { agentId: readNonEmptyString(body.data.agentId) } : {}
2472
+ };
2473
+ try {
2474
+ const result = await chatRuntime.stopTurn(request);
2475
+ return c.json(ok(result));
2476
+ } catch (error) {
2477
+ return c.json(err("CHAT_TURN_STOP_FAILED", String(error)), 500);
2478
+ }
2479
+ });
2382
2480
  app.post("/api/chat/turn/stream", async (c) => {
2383
2481
  const chatRuntime = options.chatRuntime;
2384
2482
  if (!chatRuntime) {
@@ -2398,15 +2496,47 @@ function createUiRouter(options) {
2398
2496
  const metadata = isRecord(body.data.metadata) ? body.data.metadata : void 0;
2399
2497
  const requestedAgentId = readNonEmptyString(body.data.agentId) ?? resolveAgentIdFromSessionKey(sessionKey);
2400
2498
  const requestedModel = readNonEmptyString(body.data.model);
2499
+ let runId = createChatRunId();
2500
+ const supportsManagedRuns = Boolean(chatRuntime.startTurnRun && chatRuntime.streamRun);
2501
+ let stopCapabilities = { stopSupported: Boolean(chatRuntime.stopTurn) };
2502
+ if (chatRuntime.getCapabilities) {
2503
+ try {
2504
+ stopCapabilities = await chatRuntime.getCapabilities({
2505
+ sessionKey,
2506
+ ...requestedAgentId ? { agentId: requestedAgentId } : {}
2507
+ });
2508
+ } catch {
2509
+ stopCapabilities = {
2510
+ stopSupported: false,
2511
+ stopReason: "failed to resolve runtime stop capability"
2512
+ };
2513
+ }
2514
+ }
2401
2515
  const request = {
2402
2516
  message,
2403
2517
  sessionKey,
2404
2518
  channel: readNonEmptyString(body.data.channel) ?? "ui",
2405
2519
  chatId: readNonEmptyString(body.data.chatId) ?? "web-ui",
2520
+ runId,
2406
2521
  ...requestedAgentId ? { agentId: requestedAgentId } : {},
2407
2522
  ...requestedModel ? { model: requestedModel } : {},
2408
2523
  ...metadata ? { metadata } : {}
2409
2524
  };
2525
+ let managedRun = null;
2526
+ if (supportsManagedRuns && chatRuntime.startTurnRun) {
2527
+ try {
2528
+ managedRun = await chatRuntime.startTurnRun(request);
2529
+ } catch (error) {
2530
+ return c.json(err("CHAT_TURN_FAILED", String(error)), 500);
2531
+ }
2532
+ if (readNonEmptyString(managedRun.runId)) {
2533
+ runId = readNonEmptyString(managedRun.runId);
2534
+ }
2535
+ stopCapabilities = {
2536
+ stopSupported: managedRun.stopSupported,
2537
+ ...readNonEmptyString(managedRun.stopReason) ? { stopReason: readNonEmptyString(managedRun.stopReason) } : {}
2538
+ };
2539
+ }
2410
2540
  const encoder = new TextEncoder();
2411
2541
  const stream = new ReadableStream({
2412
2542
  start: async (controller) => {
@@ -2415,9 +2545,65 @@ function createUiRouter(options) {
2415
2545
  };
2416
2546
  try {
2417
2547
  push("ready", {
2418
- sessionKey,
2419
- requestedAt: requestedAt.toISOString()
2548
+ sessionKey: managedRun?.sessionKey ?? sessionKey,
2549
+ requestedAt: managedRun?.requestedAt ?? requestedAt.toISOString(),
2550
+ runId,
2551
+ stopSupported: stopCapabilities.stopSupported,
2552
+ ...readNonEmptyString(stopCapabilities.stopReason) ? { stopReason: readNonEmptyString(stopCapabilities.stopReason) } : {}
2420
2553
  });
2554
+ if (supportsManagedRuns && chatRuntime.streamRun) {
2555
+ let hasFinal2 = false;
2556
+ for await (const event of chatRuntime.streamRun({ runId })) {
2557
+ const typed = event;
2558
+ if (typed.type === "delta") {
2559
+ if (typed.delta) {
2560
+ push("delta", { delta: typed.delta });
2561
+ }
2562
+ continue;
2563
+ }
2564
+ if (typed.type === "session_event") {
2565
+ push("session_event", typed.event);
2566
+ continue;
2567
+ }
2568
+ if (typed.type === "final") {
2569
+ const latestRun = chatRuntime.getRun ? await chatRuntime.getRun({ runId }) : null;
2570
+ const response = latestRun ? buildChatTurnViewFromRun({
2571
+ run: latestRun,
2572
+ fallbackSessionKey: sessionKey,
2573
+ fallbackAgentId: requestedAgentId,
2574
+ fallbackModel: requestedModel,
2575
+ fallbackReply: typed.result.reply
2576
+ }) : buildChatTurnView({
2577
+ result: typed.result,
2578
+ fallbackSessionKey: sessionKey,
2579
+ requestedAgentId,
2580
+ requestedModel,
2581
+ requestedAt,
2582
+ startedAtMs
2583
+ });
2584
+ hasFinal2 = true;
2585
+ push("final", response);
2586
+ options.publish({ type: "config.updated", payload: { path: "session" } });
2587
+ continue;
2588
+ }
2589
+ if (typed.type === "error") {
2590
+ push("error", {
2591
+ code: "CHAT_TURN_FAILED",
2592
+ message: typed.error
2593
+ });
2594
+ return;
2595
+ }
2596
+ }
2597
+ if (!hasFinal2) {
2598
+ push("error", {
2599
+ code: "CHAT_TURN_FAILED",
2600
+ message: "stream ended without a final result"
2601
+ });
2602
+ return;
2603
+ }
2604
+ push("done", { ok: true });
2605
+ return;
2606
+ }
2421
2607
  const streamTurn = chatRuntime.processTurnStream;
2422
2608
  if (!streamTurn) {
2423
2609
  const result = await chatRuntime.processTurn(request);
@@ -2497,6 +2683,151 @@ function createUiRouter(options) {
2497
2683
  }
2498
2684
  });
2499
2685
  });
2686
+ app.get("/api/chat/runs", async (c) => {
2687
+ const chatRuntime = options.chatRuntime;
2688
+ if (!chatRuntime?.listRuns) {
2689
+ return c.json(err("NOT_AVAILABLE", "chat run management unavailable"), 503);
2690
+ }
2691
+ const query = c.req.query();
2692
+ const sessionKey = readNonEmptyString(query.sessionKey);
2693
+ const states = readChatRunStates(query.states);
2694
+ const limit = typeof query.limit === "string" ? Number.parseInt(query.limit, 10) : void 0;
2695
+ try {
2696
+ const data = await chatRuntime.listRuns({
2697
+ ...sessionKey ? { sessionKey } : {},
2698
+ ...states ? { states } : {},
2699
+ ...Number.isFinite(limit) ? { limit } : {}
2700
+ });
2701
+ return c.json(ok(data));
2702
+ } catch (error) {
2703
+ return c.json(err("CHAT_RUN_QUERY_FAILED", String(error)), 500);
2704
+ }
2705
+ });
2706
+ app.get("/api/chat/runs/:runId", async (c) => {
2707
+ const chatRuntime = options.chatRuntime;
2708
+ if (!chatRuntime?.getRun) {
2709
+ return c.json(err("NOT_AVAILABLE", "chat run management unavailable"), 503);
2710
+ }
2711
+ const runId = readNonEmptyString(c.req.param("runId"));
2712
+ if (!runId) {
2713
+ return c.json(err("INVALID_PATH", "runId is required"), 400);
2714
+ }
2715
+ try {
2716
+ const run = await chatRuntime.getRun({ runId });
2717
+ if (!run) {
2718
+ return c.json(err("NOT_FOUND", `chat run not found: ${runId}`), 404);
2719
+ }
2720
+ return c.json(ok(run));
2721
+ } catch (error) {
2722
+ return c.json(err("CHAT_RUN_QUERY_FAILED", String(error)), 500);
2723
+ }
2724
+ });
2725
+ app.get("/api/chat/runs/:runId/stream", async (c) => {
2726
+ const chatRuntime = options.chatRuntime;
2727
+ const streamRun = chatRuntime?.streamRun;
2728
+ const getRun = chatRuntime?.getRun;
2729
+ if (!streamRun || !getRun) {
2730
+ return c.json(err("NOT_AVAILABLE", "chat run stream unavailable"), 503);
2731
+ }
2732
+ const runId = readNonEmptyString(c.req.param("runId"));
2733
+ if (!runId) {
2734
+ return c.json(err("INVALID_PATH", "runId is required"), 400);
2735
+ }
2736
+ const query = c.req.query();
2737
+ const fromEventIndex = typeof query.fromEventIndex === "string" ? Number.parseInt(query.fromEventIndex, 10) : void 0;
2738
+ const run = await getRun({ runId });
2739
+ if (!run) {
2740
+ return c.json(err("NOT_FOUND", `chat run not found: ${runId}`), 404);
2741
+ }
2742
+ const encoder = new TextEncoder();
2743
+ const stream = new ReadableStream({
2744
+ start: async (controller) => {
2745
+ const push = (event, data) => {
2746
+ controller.enqueue(encoder.encode(toSseFrame(event, data)));
2747
+ };
2748
+ try {
2749
+ push("ready", {
2750
+ sessionKey: run.sessionKey,
2751
+ requestedAt: run.requestedAt,
2752
+ runId: run.runId,
2753
+ stopSupported: run.stopSupported,
2754
+ ...readNonEmptyString(run.stopReason) ? { stopReason: readNonEmptyString(run.stopReason) } : {}
2755
+ });
2756
+ let hasFinal = false;
2757
+ for await (const event of streamRun({
2758
+ runId: run.runId,
2759
+ ...Number.isFinite(fromEventIndex) ? { fromEventIndex } : {}
2760
+ })) {
2761
+ const typed = event;
2762
+ if (typed.type === "delta") {
2763
+ if (typed.delta) {
2764
+ push("delta", { delta: typed.delta });
2765
+ }
2766
+ continue;
2767
+ }
2768
+ if (typed.type === "session_event") {
2769
+ push("session_event", typed.event);
2770
+ continue;
2771
+ }
2772
+ if (typed.type === "final") {
2773
+ const latestRun = await getRun({ runId: run.runId });
2774
+ const response = latestRun ? buildChatTurnViewFromRun({
2775
+ run: latestRun,
2776
+ fallbackSessionKey: run.sessionKey,
2777
+ fallbackAgentId: run.agentId,
2778
+ fallbackModel: run.model,
2779
+ fallbackReply: typed.result.reply
2780
+ }) : buildChatTurnView({
2781
+ result: typed.result,
2782
+ fallbackSessionKey: run.sessionKey,
2783
+ requestedAgentId: run.agentId,
2784
+ requestedModel: run.model,
2785
+ requestedAt: new Date(run.requestedAt),
2786
+ startedAtMs: Date.parse(run.requestedAt)
2787
+ });
2788
+ hasFinal = true;
2789
+ push("final", response);
2790
+ continue;
2791
+ }
2792
+ if (typed.type === "error") {
2793
+ push("error", {
2794
+ code: "CHAT_TURN_FAILED",
2795
+ message: typed.error
2796
+ });
2797
+ return;
2798
+ }
2799
+ }
2800
+ if (!hasFinal) {
2801
+ const latestRun = await getRun({ runId: run.runId });
2802
+ if (latestRun?.state === "failed") {
2803
+ push("error", {
2804
+ code: "CHAT_TURN_FAILED",
2805
+ message: latestRun.error ?? "chat run failed"
2806
+ });
2807
+ return;
2808
+ }
2809
+ }
2810
+ push("done", { ok: true });
2811
+ } catch (error) {
2812
+ push("error", {
2813
+ code: "CHAT_TURN_FAILED",
2814
+ message: String(error)
2815
+ });
2816
+ } finally {
2817
+ controller.close();
2818
+ }
2819
+ }
2820
+ });
2821
+ return new Response(stream, {
2822
+ status: 200,
2823
+ headers: {
2824
+ "Content-Type": "text/event-stream; charset=utf-8",
2825
+ "Cache-Control": "no-cache, no-transform",
2826
+ "Connection": "keep-alive",
2827
+ "X-Accel-Buffering": "no"
2828
+ }
2829
+ });
2830
+ });
2500
2831
  app.get("/api/sessions", (c) => {
2501
2832
  const query = c.req.query();
2502
2833
  const q = typeof query.q === "string" ? query.q : void 0;
@@ -2610,6 +2941,12 @@ function createUiRouter(options) {
2610
2941
  if (body.data.agents?.defaults && Object.prototype.hasOwnProperty.call(body.data.agents.defaults, "contextTokens")) {
2611
2942
  options.publish({ type: "config.updated", payload: { path: "agents.defaults.contextTokens" } });
2612
2943
  }
2944
+ if (body.data.agents?.defaults && Object.prototype.hasOwnProperty.call(body.data.agents.defaults, "engine")) {
2945
+ options.publish({ type: "config.updated", payload: { path: "agents.defaults.engine" } });
2946
+ }
2947
+ if (body.data.agents?.defaults && Object.prototype.hasOwnProperty.call(body.data.agents.defaults, "engineConfig")) {
2948
+ options.publish({ type: "config.updated", payload: { path: "agents.defaults.engineConfig" } });
2949
+ }
2613
2950
  options.publish({ type: "config.updated", payload: { path: "agents.list" } });
2614
2951
  options.publish({ type: "config.updated", payload: { path: "bindings" } });
2615
2952
  options.publish({ type: "config.updated", payload: { path: "session" } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/server",
3
- "version": "0.5.30",
3
+ "version": "0.6.1",
4
4
  "private": false,
5
5
  "description": "Nextclaw UI/API server.",
6
6
  "type": "module",
@@ -15,10 +15,10 @@
15
15
  ],
16
16
  "dependencies": {
17
17
  "@hono/node-server": "^1.13.3",
18
- "@nextclaw/openclaw-compat": "^0.1.34",
18
+ "@nextclaw/openclaw-compat": "^0.2.0",
19
19
  "hono": "^4.6.2",
20
20
  "ws": "^8.18.0",
21
- "@nextclaw/core": "^0.6.45"
21
+ "@nextclaw/core": "^0.7.0"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@types/node": "^20.17.6",