neoctl 0.2.0 → 0.2.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.
Files changed (112) hide show
  1. package/README.md +20 -9
  2. package/dist/context/compaction.d.ts +3 -0
  3. package/dist/context/compaction.js +231 -40
  4. package/dist/context/compaction.js.map +1 -1
  5. package/dist/context/prompts.js +4 -4
  6. package/dist/context/prompts.js.map +1 -1
  7. package/dist/core/commands.d.ts +66 -0
  8. package/dist/core/commands.js +151 -0
  9. package/dist/core/commands.js.map +1 -0
  10. package/dist/core/context-metrics.d.ts +5 -2
  11. package/dist/core/context-metrics.js +85 -9
  12. package/dist/core/context-metrics.js.map +1 -1
  13. package/dist/core/image-registry.d.ts +42 -0
  14. package/dist/core/image-registry.js +128 -0
  15. package/dist/core/image-registry.js.map +1 -0
  16. package/dist/core/message-pipeline.d.ts +6 -0
  17. package/dist/core/message-pipeline.js +12 -0
  18. package/dist/core/message-pipeline.js.map +1 -1
  19. package/dist/core/query-engine.d.ts +3 -5
  20. package/dist/core/query-engine.js +35 -48
  21. package/dist/core/query-engine.js.map +1 -1
  22. package/dist/core/query.d.ts +1 -5
  23. package/dist/core/query.js +21 -8
  24. package/dist/core/query.js.map +1 -1
  25. package/dist/core/runtime.d.ts +59 -0
  26. package/dist/core/runtime.js +161 -0
  27. package/dist/core/runtime.js.map +1 -0
  28. package/dist/core/state.d.ts +6 -7
  29. package/dist/core/state.js +2 -0
  30. package/dist/core/state.js.map +1 -1
  31. package/dist/index.d.ts +14 -10
  32. package/dist/index.js +14 -10
  33. package/dist/index.js.map +1 -1
  34. package/dist/model/anthropic-adapter.d.ts +30 -0
  35. package/dist/model/anthropic-adapter.js +106 -0
  36. package/dist/model/anthropic-adapter.js.map +1 -0
  37. package/dist/model/anthropic-mapper.d.ts +13 -0
  38. package/dist/model/anthropic-mapper.js +482 -0
  39. package/dist/model/anthropic-mapper.js.map +1 -0
  40. package/dist/model/config.d.ts +8 -2
  41. package/dist/model/config.js +24 -1
  42. package/dist/model/config.js.map +1 -1
  43. package/dist/model/model-gateway.js +1 -1
  44. package/dist/model/model-gateway.js.map +1 -1
  45. package/dist/model/openai-mappers.js +1 -1
  46. package/dist/model/openai-mappers.js.map +1 -1
  47. package/dist/model/provider-factory.js +17 -0
  48. package/dist/model/provider-factory.js.map +1 -1
  49. package/dist/model/smoke-anthropic-mapper.d.ts +1 -0
  50. package/dist/model/smoke-anthropic-mapper.js +85 -0
  51. package/dist/model/smoke-anthropic-mapper.js.map +1 -0
  52. package/dist/repl/index.js +77 -70
  53. package/dist/repl/index.js.map +1 -1
  54. package/dist/repl/markdown-renderer.d.ts +19 -0
  55. package/dist/repl/markdown-renderer.js +58 -3
  56. package/dist/repl/markdown-renderer.js.map +1 -1
  57. package/dist/repl/transcript-flush.d.ts +6 -0
  58. package/dist/repl/transcript-flush.js +54 -0
  59. package/dist/repl/transcript-flush.js.map +1 -0
  60. package/dist/repl/transcript-format.d.ts +16 -0
  61. package/dist/repl/transcript-format.js +199 -0
  62. package/dist/repl/transcript-format.js.map +1 -0
  63. package/dist/server/agent-manager.d.ts +56 -0
  64. package/dist/server/agent-manager.js +171 -0
  65. package/dist/server/agent-manager.js.map +1 -0
  66. package/dist/server/connection-manager.d.ts +18 -0
  67. package/dist/server/connection-manager.js +52 -0
  68. package/dist/server/connection-manager.js.map +1 -0
  69. package/dist/server/event-mapper.d.ts +4 -0
  70. package/dist/server/event-mapper.js +31 -0
  71. package/dist/server/event-mapper.js.map +1 -0
  72. package/dist/server/query-runtime.d.ts +29 -0
  73. package/dist/server/query-runtime.js +61 -0
  74. package/dist/server/query-runtime.js.map +1 -0
  75. package/dist/server/rpc-router.d.ts +24 -0
  76. package/dist/server/rpc-router.js +165 -0
  77. package/dist/server/rpc-router.js.map +1 -0
  78. package/dist/server/rpc-types.d.ts +92 -0
  79. package/dist/server/rpc-types.js +2 -0
  80. package/dist/server/rpc-types.js.map +1 -0
  81. package/dist/server/ws-server.d.ts +22 -0
  82. package/dist/server/ws-server.js +84 -0
  83. package/dist/server/ws-server.js.map +1 -0
  84. package/dist/session/session-export.d.ts +0 -1
  85. package/dist/session/session-export.js +1 -2
  86. package/dist/session/session-export.js.map +1 -1
  87. package/dist/skills/skill-tool.js +1 -1
  88. package/dist/skills/skill-tool.js.map +1 -1
  89. package/dist/tasks/task-record.d.ts +50 -0
  90. package/dist/tasks/task-record.js +50 -0
  91. package/dist/tasks/task-record.js.map +1 -0
  92. package/dist/tasks/task-store.d.ts +9 -23
  93. package/dist/tasks/task-store.js +2 -37
  94. package/dist/tasks/task-store.js.map +1 -1
  95. package/dist/tasks/task-tools.d.ts +3 -18
  96. package/dist/tasks/task-tools.js +9 -89
  97. package/dist/tasks/task-tools.js.map +1 -1
  98. package/dist/tips.js +6 -6
  99. package/dist/tips.js.map +1 -1
  100. package/dist/tools/builtins/exec-tool.js +2 -2
  101. package/dist/tools/builtins/exec-tool.js.map +1 -1
  102. package/dist/tools/builtins/image-loader-tool.d.ts +22 -0
  103. package/dist/tools/builtins/image-loader-tool.js +235 -0
  104. package/dist/tools/builtins/image-loader-tool.js.map +1 -0
  105. package/dist/types/messages.d.ts +0 -1
  106. package/dist/types/messages.js.map +1 -1
  107. package/dist/web/html.js +3 -4
  108. package/dist/web/html.js.map +1 -1
  109. package/dist/web/index.d.ts +11 -5
  110. package/dist/web/index.js +68 -28
  111. package/dist/web/index.js.map +1 -1
  112. package/package.json +3 -1
package/dist/web/html.js CHANGED
@@ -686,8 +686,7 @@ async function submit() {
686
686
  input.value = '';
687
687
  state.attachments = [];
688
688
  if (state.busy) {
689
- state.busy = false;
690
- state.queuedInput = undefined;
689
+ state.queuedInput = text;
691
690
  }
692
691
  autosize();
693
692
  renderCompletions();
@@ -752,8 +751,8 @@ input.addEventListener('keydown', (e) => {
752
751
  if (e.key === 'ArrowUp' && !input.value) { e.preventDefault(); advanceTip(-1); return; }
753
752
  if (e.key === 'ArrowDown' && state.historyIndex !== undefined) { e.preventDefault(); state.historyIndex -= 1; if (state.historyIndex < 0) { state.historyIndex = undefined; input.value = ''; } else input.value = state.history[state.historyIndex] || ''; autosize(); return; }
754
753
  if (e.key === 'ArrowDown' && !input.value) { e.preventDefault(); advanceTip(); return; }
755
- if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'c') { if (input.value) { input.value = ''; autosize(); renderCompletions(); } else fetch('/api/interrupt', { method: 'POST' }); }
756
- if (e.key === 'Escape') { state.completionIndex = 0; if (state.queuedInput) fetch('/api/interrupt', { method: 'POST' }); else renderCompletions(); }
754
+ if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'c') { if (input.value) { input.value = ''; autosize(); renderCompletions(); } else if (state.queuedInput) { state.queuedInput = undefined; scheduleRender(); fetch('/api/queue/cancel', { method: 'POST' }); } else fetch('/api/interrupt', { method: 'POST' }); }
755
+ if (e.key === 'Escape') { state.completionIndex = 0; if (state.queuedInput) { state.queuedInput = undefined; scheduleRender(); fetch('/api/queue/cancel', { method: 'POST' }); } else renderCompletions(); }
757
756
  });
758
757
  document.addEventListener('keydown', (e) => {
759
758
  if (e.target === input || e.target.closest('input, textarea, select')) return;
@@ -1 +1 @@
1
- {"version":3,"file":"html.js","sourceRoot":"","sources":["../../src/web/html.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA41B1B,CAAC"}
1
+ {"version":3,"file":"html.js","sourceRoot":"","sources":["../../src/web/html.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA21B1B,CAAC"}
@@ -13,7 +13,7 @@ export interface WebRuntime {
13
13
  communicationLogger: CommunicationLogger;
14
14
  modelGateway: LoggingModelGateway;
15
15
  agentRuntime: AgentToolRuntime;
16
- usage: SessionUsageTracker;
16
+ usage: WebSessionUsageTracker;
17
17
  taskStore: TaskStore;
18
18
  tools: ToolRegistry;
19
19
  initialMetrics: ContextMetrics;
@@ -21,7 +21,7 @@ export interface WebRuntime {
21
21
  envPath: string;
22
22
  envNotice?: string;
23
23
  }
24
- export interface UsageTotals {
24
+ interface WebUsageTotals {
25
25
  inputTokens: number;
26
26
  outputTokens: number;
27
27
  totalTokens: number;
@@ -30,12 +30,12 @@ export interface UsageTotals {
30
30
  requests: number;
31
31
  computedTotalTokens: boolean;
32
32
  }
33
- export declare class SessionUsageTracker {
33
+ declare class WebSessionUsageTracker {
34
34
  private totals;
35
35
  private lastUsage?;
36
36
  add(usage: ModelUsage): void;
37
37
  reset(): void;
38
- snapshot(): UsageTotals;
38
+ snapshot(): WebUsageTotals;
39
39
  }
40
40
  interface UiLineImage {
41
41
  src: string;
@@ -140,6 +140,7 @@ export declare class WebRepl {
140
140
  private status;
141
141
  private busy;
142
142
  private queuedInput;
143
+ private queuedAttachments;
143
144
  private foregroundRun;
144
145
  private foregroundRunToken;
145
146
  private readonly backgroundSessionRuns;
@@ -165,7 +166,7 @@ export declare class WebRepl {
165
166
  runningSessionIds: string[];
166
167
  session: import("../index.js").SessionStoreSnapshot | undefined;
167
168
  catalog: {
168
- commands: import("../repl/commands.js").ReplCommandDefinition[];
169
+ commands: import("../core/commands.js").RuntimeCommandDefinition[];
169
170
  modelIds: string[];
170
171
  reasoning: string[];
171
172
  envPath: string;
@@ -183,6 +184,7 @@ export declare class WebRepl {
183
184
  ok: false;
184
185
  error: string;
185
186
  }>;
187
+ private startRun;
186
188
  listSessions(): Promise<{
187
189
  sessions: import("../index.js").SessionSummary[];
188
190
  runningSessionIds: string[];
@@ -217,6 +219,10 @@ export declare class WebRepl {
217
219
  ok: true;
218
220
  interrupted: boolean;
219
221
  };
222
+ cancelQueue(): {
223
+ ok: true;
224
+ cancelled: boolean;
225
+ };
220
226
  private append;
221
227
  private updateLine;
222
228
  private replaceLineText;
package/dist/web/index.js CHANGED
@@ -20,11 +20,11 @@ import { grepTool } from "../tools/builtins/grep-tool.js";
20
20
  import { searchTool } from "../tools/builtins/search-tool.js";
21
21
  import { planTool } from "../tools/builtins/plan-tool.js";
22
22
  import { createOpenAIImageGenerationTool } from "../tools/builtins/image-generation-tool.js";
23
- import { createVisionTool } from "../tools/builtins/vision-tool.js";
23
+ import { createLoadImageTool } from "../tools/builtins/image-loader-tool.js";
24
24
  import { createAgentTool, resumeAgentTask } from "../agents/agent-tool.js";
25
25
  import { createTaskTools } from "../tasks/task-tools.js";
26
26
  import { TaskStore } from "../tasks/task-store.js";
27
- import { parseReplCommand, helpText, replCommandDefinitions } from "../repl/commands.js";
27
+ import { parseReplCommand, helpText, replCommandDefinitions } from "../core/commands.js";
28
28
  import { writeSessionMarkdownExport } from "../session/session-export.js";
29
29
  import { WEB_HTML } from "./html.js";
30
30
  import { appTips, formatTipLine, initialTipIndex, tipAt } from "../tips.js";
@@ -35,8 +35,8 @@ const highlightPackageDir = path.dirname(require.resolve("@highlightjs/cdn-asset
35
35
  const markedAssetPath = path.join(markedPackageDir, "lib", "marked.esm.js");
36
36
  const highlightAssetPath = path.join(highlightPackageDir, "highlight.min.js");
37
37
  const highlightThemeAssetPath = path.join(highlightPackageDir, "styles", "atom-one-dark.min.css");
38
- export class SessionUsageTracker {
39
- totals = emptyUsageTotals();
38
+ class WebSessionUsageTracker {
39
+ totals = emptyWebUsageTotals();
40
40
  lastUsage;
41
41
  add(usage) {
42
42
  if (usage === this.lastUsage)
@@ -61,14 +61,14 @@ export class SessionUsageTracker {
61
61
  };
62
62
  }
63
63
  reset() {
64
- this.totals = emptyUsageTotals();
64
+ this.totals = emptyWebUsageTotals();
65
65
  this.lastUsage = undefined;
66
66
  }
67
67
  snapshot() {
68
68
  return { ...this.totals };
69
69
  }
70
70
  }
71
- function emptyUsageTotals() {
71
+ function emptyWebUsageTotals() {
72
72
  return { inputTokens: 0, outputTokens: 0, totalTokens: 0, reasoningTokens: 0, cachedTokens: 0, requests: 0, computedTotalTokens: false };
73
73
  }
74
74
  function usageTokenValue(value) {
@@ -121,7 +121,7 @@ export async function createWebRuntime(options = {}) {
121
121
  tools.register(readFileTool);
122
122
  tools.register(grepTool);
123
123
  tools.register(searchTool);
124
- tools.register(createVisionTool({ modelGateway, model: modelConfig?.model }));
124
+ tools.register(createLoadImageTool());
125
125
  if (modelConfig?.provider === "openai")
126
126
  tools.register(createOpenAIImageGenerationTool());
127
127
  tools.register(planTool);
@@ -163,7 +163,7 @@ export async function createWebRuntime(options = {}) {
163
163
  communicationLogger,
164
164
  modelGateway,
165
165
  agentRuntime,
166
- usage: new SessionUsageTracker(),
166
+ usage: new WebSessionUsageTracker(),
167
167
  taskStore,
168
168
  tools,
169
169
  initialMetrics,
@@ -193,12 +193,8 @@ function syncImageGenerationTool(runtime, provider) {
193
193
  if (provider === "openai")
194
194
  runtime.tools.register(createOpenAIImageGenerationTool());
195
195
  }
196
- function syncVisionTool(runtime, model) {
197
- runtime.tools.unregister("vision");
198
- runtime.tools.register(createVisionTool({ modelGateway: runtime.modelGateway, model }));
199
- }
200
196
  function formatCreatedEnvNotice(dotEnvPath) {
201
- return `Created default config file: ${dotEnvPath}\nSet MODEL_PROVIDER and the matching provider section (for example OPENAI_API_KEY or KIMI_API_KEY), then restart neo.`;
197
+ return `Created default config file: ${dotEnvPath}\nSet MODEL_PROVIDER and the matching provider section (for example OPENAI_API_KEY, ANTHROPIC_API_KEY, or KIMI_API_KEY), then restart neo.`;
202
198
  }
203
199
  function parseResumeFlag(value) {
204
200
  if (!value)
@@ -258,6 +254,7 @@ export class WebRepl {
258
254
  status;
259
255
  busy = false;
260
256
  queuedInput;
257
+ queuedAttachments;
261
258
  foregroundRun;
262
259
  foregroundRunToken = 0;
263
260
  backgroundSessionRuns = new Map();
@@ -315,8 +312,15 @@ export class WebRepl {
315
312
  await this.detachRunningForeground("session browser");
316
313
  }
317
314
  else if (this.busy) {
318
- this.stopForegroundRun("Interrupted by new prompt");
315
+ this.queuedInput = text;
316
+ this.queuedAttachments = attachments;
317
+ this.broadcastSync();
318
+ return { ok: true };
319
319
  }
320
+ this.startRun(text, attachments);
321
+ return { ok: true };
322
+ }
323
+ startRun(text, attachments = []) {
320
324
  const run = this.handleCommandOrPrompt(text, attachments).catch((error) => {
321
325
  this.append({ kind: "error", text: error instanceof Error ? error.message : String(error) });
322
326
  this.setBusy(false);
@@ -327,7 +331,6 @@ export class WebRepl {
327
331
  if (this.foregroundRun === run)
328
332
  this.foregroundRun = undefined;
329
333
  }).catch(() => undefined);
330
- return { ok: true };
331
334
  }
332
335
  async listSessions() {
333
336
  const sessions = await this.runtime.engine.listSessions(Number.POSITIVE_INFINITY);
@@ -413,7 +416,7 @@ export class WebRepl {
413
416
  async saveLogin(providerValue, values) {
414
417
  const provider = parseLoginProvider(providerValue);
415
418
  if (!provider)
416
- return { ok: false, error: "provider must be openai, deepseek, or kimi" };
419
+ return { ok: false, error: "provider must be openai, anthropic, deepseek, or kimi" };
417
420
  const payload = { ...createLoginFormPayload(this.runtime.envPath, provider), provider, values };
418
421
  const validationError = validateLoginFormPayload(payload);
419
422
  if (validationError)
@@ -429,7 +432,6 @@ export class WebRepl {
429
432
  this.runtime.agentRuntime.modelGateway = this.runtime.modelGateway;
430
433
  this.runtime.engine.setModelProvider({ modelGateway: this.runtime.modelGateway, model: config.model, fallbackModel: config.fallbackModel, reasoning: config.defaultReasoning });
431
434
  syncImageGenerationTool(this.runtime, config.provider);
432
- syncVisionTool(this.runtime, config.model);
433
435
  this.runtime.defaultReasoning = config.defaultReasoning;
434
436
  const metrics = await this.runtime.engine.contextMetrics();
435
437
  this.setStatus({ ...this.status, metrics, activityTick: this.status.activityTick + 1 });
@@ -446,6 +448,14 @@ export class WebRepl {
446
448
  const interrupted = this.stopForegroundRun("Interrupted from neo web");
447
449
  return { ok: true, interrupted };
448
450
  }
451
+ cancelQueue() {
452
+ const had = this.queuedInput !== undefined;
453
+ this.queuedInput = undefined;
454
+ this.queuedAttachments = undefined;
455
+ if (had)
456
+ this.broadcastSync();
457
+ return { ok: true, cancelled: had };
458
+ }
449
459
  append(line) {
450
460
  const id = ++this.lineId;
451
461
  this.lines.push({ id, ...line });
@@ -490,6 +500,7 @@ export class WebRepl {
490
500
  this.activeAbortController = undefined;
491
501
  this.interruptArmed = false;
492
502
  this.queuedInput = undefined;
503
+ this.queuedAttachments = undefined;
493
504
  this.finalizeForegroundView();
494
505
  this.busy = false;
495
506
  this.status = { ...this.status, phase: "ready", detail: undefined, inputTokenUpdatedAt: undefined, outputTokenUpdatedAt: undefined, retryCooldownUntil: undefined };
@@ -534,6 +545,7 @@ export class WebRepl {
534
545
  this.activeAbortController = undefined;
535
546
  this.interruptArmed = false;
536
547
  this.queuedInput = undefined;
548
+ this.queuedAttachments = undefined;
537
549
  this.busy = false;
538
550
  this.status = { ...this.status, phase: "ready", detail: undefined };
539
551
  this.append(systemLine(`Detached running ${sessionId} to background for ${reason}.`));
@@ -675,7 +687,7 @@ export class WebRepl {
675
687
  if (command.type === "help")
676
688
  return void this.append(systemLine(helpText, EXPANDED_SUMMARY_MAX_LINES));
677
689
  if (command.type === "cost")
678
- return void this.append({ kind: "system", text: formatUsageTotals(this.runtime.usage.snapshot()), previewStyle: "summary" });
690
+ return void this.append({ kind: "system", text: formatWebUsageTotals(this.runtime.usage.snapshot()), previewStyle: "summary" });
679
691
  if (command.type === "reset") {
680
692
  this.runtime.engine.reset();
681
693
  this.runtime.usage.reset();
@@ -794,9 +806,19 @@ export class WebRepl {
794
806
  this.activeAbortController = undefined;
795
807
  this.interruptArmed = false;
796
808
  this.finalizeForegroundView();
797
- this.setBusy(false);
798
- this.setStatus({ ...this.status, phase: "ready", detail: undefined, inputTokenUpdatedAt: undefined, outputTokenUpdatedAt: undefined, retryCooldownUntil: undefined });
799
- this.broadcastSync();
809
+ const queuedText = this.queuedInput;
810
+ const queuedAttach = this.queuedAttachments;
811
+ this.queuedInput = undefined;
812
+ this.queuedAttachments = undefined;
813
+ if (queuedText !== undefined) {
814
+ this.startRun(queuedText, queuedAttach ?? []);
815
+ this.broadcastSync();
816
+ }
817
+ else {
818
+ this.setBusy(false);
819
+ this.setStatus({ ...this.status, phase: "ready", detail: undefined, inputTokenUpdatedAt: undefined, outputTokenUpdatedAt: undefined, retryCooldownUntil: undefined });
820
+ this.broadcastSync();
821
+ }
800
822
  }
801
823
  }
802
824
  async runCompaction(type) {
@@ -870,6 +892,8 @@ async function route(req, res, router) {
870
892
  }
871
893
  if (req.method === "POST" && url.pathname === "/api/interrupt")
872
894
  return sendJson(res, repl.interrupt());
895
+ if (req.method === "POST" && url.pathname === "/api/queue/cancel")
896
+ return sendJson(res, repl.cancelQueue());
873
897
  if (req.method === "GET" && url.pathname === "/api/sessions")
874
898
  return sendJson(res, await repl.listSessions());
875
899
  if (req.method === "POST" && url.pathname === "/api/sessions/resume") {
@@ -1086,7 +1110,6 @@ async function handleModelCommand(command, runtime) {
1086
1110
  runtime.agentRuntime.modelGateway = runtime.modelGateway;
1087
1111
  runtime.engine.setModelProvider({ modelGateway: runtime.modelGateway, model: config.model, fallbackModel: config.fallbackModel, reasoning: config.defaultReasoning });
1088
1112
  syncImageGenerationTool(runtime, config.provider);
1089
- syncVisionTool(runtime, config.model);
1090
1113
  runtime.defaultReasoning = config.defaultReasoning;
1091
1114
  }
1092
1115
  }
@@ -1137,14 +1160,14 @@ async function persistModelCommandSettings(runtime, command, reasoningUpdate) {
1137
1160
  return { providerChanged: targetProvider !== currentProvider };
1138
1161
  }
1139
1162
  function currentModelProvider() {
1140
- return parseLoginProvider(process.env.MODEL_PROVIDER) ?? "openai";
1163
+ return parseLoginProvider(process.env.MODEL_PROVIDER) ?? (process.env.ANTHROPIC_API_KEY ? "anthropic" : "openai");
1141
1164
  }
1142
1165
  function parseLoginProvider(value) {
1143
- if (value === "openai" || value === "deepseek" || value === "kimi")
1166
+ if (value === "openai" || value === "anthropic" || value === "deepseek" || value === "kimi")
1144
1167
  return value;
1145
1168
  return undefined;
1146
1169
  }
1147
- const LOGIN_PROVIDERS = ["openai", "deepseek", "kimi"];
1170
+ const LOGIN_PROVIDERS = ["openai", "anthropic", "deepseek", "kimi"];
1148
1171
  const SHARED_LOGIN_FIELDS = [
1149
1172
  { key: "reasoningEffort", label: "Reasoning effort", envKey: "MODEL_REASONING_EFFORT", scope: "shared", options: ["", "off", "none", "minimal", "low", "medium", "high", "xhigh", "max"] },
1150
1173
  { key: "reasoningSummary", label: "Reasoning summary", envKey: "MODEL_REASONING_SUMMARY", scope: "shared", options: ["", "auto", "concise", "detailed"] },
@@ -1162,6 +1185,14 @@ const LOGIN_FIELD_DEFINITIONS = {
1162
1185
  { key: "endpoint", label: "Endpoint", envKey: "OPENAI_ENDPOINT", scope: "provider", placeholder: "auto", options: ["auto", "responses", "chat"] },
1163
1186
  ...SHARED_LOGIN_FIELDS,
1164
1187
  ],
1188
+ anthropic: [
1189
+ { key: "apiKey", label: "API key", envKey: "ANTHROPIC_API_KEY", scope: "provider", required: true, secret: true, placeholder: "sk-ant-..." },
1190
+ { key: "baseUrl", label: "Base URL", envKey: "ANTHROPIC_BASE_URL", scope: "provider", placeholder: "https://api.anthropic.com" },
1191
+ { key: "model", label: "Model", envKey: "ANTHROPIC_MODEL", scope: "provider", required: true, placeholder: "claude-sonnet-4-6" },
1192
+ { key: "fallbackModel", label: "Fallback model", envKey: "ANTHROPIC_FALLBACK_MODEL", scope: "provider" },
1193
+ { key: "version", label: "Anthropic version", envKey: "ANTHROPIC_VERSION", scope: "provider", placeholder: "2023-06-01" },
1194
+ ...SHARED_LOGIN_FIELDS,
1195
+ ],
1165
1196
  deepseek: [
1166
1197
  { key: "apiKey", label: "API key", envKey: "DEEPSEEK_API_KEY", scope: "provider", required: true, secret: true, placeholder: "sk-..." },
1167
1198
  { key: "baseUrl", label: "Base URL", envKey: "DEEPSEEK_BASE_URL", scope: "provider", placeholder: "https://api.deepseek.com" },
@@ -1180,6 +1211,7 @@ const LOGIN_FIELD_DEFINITIONS = {
1180
1211
  const DEPRECATED_MODEL_ENV_KEYS = [
1181
1212
  "MODEL_API_KEY", "MODEL_BASE_URL", "MODEL_ID", "MODEL_FALLBACK_ID", "MODEL_ENDPOINT", "OPENAI_PROVIDER",
1182
1213
  "OPENAI_REASONING_EFFORT", "OPENAI_REASONING_SUMMARY", "OPENAI_MAX_OUTPUT_TOKENS", "OPENAI_TIMEOUT_MS", "OPENAI_STREAM_IDLE_TIMEOUT_MS", "OPENAI_MAX_RETRIES",
1214
+ "ANTHROPIC_REASONING_EFFORT", "ANTHROPIC_REASONING_SUMMARY", "ANTHROPIC_MAX_OUTPUT_TOKENS", "ANTHROPIC_TIMEOUT_MS", "ANTHROPIC_STREAM_IDLE_TIMEOUT_MS", "ANTHROPIC_MAX_RETRIES",
1183
1215
  "DEEPSEEK_REASONING_EFFORT", "DEEPSEEK_REASONING_SUMMARY", "DEEPSEEK_MAX_OUTPUT_TOKENS", "DEEPSEEK_TIMEOUT_MS", "DEEPSEEK_STREAM_IDLE_TIMEOUT_MS", "DEEPSEEK_MAX_RETRIES",
1184
1216
  "KIMI_REASONING_EFFORT", "KIMI_REASONING_SUMMARY", "KIMI_MAX_OUTPUT_TOKENS", "KIMI_TIMEOUT_MS", "KIMI_STREAM_IDLE_TIMEOUT_MS", "KIMI_MAX_RETRIES",
1185
1217
  "MOONSHOT_REASONING_EFFORT", "MOONSHOT_REASONING_SUMMARY", "MOONSHOT_MAX_OUTPUT_TOKENS", "MOONSHOT_TIMEOUT_MS", "MOONSHOT_STREAM_IDLE_TIMEOUT_MS", "MOONSHOT_MAX_RETRIES",
@@ -1218,9 +1250,13 @@ function guessLoginProvider(env) {
1218
1250
  return "kimi";
1219
1251
  if (env.DEEPSEEK_API_KEY ?? process.env.DEEPSEEK_API_KEY)
1220
1252
  return "deepseek";
1253
+ if (env.ANTHROPIC_API_KEY ?? process.env.ANTHROPIC_API_KEY)
1254
+ return "anthropic";
1221
1255
  return currentModelProvider();
1222
1256
  }
1223
1257
  function defaultBaseUrlForLoginProvider(provider) {
1258
+ if (provider === "anthropic")
1259
+ return "https://api.anthropic.com";
1224
1260
  if (provider === "deepseek")
1225
1261
  return "https://api.deepseek.com";
1226
1262
  if (provider === "kimi")
@@ -1228,6 +1264,8 @@ function defaultBaseUrlForLoginProvider(provider) {
1228
1264
  return "https://api.openai.com";
1229
1265
  }
1230
1266
  function defaultModelForLoginProvider(provider) {
1267
+ if (provider === "anthropic")
1268
+ return "claude-sonnet-4-6";
1231
1269
  if (provider === "deepseek")
1232
1270
  return "deepseek-chat";
1233
1271
  if (provider === "kimi")
@@ -1288,6 +1326,8 @@ function stripEnvQuotes(value) {
1288
1326
  return value;
1289
1327
  }
1290
1328
  function modelEnvKeyForProvider(provider) {
1329
+ if (provider === "anthropic")
1330
+ return "ANTHROPIC_MODEL";
1291
1331
  if (provider === "deepseek")
1292
1332
  return "DEEPSEEK_MODEL";
1293
1333
  if (provider === "kimi")
@@ -1844,7 +1884,7 @@ function truncate(value, maxLength) {
1844
1884
  function estimateTokens(text) {
1845
1885
  return text ? Math.max(1, Math.ceil(text.length / 4)) : 0;
1846
1886
  }
1847
- function formatUsageTotals(totals) {
1887
+ function formatWebUsageTotals(totals) {
1848
1888
  const totalLabel = totals.computedTotalTokens ? "Total tokens (computed)" : "Total tokens";
1849
1889
  return [
1850
1890
  "Session usage:",
@@ -1859,12 +1899,12 @@ function formatUsageTotals(totals) {
1859
1899
  function formatManualCompaction(result) {
1860
1900
  if (!result.changed)
1861
1901
  return "No context compaction was needed.";
1862
- return `context compacted: ${result.messages.length} message(s) retained, ${formatNumber(result.tokensFreed ?? 0)} chars removed`;
1902
+ return `context compacted: ${result.messages.length} message(s) retained, ${formatNumber(result.charsFreed ?? result.tokensFreed ?? 0)} chars removed`;
1863
1903
  }
1864
1904
  function formatPureCompaction(result) {
1865
1905
  if (!result.changed)
1866
1906
  return "No context available to purify.";
1867
- return `pure context compacted: ${result.messages.length} sanitized message(s) retained, ${formatNumber(result.tokensFreed ?? 0)} chars removed; raw command/log/code details omitted`;
1907
+ return `pure context compacted: ${result.messages.length} sanitized message(s) retained, ${formatNumber(result.charsFreed ?? result.tokensFreed ?? 0)} chars removed; raw command/log/code details omitted`;
1868
1908
  }
1869
1909
  function formatNumber(value) {
1870
1910
  return value === undefined ? "?" : new Intl.NumberFormat("en-US").format(Math.round(value));