neoctl 0.2.35 → 0.2.36

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.
Files changed (71) hide show
  1. package/dist/agents/agent-tool-persistence.test.d.ts +1 -0
  2. package/dist/agents/agent-tool-persistence.test.js +313 -0
  3. package/dist/agents/agent-tool-persistence.test.js.map +1 -0
  4. package/dist/agents/agent-tool.js +137 -60
  5. package/dist/agents/agent-tool.js.map +1 -1
  6. package/dist/agents/local-agent-task.d.ts +41 -0
  7. package/dist/agents/local-agent-task.js +3 -0
  8. package/dist/agents/local-agent-task.js.map +1 -1
  9. package/dist/agents/no-nested-delegation.test.d.ts +1 -0
  10. package/dist/agents/no-nested-delegation.test.js +38 -0
  11. package/dist/agents/no-nested-delegation.test.js.map +1 -0
  12. package/dist/agents/smoke-agent-lifecycle.d.ts +1 -0
  13. package/dist/agents/smoke-agent-lifecycle.js +188 -0
  14. package/dist/agents/smoke-agent-lifecycle.js.map +1 -0
  15. package/dist/core/query-engine.js +1 -1
  16. package/dist/core/query-engine.js.map +1 -1
  17. package/dist/core/query.d.ts +3 -1
  18. package/dist/core/query.js +16 -3
  19. package/dist/core/query.js.map +1 -1
  20. package/dist/core/run-agent-pause.test.d.ts +1 -0
  21. package/dist/core/run-agent-pause.test.js +52 -0
  22. package/dist/core/run-agent-pause.test.js.map +1 -0
  23. package/dist/core/run-agent-persistence.test.d.ts +1 -0
  24. package/dist/core/run-agent-persistence.test.js +222 -0
  25. package/dist/core/run-agent-persistence.test.js.map +1 -0
  26. package/dist/core/run-agent.d.ts +11 -0
  27. package/dist/core/run-agent.js +56 -11
  28. package/dist/core/run-agent.js.map +1 -1
  29. package/dist/core/smoke-core-loop.js +8 -7
  30. package/dist/core/smoke-core-loop.js.map +1 -1
  31. package/dist/model/model-metadata.json +99 -99
  32. package/dist/repl/index.js +20 -26
  33. package/dist/repl/index.js.map +1 -1
  34. package/dist/session/session-store-safety.test.d.ts +1 -0
  35. package/dist/session/session-store-safety.test.js +107 -0
  36. package/dist/session/session-store-safety.test.js.map +1 -0
  37. package/dist/session/session-store.d.ts +1 -0
  38. package/dist/session/session-store.js +64 -5
  39. package/dist/session/session-store.js.map +1 -1
  40. package/dist/session/tool-result-memory.d.ts +3 -1
  41. package/dist/session/tool-result-memory.js.map +1 -1
  42. package/dist/tasks/subagent-tools.d.ts +4 -1
  43. package/dist/tasks/subagent-tools.js +85 -24
  44. package/dist/tasks/subagent-tools.js.map +1 -1
  45. package/dist/tasks/subagent-tools.test.d.ts +1 -0
  46. package/dist/tasks/subagent-tools.test.js +158 -0
  47. package/dist/tasks/subagent-tools.test.js.map +1 -0
  48. package/dist/tasks/task-ack-size.test.d.ts +1 -0
  49. package/dist/tasks/task-ack-size.test.js +100 -0
  50. package/dist/tasks/task-ack-size.test.js.map +1 -0
  51. package/dist/tasks/task-persistence.d.ts +22 -0
  52. package/dist/tasks/task-persistence.js +194 -0
  53. package/dist/tasks/task-persistence.js.map +1 -0
  54. package/dist/tasks/task-persistence.test.d.ts +1 -0
  55. package/dist/tasks/task-persistence.test.js +331 -0
  56. package/dist/tasks/task-persistence.test.js.map +1 -0
  57. package/dist/tasks/task-store.d.ts +45 -3
  58. package/dist/tasks/task-store.js +292 -16
  59. package/dist/tasks/task-store.js.map +1 -1
  60. package/dist/tools/tool.d.ts +5 -0
  61. package/dist/tools/tool.js.map +1 -1
  62. package/dist/web/index.d.ts +99 -6
  63. package/dist/web/index.js +93 -56
  64. package/dist/web/index.js.map +1 -1
  65. package/dist/web/smoke-task-session-entrypoints.d.ts +1 -0
  66. package/dist/web/smoke-task-session-entrypoints.js +75 -0
  67. package/dist/web/smoke-task-session-entrypoints.js.map +1 -0
  68. package/dist/web/smoke-web-agent-tasks.d.ts +1 -0
  69. package/dist/web/smoke-web-agent-tasks.js +50 -0
  70. package/dist/web/smoke-web-agent-tasks.js.map +1 -0
  71. package/package.json +4 -4
@@ -6,11 +6,12 @@ import { type ModelProviderName } from "../model/config.js";
6
6
  import { CommunicationLogger, LoggingModelGateway } from "../model/communication-logger.js";
7
7
  import type { ModelUsage, ReasoningConfig } from "../model/model-gateway.js";
8
8
  import { ToolRegistry } from "../tools/registry.js";
9
- import type { Tool } from "../tools/tool.js";
9
+ import type { Tool, ToolUseContext } from "../tools/tool.js";
10
10
  import { type ToolPresentation } from "../tools/tool-catalog.js";
11
11
  import { ExecProcessManager } from "../tools/builtins/exec-process-manager.js";
12
12
  import { type AgentToolRuntime } from "../agents/agent-tool.js";
13
13
  import { TaskStore } from "../tasks/task-store.js";
14
+ import type { TaskNotificationSource } from "../core/query.js";
14
15
  import type { CompactionReport } from "../context/compaction.js";
15
16
  import type { PromptSection } from "../context/prompts.js";
16
17
  import type { NeoPluginResource } from "../plugins/plugin-system.js";
@@ -302,6 +303,11 @@ type WebLineOperation = {
302
303
  };
303
304
  export declare function runWebServer(argv?: string[], runtimeOptions?: RunWebServerOptions): Promise<void>;
304
305
  export declare function createWebRuntime(options?: CreateWebRuntimeOptions): Promise<WebRuntime>;
306
+ /** Bind only the foreground view; never stop work owned by another session. */
307
+ export declare function activateSession(runtime: Pick<WebRuntime, "engine" | "taskStore">): void;
308
+ export declare function sessionAgentTasks(runtime: Pick<WebRuntime, "engine" | "taskStore">): import("../index.js").LocalAgentTask[];
309
+ export declare function createResumeParentContext(engine: QueryEngine, tools: ToolUseContext["tools"], cwd?: string): ToolUseContext;
310
+ export declare function createTaskNotificationSource(taskStore: TaskStore): TaskNotificationSource;
305
311
  export declare class WebRuntimeRouter {
306
312
  private readonly repls;
307
313
  private readonly createRuntime;
@@ -358,7 +364,6 @@ export declare class WebRepl {
358
364
  private pendingUserImageEchoMessageId;
359
365
  private runtimeContextRevision;
360
366
  private runtimeContextPublishQueue;
361
- private pendingRuntimeConfiguration;
362
367
  private readonly backgroundSessionRuns;
363
368
  private readonly suppressReattachedStreaming;
364
369
  constructor(runtime: WebRuntime);
@@ -379,11 +384,48 @@ export declare class WebRepl {
379
384
  type: import("../index.js").LocalAgentTaskType;
380
385
  status: import("../index.js").LocalAgentTaskStatus;
381
386
  description: string;
382
- prompt: string;
383
- progress: import("../index.js").AgentProgressSnapshot;
384
- outputFile: string;
387
+ runGeneration: number;
388
+ progress: {
389
+ lastActivity: string | undefined;
390
+ currentAction: string | undefined;
391
+ totalToolUseCount: number;
392
+ steps: {
393
+ id: string;
394
+ title: string;
395
+ status: "completed" | "running" | "failed";
396
+ }[] | undefined;
397
+ };
398
+ pendingMessageCount: number;
399
+ deliveredRetainedThisRun: number | undefined;
400
+ messageReceipts: {
401
+ id: string;
402
+ status: "queued" | "delivered";
403
+ queuedAt: string;
404
+ deliveredAt: string | undefined;
405
+ runGeneration: number;
406
+ }[];
407
+ result: {
408
+ content: string;
409
+ truncated: boolean;
410
+ status: "completed" | "incomplete" | undefined;
411
+ } | undefined;
412
+ error: string | undefined;
413
+ errorTruncated: boolean;
414
+ runHistory: {
415
+ runGeneration: number;
416
+ status: import("../index.js").LocalAgentTaskStatus;
417
+ completedAt: string | undefined;
418
+ result: {
419
+ content: string;
420
+ truncated: boolean;
421
+ status: "completed" | "incomplete" | undefined;
422
+ } | undefined;
423
+ error: string | undefined;
424
+ errorTruncated: boolean;
425
+ }[];
385
426
  createdAt: string;
386
427
  updatedAt: string;
428
+ completedAt: string | undefined;
387
429
  } | {
388
430
  kind: "terminal";
389
431
  taskId: string;
@@ -409,6 +451,57 @@ export declare class WebRepl {
409
451
  createdAt: number;
410
452
  sessionId: string;
411
453
  })[];
454
+ agentTaskHistory: {
455
+ kind: "agent";
456
+ taskId: string;
457
+ agentId: string;
458
+ agentType: string | undefined;
459
+ type: import("../index.js").LocalAgentTaskType;
460
+ status: import("../index.js").LocalAgentTaskStatus;
461
+ description: string;
462
+ runGeneration: number;
463
+ progress: {
464
+ lastActivity: string | undefined;
465
+ currentAction: string | undefined;
466
+ totalToolUseCount: number;
467
+ steps: {
468
+ id: string;
469
+ title: string;
470
+ status: "completed" | "running" | "failed";
471
+ }[] | undefined;
472
+ };
473
+ pendingMessageCount: number;
474
+ deliveredRetainedThisRun: number | undefined;
475
+ messageReceipts: {
476
+ id: string;
477
+ status: "queued" | "delivered";
478
+ queuedAt: string;
479
+ deliveredAt: string | undefined;
480
+ runGeneration: number;
481
+ }[];
482
+ result: {
483
+ content: string;
484
+ truncated: boolean;
485
+ status: "completed" | "incomplete" | undefined;
486
+ } | undefined;
487
+ error: string | undefined;
488
+ errorTruncated: boolean;
489
+ runHistory: {
490
+ runGeneration: number;
491
+ status: import("../index.js").LocalAgentTaskStatus;
492
+ completedAt: string | undefined;
493
+ result: {
494
+ content: string;
495
+ truncated: boolean;
496
+ status: "completed" | "incomplete" | undefined;
497
+ } | undefined;
498
+ error: string | undefined;
499
+ errorTruncated: boolean;
500
+ }[];
501
+ createdAt: string;
502
+ updatedAt: string;
503
+ completedAt: string | undefined;
504
+ }[];
412
505
  backgroundSessionRunCount: number;
413
506
  runningSessionIds: string[];
414
507
  session: import("../index.js").SessionStoreSnapshot | undefined;
@@ -480,7 +573,6 @@ export declare class WebRepl {
480
573
  private parseToolModes;
481
574
  private applySessionToolOverrides;
482
575
  private syncPluginToolRegistration;
483
- private applyRuntimeConfiguration;
484
576
  private refreshToolConfiguration;
485
577
  private applySessionPluginOverrides;
486
578
  submit(text: string, attachments?: WebAttachmentPayload[]): Promise<{
@@ -545,6 +637,7 @@ export declare class WebRepl {
545
637
  private setStatus;
546
638
  private finalizeForegroundView;
547
639
  private stopForegroundRun;
640
+ private agentTaskSnapshot;
548
641
  private backgroundTasks;
549
642
  private detachRunningForeground;
550
643
  private reattachRunningSession;
package/dist/web/index.js CHANGED
@@ -167,14 +167,10 @@ export async function createWebRuntime(options = {}) {
167
167
  tools.register(tool);
168
168
  const agentRuntime = { modelGateway, tools, taskStore };
169
169
  registerTool(createAgentTool(agentRuntime), { source: "builtin" });
170
+ let runtime;
170
171
  const resumeHandler = async (taskId, directive) => {
171
- const dummyContext = {
172
- agentId: "main",
173
- tools,
174
- appState: new InMemoryAppState("main", options.cwd),
175
- emit: () => undefined,
176
- };
177
- return resumeAgentTask(taskId, directive, agentRuntime, taskStore, dummyContext);
172
+ const parentContext = createResumeParentContext(runtime.engine, tools, options.cwd);
173
+ return resumeAgentTask(taskId, directive, agentRuntime, taskStore, parentContext);
178
174
  };
179
175
  for (const tool of createSubagentTools(taskStore, resumeHandler))
180
176
  registerTool(tool, { source: "builtin" });
@@ -210,8 +206,9 @@ export async function createWebRuntime(options = {}) {
210
206
  },
211
207
  });
212
208
  await engine.initialize();
209
+ activateSession({ engine, taskStore });
213
210
  const initialMetrics = await engine.contextMetrics();
214
- return {
211
+ runtime = {
215
212
  engine,
216
213
  appPromptStore,
217
214
  communicationLogger,
@@ -242,6 +239,7 @@ export async function createWebRuntime(options = {}) {
242
239
  resolveSession: options.resolveSessionToolOverrides,
243
240
  },
244
241
  };
242
+ return runtime;
245
243
  }
246
244
  function normalizeWebRuntimePlugins(value) {
247
245
  if (!Array.isArray(value))
@@ -274,10 +272,38 @@ function applyToolOverrides(registry, catalog, globalOverrides, sessionOverrides
274
272
  for (const tool of catalog)
275
273
  registry.setEnabled(tool.name, sessionOverrides[tool.name] ?? globalOverrides[tool.name] ?? true);
276
274
  }
277
- function createTaskNotificationSource(taskStore) {
275
+ /** Bind only the foreground view; never stop work owned by another session. */
276
+ export function activateSession(runtime) {
277
+ runtime.taskStore.bindSession?.(runtime.engine.snapshot().session?.sessionDir || undefined);
278
+ }
279
+ export function sessionAgentTasks(runtime) {
280
+ const dir = runtime.engine.snapshot().session?.sessionDir || undefined;
281
+ // Older embedders provide minimal TaskStore mocks with no persistence API.
282
+ return runtime.taskStore.list().filter((task) => !runtime.taskStore.bindSession
283
+ || task.ownerSessionDir === (dir ? path.resolve(dir) : undefined));
284
+ }
285
+ export function createResumeParentContext(engine, tools, cwd) {
286
+ const snapshot = engine.snapshot();
287
+ const session = snapshot.session;
288
+ return {
289
+ agentId: snapshot.agentId ?? "main",
290
+ tools,
291
+ appState: new InMemoryAppState(snapshot.agentId ?? "main", cwd),
292
+ session: session?.sessionDir ? {
293
+ sessionId: session.sessionId,
294
+ sessionDir: session.sessionDir,
295
+ rootDir: path.dirname(session.sessionDir),
296
+ } : undefined,
297
+ options: { mainLoopModel: snapshot.model, reasoning: snapshot.reasoning,
298
+ contextWindowTokensOverride: snapshot.contextWindowTokensOverride },
299
+ emit: () => undefined,
300
+ };
301
+ }
302
+ export function createTaskNotificationSource(taskStore) {
278
303
  return {
279
- collectUnnotifiedCompletions() {
280
- return taskStore.collectUnnotifiedCompletions().map((task) => ({
304
+ collectUnnotifiedCompletions(sessionDir) {
305
+ return taskStore.collectUnnotifiedCompletions(sessionDir).filter((task) => !taskStore.bindSession
306
+ || task.ownerSessionDir === (sessionDir ? path.resolve(sessionDir) : undefined)).map((task) => ({
281
307
  taskId: task.taskId,
282
308
  agentId: task.agentId,
283
309
  status: task.status,
@@ -389,7 +415,6 @@ export class WebRepl {
389
415
  pendingUserImageEchoMessageId;
390
416
  runtimeContextRevision = 0;
391
417
  runtimeContextPublishQueue = Promise.resolve();
392
- pendingRuntimeConfiguration = false;
393
418
  backgroundSessionRuns = new Map();
394
419
  suppressReattachedStreaming = new Set();
395
420
  constructor(runtime) {
@@ -442,6 +467,7 @@ export class WebRepl {
442
467
  queuedInput: this.queuedInput,
443
468
  backgroundTaskCount: backgroundTasks.length,
444
469
  backgroundTasks,
470
+ agentTaskHistory: sessionAgentTasks(this.runtime).filter((task) => this.runtime.taskStore.isTerminal(task)).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)).slice(0, 20).map((task) => this.agentTaskSnapshot(task)),
445
471
  backgroundSessionRunCount: this.backgroundSessionRuns.size,
446
472
  runningSessionIds: [...this.backgroundSessionRuns.keys()],
447
473
  session: this.runtime.engine.snapshot().session,
@@ -514,6 +540,8 @@ export class WebRepl {
514
540
  };
515
541
  }
516
542
  async setGlobalTools(value) {
543
+ if (this.busy)
544
+ return actionFailure("TOOL_UPDATE_BLOCKED", "cannot change global tools while a response is running");
517
545
  if (!isRecord(value))
518
546
  return actionFailure("INVALID_REQUEST", "tool overrides must be an object");
519
547
  const support = this.runtime.toolSupport;
@@ -524,12 +552,10 @@ export class WebRepl {
524
552
  return normalized;
525
553
  try {
526
554
  support.globalOverrides = normalized.value;
555
+ this.syncPluginToolRegistration();
556
+ applyToolOverrides(this.runtime.tools, support.catalog, support.globalOverrides, support.sessionOverrides);
527
557
  if (support.persistGlobal)
528
558
  await support.persistGlobal(support.globalOverrides);
529
- if (this.busy)
530
- this.pendingRuntimeConfiguration = true;
531
- else
532
- await this.applyRuntimeConfiguration();
533
559
  await this.refreshToolConfiguration();
534
560
  return { ok: true, state: this.globalTools() };
535
561
  }
@@ -538,6 +564,8 @@ export class WebRepl {
538
564
  }
539
565
  }
540
566
  async setSessionTools(value) {
567
+ if (this.busy)
568
+ return actionFailure("TOOL_UPDATE_BLOCKED", "cannot change session tools while a response is running");
541
569
  if (!isRecord(value))
542
570
  return actionFailure("INVALID_REQUEST", "tool overrides must be an object");
543
571
  const support = this.runtime.toolSupport;
@@ -561,6 +589,8 @@ export class WebRepl {
561
589
  await this.applySessionToolOverrides(await support.resolveSession(sessionId), false);
562
590
  }
563
591
  async setSessionPlugins(value) {
592
+ if (this.busy)
593
+ return actionFailure("PLUGIN_UPDATE_BLOCKED", "cannot change session plugins while a response is running");
564
594
  if (!isRecord(value))
565
595
  return actionFailure("INVALID_REQUEST", "plugin overrides must be an object");
566
596
  const support = this.runtime.pluginSupport;
@@ -624,13 +654,11 @@ export class WebRepl {
624
654
  if (!support)
625
655
  return;
626
656
  support.sessionOverrides = normalizeToolOverrides(overrides, support.catalog);
657
+ this.syncPluginToolRegistration();
658
+ applyToolOverrides(this.runtime.tools, support.catalog, support.globalOverrides, support.sessionOverrides);
627
659
  const sessionId = this.runtime.engine.snapshot().session?.sessionId;
628
660
  if (persist && sessionId && support.persistSession)
629
661
  await support.persistSession(sessionId, support.sessionOverrides);
630
- if (this.busy)
631
- this.pendingRuntimeConfiguration = true;
632
- else
633
- await this.applyRuntimeConfiguration();
634
662
  await this.refreshToolConfiguration();
635
663
  }
636
664
  syncPluginToolRegistration() {
@@ -645,18 +673,6 @@ export class WebRepl {
645
673
  this.runtime.tools.register(tool);
646
674
  }
647
675
  }
648
- async applyRuntimeConfiguration() {
649
- this.syncPluginToolRegistration();
650
- const pluginSupport = this.runtime.pluginSupport;
651
- if (pluginSupport) {
652
- const activePlugins = activeWebRuntimePlugins(pluginSupport.catalog, pluginSupport.overrides);
653
- this.runtime.engine.setRuntimePlugins([...pluginSupport.basePluginIds, ...activePlugins.map((plugin) => plugin.id)], [...pluginSupport.basePromptSections, ...activePlugins.flatMap((plugin) => plugin.promptSections ?? [])]);
654
- }
655
- const toolSupport = this.runtime.toolSupport;
656
- if (toolSupport)
657
- applyToolOverrides(this.runtime.tools, toolSupport.catalog, toolSupport.globalOverrides, toolSupport.sessionOverrides);
658
- this.pendingRuntimeConfiguration = false;
659
- }
660
676
  async refreshToolConfiguration() {
661
677
  const metrics = await this.runtime.engine.contextMetrics();
662
678
  this.runtime.initialMetrics = metrics;
@@ -669,14 +685,21 @@ export class WebRepl {
669
685
  if (!support)
670
686
  return;
671
687
  const normalized = normalizePluginOverrides(overrides, support.catalog);
688
+ for (const plugin of support.catalog)
689
+ for (const tool of plugin.tools)
690
+ this.runtime.tools.unregister(tool.name);
691
+ const activePlugins = activeWebRuntimePlugins(support.catalog, normalized);
692
+ for (const plugin of activePlugins)
693
+ for (const tool of plugin.tools)
694
+ this.runtime.tools.register(tool);
672
695
  support.overrides = normalized;
696
+ const toolSupport = this.runtime.toolSupport;
697
+ if (toolSupport)
698
+ applyToolOverrides(this.runtime.tools, toolSupport.catalog, toolSupport.globalOverrides, toolSupport.sessionOverrides);
699
+ this.runtime.engine.setRuntimePlugins([...support.basePluginIds, ...activePlugins.map((plugin) => plugin.id)], [...support.basePromptSections, ...activePlugins.flatMap((plugin) => plugin.promptSections ?? [])]);
673
700
  const sessionId = this.runtime.engine.snapshot().session?.sessionId;
674
701
  if (persist && sessionId && support.persist)
675
702
  await support.persist(sessionId, normalized);
676
- if (this.busy)
677
- this.pendingRuntimeConfiguration = true;
678
- else
679
- await this.applyRuntimeConfiguration();
680
703
  const metrics = await this.runtime.engine.contextMetrics();
681
704
  this.runtime.initialMetrics = metrics;
682
705
  this.setStatus({ ...this.status, metrics, activityTick: this.status.activityTick + 1 });
@@ -727,11 +750,7 @@ export class WebRepl {
727
750
  this.queuedAttachments = [...(this.queuedAttachments ?? []), ...attachments].slice(0, 20);
728
751
  }
729
752
  startRun(text, attachments = []) {
730
- const run = (async () => {
731
- if (this.pendingRuntimeConfiguration)
732
- await this.applyRuntimeConfiguration();
733
- await this.handleCommandOrPrompt(text, attachments);
734
- })().catch((error) => {
753
+ const run = this.handleCommandOrPrompt(text, attachments).catch((error) => {
735
754
  this.append({ kind: "error", text: error instanceof Error ? error.message : String(error) });
736
755
  this.setBusy(false);
737
756
  this.setStatus({ ...this.status, phase: "ready", detail: undefined });
@@ -759,6 +778,7 @@ export class WebRepl {
759
778
  await this.detachRunningForeground("session switch");
760
779
  this.runtime.engine = this.runtime.engine.forkForSession(sessionId, true);
761
780
  await this.runtime.engine.initialize();
781
+ activateSession(this.runtime);
762
782
  const snapshot = this.runtime.engine.snapshot().session;
763
783
  if (!snapshot)
764
784
  throw new Error("session transcripts are disabled");
@@ -776,6 +796,7 @@ export class WebRepl {
776
796
  await this.detachRunningForeground("new session");
777
797
  this.runtime.engine = this.runtime.engine.forkForSession(undefined, false);
778
798
  await this.runtime.engine.initialize();
799
+ activateSession(this.runtime);
779
800
  const snapshot = this.runtime.engine.snapshot().session;
780
801
  if (!snapshot)
781
802
  throw new Error("session transcripts are disabled");
@@ -892,6 +913,8 @@ export class WebRepl {
892
913
  }
893
914
  }
894
915
  async setContextWindowK(value) {
916
+ if (this.busy)
917
+ return actionFailure("CONTEXT_WINDOW_UPDATE_BLOCKED", "cannot change context window while a response is running");
895
918
  if (typeof value !== "string" || !/^\d+$/u.test(value) || Number(value) <= 0) {
896
919
  return actionFailure("CONTEXT_WINDOW_INVALID", "context window must be a positive integer in k");
897
920
  }
@@ -1017,23 +1040,28 @@ export class WebRepl {
1017
1040
  this.broadcastSync();
1018
1041
  return runWasActive;
1019
1042
  }
1043
+ agentTaskSnapshot(task) {
1044
+ const terminal = this.runtime.taskStore.isTerminal(task);
1045
+ const result = (value, limit = 1500) => value ? { content: value.content.slice(0, limit), truncated: value.content.length > limit, status: value.status } : undefined;
1046
+ return {
1047
+ kind: "agent", taskId: task.taskId, agentId: task.agentId,
1048
+ agentType: task.agentType, type: task.type, status: task.status,
1049
+ description: task.description, runGeneration: task.runGeneration ?? 1,
1050
+ progress: { lastActivity: task.progress.lastActivity, currentAction: task.progress.currentAction, totalToolUseCount: task.progress.totalToolUseCount, steps: task.progress.steps?.slice(-8).map((step) => ({ id: step.id, title: step.title, status: step.status })) },
1051
+ pendingMessageCount: task.pendingMessages.length,
1052
+ deliveredRetainedThisRun: task.messageReceipts?.filter((receipt) => receipt.status === "delivered" && receipt.runGeneration === task.runGeneration).length,
1053
+ messageReceipts: (task.messageReceipts ?? []).slice(-20).map(({ id, status, queuedAt, deliveredAt, runGeneration }) => ({ id, status, queuedAt, deliveredAt, runGeneration })),
1054
+ result: terminal ? result(task.result) : undefined,
1055
+ error: terminal ? task.error?.slice(0, 1500) : undefined,
1056
+ errorTruncated: terminal && (task.error?.length ?? 0) > 1500,
1057
+ runHistory: (task.runHistory ?? []).slice(-3).map((run) => ({ runGeneration: run.runGeneration, status: run.status, completedAt: run.completedAt, result: result(run.result, 600), error: run.error?.slice(0, 600), errorTruncated: (run.error?.length ?? 0) > 600 })),
1058
+ createdAt: task.createdAt, updatedAt: task.updatedAt, completedAt: task.completedAt,
1059
+ };
1060
+ }
1020
1061
  backgroundTasks() {
1021
- const agentTasks = this.runtime.taskStore.list()
1062
+ const agentTasks = sessionAgentTasks(this.runtime)
1022
1063
  .filter((task) => !this.runtime.taskStore.isTerminal(task))
1023
- .map((task) => ({
1024
- kind: "agent",
1025
- taskId: task.taskId,
1026
- agentId: task.agentId,
1027
- agentType: task.agentType,
1028
- type: task.type,
1029
- status: task.status,
1030
- description: task.description,
1031
- prompt: task.prompt,
1032
- progress: task.progress,
1033
- outputFile: task.outputFile,
1034
- createdAt: task.createdAt,
1035
- updatedAt: task.updatedAt,
1036
- }));
1064
+ .map((task) => this.agentTaskSnapshot(task));
1037
1065
  const terminalTasks = this.runtime.execProcessManager.list()
1038
1066
  .filter((terminal) => terminal.status === "running" && terminal.backgrounded)
1039
1067
  .map((terminal) => ({
@@ -1108,6 +1136,7 @@ export class WebRepl {
1108
1136
  await this.detachRunningForeground("session switch");
1109
1137
  this.backgroundSessionRuns.delete(run.sessionId);
1110
1138
  this.runtime.engine = run.engine;
1139
+ activateSession(this.runtime);
1111
1140
  this.activeAbortController = run.abortController;
1112
1141
  this.interruptArmed = false;
1113
1142
  this.foregroundRun = run.promise;
@@ -1430,6 +1459,7 @@ export class WebRepl {
1430
1459
  return void this.append({ kind: "system", text: formatUsageTotals(this.runtime.usage.snapshot()), previewStyle: "summary" });
1431
1460
  if (command.type === "reset") {
1432
1461
  this.runtime.engine.reset();
1462
+ activateSession(this.runtime);
1433
1463
  this.runtime.usage.reset();
1434
1464
  this.status = await resetStatus(this.runtime);
1435
1465
  this.append(systemLine("transcript reset"));
@@ -3026,6 +3056,13 @@ function buildToolResultDisplay(toolName, output, ok) {
3026
3056
  }
3027
3057
  else if (toolName.startsWith("subagent_") || toolName === "subagent_run") {
3028
3058
  subject = stringValue(data.description) || stringValue(data.task_id) || stringValue(data.agent_id);
3059
+ pushToolFact(facts, "任务状态", data.status);
3060
+ pushToolFact(facts, "轮次", data.run_generation);
3061
+ pushToolFact(facts, "待交付", data.pending_messages);
3062
+ if (toolName === "subagent_message")
3063
+ pushToolFact(facts, "交付说明", "仅入队,不代表已交付或完成");
3064
+ if (data.requires_resume === true)
3065
+ pushToolFact(facts, "后续", "需要显式续跑");
3029
3066
  }
3030
3067
  else if (toolName === "subagent_message") {
3031
3068
  subject = stringValue(data.target);