neoctl 0.2.34 → 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 (69) 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/model/model-metadata.json +99 -99
  30. package/dist/repl/index.js +20 -26
  31. package/dist/repl/index.js.map +1 -1
  32. package/dist/session/session-store-safety.test.d.ts +1 -0
  33. package/dist/session/session-store-safety.test.js +107 -0
  34. package/dist/session/session-store-safety.test.js.map +1 -0
  35. package/dist/session/session-store.d.ts +1 -0
  36. package/dist/session/session-store.js +64 -5
  37. package/dist/session/session-store.js.map +1 -1
  38. package/dist/session/tool-result-memory.d.ts +3 -1
  39. package/dist/session/tool-result-memory.js.map +1 -1
  40. package/dist/tasks/subagent-tools.d.ts +4 -1
  41. package/dist/tasks/subagent-tools.js +85 -24
  42. package/dist/tasks/subagent-tools.js.map +1 -1
  43. package/dist/tasks/subagent-tools.test.d.ts +1 -0
  44. package/dist/tasks/subagent-tools.test.js +158 -0
  45. package/dist/tasks/subagent-tools.test.js.map +1 -0
  46. package/dist/tasks/task-ack-size.test.d.ts +1 -0
  47. package/dist/tasks/task-ack-size.test.js +100 -0
  48. package/dist/tasks/task-ack-size.test.js.map +1 -0
  49. package/dist/tasks/task-persistence.d.ts +22 -0
  50. package/dist/tasks/task-persistence.js +194 -0
  51. package/dist/tasks/task-persistence.js.map +1 -0
  52. package/dist/tasks/task-persistence.test.d.ts +1 -0
  53. package/dist/tasks/task-persistence.test.js +331 -0
  54. package/dist/tasks/task-persistence.test.js.map +1 -0
  55. package/dist/tasks/task-store.d.ts +45 -3
  56. package/dist/tasks/task-store.js +292 -16
  57. package/dist/tasks/task-store.js.map +1 -1
  58. package/dist/tools/tool.d.ts +5 -0
  59. package/dist/tools/tool.js.map +1 -1
  60. package/dist/web/index.d.ts +99 -4
  61. package/dist/web/index.js +69 -26
  62. package/dist/web/index.js.map +1 -1
  63. package/dist/web/smoke-task-session-entrypoints.d.ts +1 -0
  64. package/dist/web/smoke-task-session-entrypoints.js +75 -0
  65. package/dist/web/smoke-task-session-entrypoints.js.map +1 -0
  66. package/dist/web/smoke-web-agent-tasks.d.ts +1 -0
  67. package/dist/web/smoke-web-agent-tasks.js +50 -0
  68. package/dist/web/smoke-web-agent-tasks.js.map +1 -0
  69. package/package.json +1 -1
@@ -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;
@@ -378,11 +384,48 @@ export declare class WebRepl {
378
384
  type: import("../index.js").LocalAgentTaskType;
379
385
  status: import("../index.js").LocalAgentTaskStatus;
380
386
  description: string;
381
- prompt: string;
382
- progress: import("../index.js").AgentProgressSnapshot;
383
- 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
+ }[];
384
426
  createdAt: string;
385
427
  updatedAt: string;
428
+ completedAt: string | undefined;
386
429
  } | {
387
430
  kind: "terminal";
388
431
  taskId: string;
@@ -408,6 +451,57 @@ export declare class WebRepl {
408
451
  createdAt: number;
409
452
  sessionId: string;
410
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
+ }[];
411
505
  backgroundSessionRunCount: number;
412
506
  runningSessionIds: string[];
413
507
  session: import("../index.js").SessionStoreSnapshot | undefined;
@@ -543,6 +637,7 @@ export declare class WebRepl {
543
637
  private setStatus;
544
638
  private finalizeForegroundView;
545
639
  private stopForegroundRun;
640
+ private agentTaskSnapshot;
546
641
  private backgroundTasks;
547
642
  private detachRunningForeground;
548
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;
278
288
  return {
279
- collectUnnotifiedCompletions() {
280
- return taskStore.collectUnnotifiedCompletions().map((task) => ({
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) {
303
+ return {
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,
@@ -441,6 +467,7 @@ export class WebRepl {
441
467
  queuedInput: this.queuedInput,
442
468
  backgroundTaskCount: backgroundTasks.length,
443
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)),
444
471
  backgroundSessionRunCount: this.backgroundSessionRuns.size,
445
472
  runningSessionIds: [...this.backgroundSessionRuns.keys()],
446
473
  session: this.runtime.engine.snapshot().session,
@@ -751,6 +778,7 @@ export class WebRepl {
751
778
  await this.detachRunningForeground("session switch");
752
779
  this.runtime.engine = this.runtime.engine.forkForSession(sessionId, true);
753
780
  await this.runtime.engine.initialize();
781
+ activateSession(this.runtime);
754
782
  const snapshot = this.runtime.engine.snapshot().session;
755
783
  if (!snapshot)
756
784
  throw new Error("session transcripts are disabled");
@@ -768,6 +796,7 @@ export class WebRepl {
768
796
  await this.detachRunningForeground("new session");
769
797
  this.runtime.engine = this.runtime.engine.forkForSession(undefined, false);
770
798
  await this.runtime.engine.initialize();
799
+ activateSession(this.runtime);
771
800
  const snapshot = this.runtime.engine.snapshot().session;
772
801
  if (!snapshot)
773
802
  throw new Error("session transcripts are disabled");
@@ -1011,23 +1040,28 @@ export class WebRepl {
1011
1040
  this.broadcastSync();
1012
1041
  return runWasActive;
1013
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
+ }
1014
1061
  backgroundTasks() {
1015
- const agentTasks = this.runtime.taskStore.list()
1062
+ const agentTasks = sessionAgentTasks(this.runtime)
1016
1063
  .filter((task) => !this.runtime.taskStore.isTerminal(task))
1017
- .map((task) => ({
1018
- kind: "agent",
1019
- taskId: task.taskId,
1020
- agentId: task.agentId,
1021
- agentType: task.agentType,
1022
- type: task.type,
1023
- status: task.status,
1024
- description: task.description,
1025
- prompt: task.prompt,
1026
- progress: task.progress,
1027
- outputFile: task.outputFile,
1028
- createdAt: task.createdAt,
1029
- updatedAt: task.updatedAt,
1030
- }));
1064
+ .map((task) => this.agentTaskSnapshot(task));
1031
1065
  const terminalTasks = this.runtime.execProcessManager.list()
1032
1066
  .filter((terminal) => terminal.status === "running" && terminal.backgrounded)
1033
1067
  .map((terminal) => ({
@@ -1102,6 +1136,7 @@ export class WebRepl {
1102
1136
  await this.detachRunningForeground("session switch");
1103
1137
  this.backgroundSessionRuns.delete(run.sessionId);
1104
1138
  this.runtime.engine = run.engine;
1139
+ activateSession(this.runtime);
1105
1140
  this.activeAbortController = run.abortController;
1106
1141
  this.interruptArmed = false;
1107
1142
  this.foregroundRun = run.promise;
@@ -1424,6 +1459,7 @@ export class WebRepl {
1424
1459
  return void this.append({ kind: "system", text: formatUsageTotals(this.runtime.usage.snapshot()), previewStyle: "summary" });
1425
1460
  if (command.type === "reset") {
1426
1461
  this.runtime.engine.reset();
1462
+ activateSession(this.runtime);
1427
1463
  this.runtime.usage.reset();
1428
1464
  this.status = await resetStatus(this.runtime);
1429
1465
  this.append(systemLine("transcript reset"));
@@ -3020,6 +3056,13 @@ function buildToolResultDisplay(toolName, output, ok) {
3020
3056
  }
3021
3057
  else if (toolName.startsWith("subagent_") || toolName === "subagent_run") {
3022
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, "后续", "需要显式续跑");
3023
3066
  }
3024
3067
  else if (toolName === "subagent_message") {
3025
3068
  subject = stringValue(data.target);