@getpaseo/server 0.1.91-beta.2 → 0.1.91

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 (40) hide show
  1. package/dist/scripts/supervisor.js +21 -0
  2. package/dist/server/server/agent/agent-manager.d.ts +1 -1
  3. package/dist/server/server/agent/agent-manager.js +23 -48
  4. package/dist/server/server/agent/prompt-attachments.js +8 -0
  5. package/dist/server/server/agent/provider-registry.d.ts +0 -1
  6. package/dist/server/server/agent/provider-registry.js +21 -3
  7. package/dist/server/server/agent/providers/codex-app-server-agent.js +9 -5
  8. package/dist/server/server/agent/providers/opencode/test-utils/test-opencode-runtime.d.ts +1 -0
  9. package/dist/server/server/agent/providers/opencode/test-utils/test-opencode-runtime.js +4 -0
  10. package/dist/server/server/agent/providers/opencode-agent.d.ts +16 -2
  11. package/dist/server/server/agent/providers/opencode-agent.js +75 -4
  12. package/dist/server/server/agent/providers/pi/agent.d.ts +23 -1
  13. package/dist/server/server/agent/providers/pi/agent.js +219 -13
  14. package/dist/server/server/agent/providers/pi/cli-runtime.js +9 -0
  15. package/dist/server/server/agent/providers/pi/rpc-types.d.ts +9 -0
  16. package/dist/server/server/agent/providers/pi/runtime.d.ts +2 -0
  17. package/dist/server/server/agent/providers/pi/session-descriptor.d.ts +12 -0
  18. package/dist/server/server/agent/providers/pi/session-descriptor.js +304 -0
  19. package/dist/server/server/agent/providers/pi/test-utils/fake-pi.d.ts +8 -0
  20. package/dist/server/server/agent/providers/pi/test-utils/fake-pi.js +22 -0
  21. package/dist/server/server/agent/runtime-mcp-config.d.ts +8 -0
  22. package/dist/server/server/agent/runtime-mcp-config.js +50 -0
  23. package/dist/server/server/auto-archive-on-merge/archive-if-safe.js +2 -2
  24. package/dist/server/server/daemon-worker.js +84 -1
  25. package/dist/server/server/file-upload/index.d.ts +27 -0
  26. package/dist/server/server/file-upload/index.js +158 -0
  27. package/dist/server/server/loop-service.d.ts +12 -12
  28. package/dist/server/server/persisted-config.d.ts +8 -0
  29. package/dist/server/server/persisted-config.js +1 -1
  30. package/dist/server/server/persistence-hooks.js +6 -4
  31. package/dist/server/server/session.d.ts +5 -2
  32. package/dist/server/server/session.js +20 -2
  33. package/dist/server/server/speech/providers/local/runtime.js +1 -0
  34. package/dist/server/server/speech/providers/local/worker-client.d.ts +14 -1
  35. package/dist/server/server/speech/providers/local/worker-client.js +169 -7
  36. package/dist/server/server/websocket-server.d.ts +2 -0
  37. package/dist/server/server/websocket-server.js +20 -7
  38. package/dist/server/server/workspace-registry.d.ts +4 -4
  39. package/dist/src/server/persisted-config.js +1 -1
  40. package/package.json +5 -5
@@ -127,6 +127,26 @@ export function runSupervisor(options) {
127
127
  execArgv: workerExecArgv,
128
128
  });
129
129
  }
130
+ const currentChild = child;
131
+ const heartbeat = setInterval(() => {
132
+ const message = { type: "paseo:supervisor-heartbeat" };
133
+ if (currentChild.connected) {
134
+ currentChild.send?.(message, (error) => {
135
+ if (error) {
136
+ writeLifecycleLog("Worker heartbeat IPC send failed", {
137
+ error: error instanceof Error ? error.message : String(error),
138
+ });
139
+ }
140
+ });
141
+ }
142
+ else {
143
+ writeLifecycleLog("Worker heartbeat skipped because IPC channel is disconnected");
144
+ }
145
+ }, 1000);
146
+ heartbeat.unref();
147
+ child.on("disconnect", () => {
148
+ writeLifecycleLog("Worker IPC channel disconnected");
149
+ });
130
150
  child.stdout?.on("data", (chunk) => {
131
151
  process.stdout.write(chunk);
132
152
  writeDurableChunk(chunk);
@@ -157,6 +177,7 @@ export function runSupervisor(options) {
157
177
  requestRestart("Restart requested by worker");
158
178
  });
159
179
  child.on("close", (code, signal) => {
180
+ clearInterval(heartbeat);
160
181
  const exitDescriptor = describeExit(code, signal);
161
182
  writeLifecycleLog("Worker exited", { code, signal, exit: exitDescriptor });
162
183
  if (shuttingDown) {
@@ -163,7 +163,6 @@ export interface AgentMetricsSnapshot {
163
163
  export declare class AgentManager {
164
164
  private readonly clients;
165
165
  private readonly providerEnabled;
166
- private readonly providerDerivedFromId;
167
166
  private readonly agents;
168
167
  private readonly timelineStore;
169
168
  private readonly agentsAwaitingInitialSnapshotPersist;
@@ -337,6 +336,7 @@ export declare class AgentManager {
337
336
  private dispatchStream;
338
337
  private dispatch;
339
338
  private normalizeConfig;
339
+ private prepareSessionConfig;
340
340
  private applyDaemonAppendSystemPrompt;
341
341
  private buildLaunchContext;
342
342
  private requireAvailableClient;
@@ -10,9 +10,9 @@ import { InMemoryAgentTimelineStore, } from "./agent-timeline-store.js";
10
10
  import { AGENT_STREAM_COALESCE_DEFAULT_WINDOW_MS, AgentStreamCoalescer, } from "./agent-stream-coalescer.js";
11
11
  import { ForegroundRunState } from "./foreground-run-state.js";
12
12
  import { getAgentProviderDefinition } from "@getpaseo/protocol/provider-manifest";
13
- import { IMPORTABLE_PROVIDERS } from "./provider-registry.js";
14
13
  import { invokeRewindCapability } from "./rewind/rewind.js";
15
14
  import { isSystemInjectedEnvelope } from "./agent-prompt.js";
15
+ import { stripInternalPaseoMcpServer, withRuntimePaseoMcpServer } from "./runtime-mcp-config.js";
16
16
  const RELOAD_SESSION_CLOSE_TIMEOUT_MS = 3000;
17
17
  const INTERRUPT_SESSION_TIMEOUT_MS = 2000;
18
18
  const STORED_AGENT_CAPABILITIES = {
@@ -54,7 +54,7 @@ function buildStoredAgentConfig(record) {
54
54
  }
55
55
  if (record.config.mcpServers != null)
56
56
  config.mcpServers = record.config.mcpServers;
57
- return config;
57
+ return stripInternalPaseoMcpServer(config);
58
58
  }
59
59
  export { AGENT_LIFECYCLE_STATUSES };
60
60
  function resolveInitialAttention(input) {
@@ -126,7 +126,6 @@ export class AgentManager {
126
126
  constructor(options) {
127
127
  this.clients = new Map();
128
128
  this.providerEnabled = new Map();
129
- this.providerDerivedFromId = new Map();
130
129
  this.agents = new Map();
131
130
  this.timelineStore = new InMemoryAgentTimelineStore();
132
131
  this.agentsAwaitingInitialSnapshotPersist = new Set();
@@ -166,7 +165,6 @@ export class AgentManager {
166
165
  for (const [provider, definition] of Object.entries(input.providerDefinitions)) {
167
166
  if (definition) {
168
167
  this.providerEnabled.set(provider, definition.enabled);
169
- this.providerDerivedFromId.set(provider, definition.derivedFromProviderId ?? null);
170
168
  }
171
169
  }
172
170
  for (const [provider, client] of Object.entries(input.clients)) {
@@ -303,15 +301,9 @@ export class AgentManager {
303
301
  .slice(0, limit);
304
302
  }
305
303
  isProviderImportable(provider, providerFilter) {
306
- if (!IMPORTABLE_PROVIDERS.includes(provider)) {
307
- return false;
308
- }
309
304
  if (this.providerEnabled.get(provider) === false) {
310
305
  return false;
311
306
  }
312
- if (this.providerDerivedFromId.get(provider) != null) {
313
- return false;
314
- }
315
307
  if (providerFilter && !providerFilter.has(provider)) {
316
308
  return false;
317
309
  }
@@ -427,27 +419,15 @@ export class AgentManager {
427
419
  }
428
420
  async createAgent(config, agentId, options) {
429
421
  const resolvedAgentId = validateAgentId(agentId ?? this.idFactory(), "createAgent");
430
- const injectedConfig = this.mcpBaseUrl == null
431
- ? config
432
- : {
433
- ...config,
434
- mcpServers: {
435
- paseo: {
436
- type: "http",
437
- url: `${this.mcpBaseUrl}?callerAgentId=${resolvedAgentId}`,
438
- },
439
- ...config.mcpServers,
440
- },
441
- };
442
- this.requireEnabledProvider(injectedConfig.provider);
443
- const normalizedConfig = this.applyDaemonAppendSystemPrompt(await this.normalizeConfig(injectedConfig));
422
+ const { storedConfig, launchConfig } = await this.prepareSessionConfig(config, resolvedAgentId);
423
+ this.requireEnabledProvider(storedConfig.provider);
444
424
  const launchContext = this.buildLaunchContext(resolvedAgentId, options?.env);
445
425
  const client = await this.requireAvailableClient({
446
- provider: normalizedConfig.provider,
426
+ provider: storedConfig.provider,
447
427
  });
448
428
  const createOptions = this.buildCreateSessionOptions(options);
449
- const session = await client.createSession(normalizedConfig, launchContext, createOptions);
450
- return this.registerSession(session, normalizedConfig, resolvedAgentId, {
429
+ const session = await client.createSession(launchConfig, launchContext, createOptions);
430
+ return this.registerSession(session, storedConfig, resolvedAgentId, {
451
431
  labels: options?.labels,
452
432
  workspaceId: options?.workspaceId,
453
433
  initialTitle: options?.initialTitle,
@@ -468,29 +448,15 @@ export class AgentManager {
468
448
  ...overrides,
469
449
  provider: handle.provider,
470
450
  };
471
- const normalizedConfig = this.applyDaemonAppendSystemPrompt(await this.normalizeConfig(mergedConfig));
472
- const resumeOverrides = { ...overrides };
473
- let hasResumeOverrides = overrides !== undefined;
474
- if (normalizedConfig.model !== mergedConfig.model) {
475
- resumeOverrides.model = normalizedConfig.model;
476
- hasResumeOverrides = true;
477
- }
478
- if (normalizedConfig.modeId !== mergedConfig.modeId) {
479
- resumeOverrides.modeId = normalizedConfig.modeId;
480
- hasResumeOverrides = true;
481
- }
482
- if (metadata.daemonAppendSystemPrompt !== normalizedConfig.daemonAppendSystemPrompt) {
483
- resumeOverrides.daemonAppendSystemPrompt = normalizedConfig.daemonAppendSystemPrompt;
484
- hasResumeOverrides = true;
485
- }
451
+ const { storedConfig, launchConfig } = await this.prepareSessionConfig(mergedConfig, resolvedAgentId);
486
452
  const launchContext = this.buildLaunchContext(resolvedAgentId);
487
453
  const client = this.requireClient(handle.provider);
488
454
  const available = await client.isAvailable();
489
455
  if (!available) {
490
456
  throw new Error(`Provider '${handle.provider}' is not available. Please ensure the CLI is installed.`);
491
457
  }
492
- const session = await client.resumeSession(handle, hasResumeOverrides ? resumeOverrides : undefined, launchContext);
493
- return this.registerSession(session, normalizedConfig, resolvedAgentId, options);
458
+ const session = await client.resumeSession(handle, launchConfig, launchContext);
459
+ return this.registerSession(session, storedConfig, resolvedAgentId, options);
494
460
  }
495
461
  // Hot-reload an active agent session with config overrides. By default the
496
462
  // in-memory timeline is preserved (used for voice-mode toggles and similar
@@ -517,11 +483,11 @@ export class AgentManager {
517
483
  ...overrides,
518
484
  provider,
519
485
  };
520
- const normalizedConfig = this.applyDaemonAppendSystemPrompt(await this.normalizeConfig(refreshConfig));
486
+ const { storedConfig, launchConfig } = await this.prepareSessionConfig(refreshConfig, agentId);
521
487
  const launchContext = this.buildLaunchContext(agentId);
522
488
  const session = handle
523
- ? await client.resumeSession(handle, normalizedConfig, launchContext)
524
- : await client.createSession(normalizedConfig, launchContext);
489
+ ? await client.resumeSession(handle, launchConfig, launchContext)
490
+ : await client.createSession(launchConfig, launchContext);
525
491
  this.agentStreamCoalescer.flushAndDiscard(agentId);
526
492
  // Remove the existing agent entry before swapping sessions
527
493
  this.agents.delete(agentId);
@@ -539,7 +505,7 @@ export class AgentManager {
539
505
  this.timelineStore.delete(agentId);
540
506
  }
541
507
  // Preserve existing labels and timeline during reload.
542
- return this.registerSession(session, normalizedConfig, agentId, {
508
+ return this.registerSession(session, storedConfig, agentId, {
543
509
  labels: existing.labels,
544
510
  createdAt: existing.createdAt,
545
511
  updatedAt: existing.updatedAt,
@@ -2503,6 +2469,15 @@ export class AgentManager {
2503
2469
  }
2504
2470
  return normalized;
2505
2471
  }
2472
+ async prepareSessionConfig(config, agentId) {
2473
+ const storedConfig = await this.normalizeConfig(stripInternalPaseoMcpServer(config));
2474
+ const launchConfig = this.applyDaemonAppendSystemPrompt(withRuntimePaseoMcpServer({
2475
+ config: storedConfig,
2476
+ agentId,
2477
+ mcpBaseUrl: this.mcpBaseUrl,
2478
+ }));
2479
+ return { storedConfig, launchConfig };
2480
+ }
2506
2481
  applyDaemonAppendSystemPrompt(config) {
2507
2482
  const daemonAppendSystemPrompt = this.appendSystemPrompt.trim();
2508
2483
  const next = { ...config };
@@ -45,6 +45,14 @@ export function renderPromptAttachmentAsText(attachment) {
45
45
  });
46
46
  return lines.join("\n");
47
47
  }
48
+ case "uploaded_file": {
49
+ return [
50
+ `Uploaded file: ${attachment.fileName}`,
51
+ `Path: ${attachment.path}`,
52
+ `MIME: ${attachment.mimeType}`,
53
+ `Size: ${attachment.size} bytes`,
54
+ ].join("\n");
55
+ }
48
56
  default:
49
57
  throw new Error("unreachable");
50
58
  }
@@ -19,7 +19,6 @@ export interface ProviderDefinition extends AgentProviderDefinition {
19
19
  fetchModels: (options: ListModelsOptions) => Promise<AgentModelDefinition[]>;
20
20
  fetchModes: (options: ListModesOptions) => Promise<AgentMode[]>;
21
21
  }
22
- export { IMPORTABLE_PROVIDERS } from "@getpaseo/protocol/importable-providers";
23
22
  export interface BuildProviderRegistryOptions {
24
23
  runtimeSettings?: AgentProviderRuntimeSettingsMap;
25
24
  providerOverrides?: Record<string, ProviderOverride>;
@@ -14,7 +14,6 @@ function isNonEmptyStringArray(value) {
14
14
  return value.length > 0;
15
15
  }
16
16
  export { AGENT_PROVIDER_DEFINITIONS, getAgentProviderDefinition };
17
- export { IMPORTABLE_PROVIDERS } from "@getpaseo/protocol/importable-providers";
18
17
  const PROVIDER_CLIENT_FACTORIES = {
19
18
  claude: (logger, runtimeSettings) => new ClaudeAgentClient({
20
19
  logger,
@@ -34,9 +33,22 @@ const PROVIDER_CLIENT_FACTORIES = {
34
33
  env: runtimeSettings?.env,
35
34
  }),
36
35
  opencode: (logger, runtimeSettings) => new OpenCodeAgentClient(logger, runtimeSettings),
37
- pi: (logger, runtimeSettings) => new PiRpcAgentClient({
36
+ pi: (logger, runtimeSettings, options) => new PiRpcAgentClient({
38
37
  logger,
39
38
  runtimeSettings,
39
+ providerParams: options?.providerParams,
40
+ }),
41
+ omp: (logger, runtimeSettings, options) => new PiRpcAgentClient({
42
+ logger,
43
+ runtimeSettings: mergeRuntimeSettings({
44
+ command: {
45
+ mode: "replace",
46
+ argv: ["omp"],
47
+ },
48
+ }, runtimeSettings),
49
+ providerParams: options?.providerParams ?? {
50
+ sessionDir: "~/.omp/agent/sessions",
51
+ },
40
52
  }),
41
53
  mock: (logger) => new MockLoadTestAgentClient(logger),
42
54
  "mock-slow": () => new MockSlowProviderClient(),
@@ -293,10 +305,12 @@ function buildResolvedBuiltinProviders(providerOverrides, runtimeSettings, optio
293
305
  profileModels: override?.models ?? [],
294
306
  additionalModels: override?.additionalModels ?? [],
295
307
  profileModelsAreAdditive: false,
296
- enabled: override?.enabled !== false,
308
+ enabled: override?.enabled ?? definition.enabledByDefault ?? true,
297
309
  derivedFromProviderId: null,
310
+ providerParams: override?.params,
298
311
  createBaseClient: (logger) => factory(logger, mergedRuntimeSettings, {
299
312
  workspaceGitService: options.workspaceGitService,
313
+ providerParams: override?.params,
300
314
  }),
301
315
  });
302
316
  }
@@ -330,6 +344,7 @@ function addDerivedProviders(resolvedProviders, providerOverrides) {
330
344
  profileModelsAreAdditive: false,
331
345
  enabled: override.enabled !== false,
332
346
  derivedFromProviderId: null,
347
+ providerParams: override.params,
333
348
  createBaseClient: (logger) => providerId === "cursor"
334
349
  ? new CursorACPAgentClient({
335
350
  logger,
@@ -356,6 +371,7 @@ function addDerivedProviders(resolvedProviders, providerOverrides) {
356
371
  const mergedRuntimeSettings = mergeRuntimeSettings(baseProvider.runtimeSettings, toRuntimeSettings(override));
357
372
  const baseDefinition = baseProvider.definition;
358
373
  const baseFactory = getProviderClientFactory(baseProviderId);
374
+ const providerParams = override.params ?? baseProvider.providerParams;
359
375
  resolvedProviders.set(providerId, {
360
376
  definition: createDerivedDefinition(providerId, baseDefinition, override),
361
377
  runtimeSettings: mergedRuntimeSettings,
@@ -364,7 +380,9 @@ function addDerivedProviders(resolvedProviders, providerOverrides) {
364
380
  profileModelsAreAdditive: false,
365
381
  enabled: override.enabled !== false,
366
382
  derivedFromProviderId: baseProviderId,
383
+ providerParams,
367
384
  createBaseClient: (logger) => baseFactory(logger, mergedRuntimeSettings, {
385
+ providerParams,
368
386
  customProvider: {
369
387
  id: providerId,
370
388
  label: override.label ?? providerId,
@@ -34,6 +34,14 @@ const TURN_START_TIMEOUT_MS = 90 * 1000;
34
34
  const INTERRUPT_TIMEOUT_MS = 2000;
35
35
  const CODEX_PROVIDER = "codex";
36
36
  const CODEX_IMAGE_ATTACHMENT_DIR = "paseo-attachments";
37
+ // Codex treats most app-server client names as the model-request originator.
38
+ // This reserved Codex name is non-originating, so requests keep Codex's default
39
+ // CLI identity instead of showing up as Paseo in provider usage logs.
40
+ const CODEX_NON_ORIGINATING_APP_SERVER_CLIENT_INFO = {
41
+ name: "codex_app_server_daemon",
42
+ title: "Codex App Server Daemon",
43
+ version: "0.0.0",
44
+ };
37
45
  const ASSISTANT_MESSAGE_BOUNDARY_MARKDOWN = "\n\n---\n\n";
38
46
  const CODEX_TOOL_THREAD_ITEM_TYPES = new Set([
39
47
  "commandExecution",
@@ -2093,11 +2101,7 @@ export function buildCodexAppServerEnv(runtimeSettings, launchEnv) {
2093
2101
  }
2094
2102
  function buildCodexAppServerInitializeParams() {
2095
2103
  return {
2096
- clientInfo: {
2097
- name: "paseo",
2098
- title: "Paseo",
2099
- version: "0.0.0",
2100
- },
2104
+ clientInfo: CODEX_NON_ORIGINATING_APP_SERVER_CLIENT_INFO,
2101
2105
  capabilities: {
2102
2106
  experimentalApi: true,
2103
2107
  },
@@ -76,6 +76,7 @@ export declare class TestOpenCodeClient {
76
76
  sessionMessagesResponse: OpenCodeResponse;
77
77
  sessionPromptAsyncEvents: unknown[];
78
78
  sessionPromptAsyncResponse: OpenCodeResponse;
79
+ sessionSummarizeEvents: unknown[];
79
80
  sessionSummarizeResponse: OpenCodeResponse;
80
81
  sessionUpdateResponse: OpenCodeResponse;
81
82
  private readonly queuedEventStream;
@@ -70,6 +70,7 @@ export class TestOpenCodeClient {
70
70
  this.sessionMessagesResponse = { data: [] };
71
71
  this.sessionPromptAsyncEvents = [idleEvent()];
72
72
  this.sessionPromptAsyncResponse = {};
73
+ this.sessionSummarizeEvents = [idleEvent()];
73
74
  this.sessionSummarizeResponse = { data: {} };
74
75
  this.sessionUpdateResponse = {};
75
76
  this.queuedEventStream = createQueuedEventStream();
@@ -182,6 +183,9 @@ export class TestOpenCodeClient {
182
183
  },
183
184
  summarize: async (parameters) => {
184
185
  this.calls.sessionSummarize.push(parameters);
186
+ for (const event of this.sessionSummarizeEvents) {
187
+ this.emitEvent(event);
188
+ }
185
189
  return this.sessionSummarizeResponse;
186
190
  },
187
191
  update: async (parameters) => {
@@ -1,6 +1,6 @@
1
- import { type AssistantMessage as OpenCodeAssistantMessage, type Event as OpenCodeEvent, type FilePartInput as OpenCodeFilePartInput, type OpencodeClient, type TextPartInput as OpenCodeTextPartInput } from "@opencode-ai/sdk/v2/client";
1
+ import { type AssistantMessage as OpenCodeAssistantMessage, type Event as OpenCodeEvent, type FilePartInput as OpenCodeFilePartInput, type Message as OpenCodeMessage, type OpencodeClient, type Part as OpenCodePart, type TextPartInput as OpenCodeTextPartInput } from "@opencode-ai/sdk/v2/client";
2
2
  import type { Logger } from "pino";
3
- import { type AgentCapabilityFlags, type AgentClient, type AgentCreateSessionOptions, type AgentFeature, type AgentLaunchContext, type AgentMode, type AgentModelDefinition, type AgentPermissionRequest, type AgentPermissionResponse, type AgentPersistenceHandle, type AgentPromptInput, type AgentRunOptions, type AgentRunResult, type AgentRuntimeInfo, type AgentSession, type AgentSessionConfig, type AgentSlashCommand, type AgentStreamEvent, type AgentUsage, type ResolveAgentCreateConfigInput, type ResolveAgentCreateConfigResult, type ListModelsOptions, type ListModesOptions, type ListPersistedAgentsOptions, type PersistedAgentDescriptor, type ToolCallTimelineItem } from "../agent-sdk-types.js";
3
+ import { type AgentCapabilityFlags, type AgentClient, type AgentCreateSessionOptions, type AgentFeature, type AgentLaunchContext, type AgentMode, type AgentModelDefinition, type AgentPermissionRequest, type AgentPermissionResponse, type AgentPersistenceHandle, type AgentPromptInput, type AgentRunOptions, type AgentRunResult, type AgentRuntimeInfo, type AgentSession, type AgentSessionConfig, type AgentSlashCommand, type AgentStreamEvent, type AgentTimelineItem, type AgentUsage, type ResolveAgentCreateConfigInput, type ResolveAgentCreateConfigResult, type ListModelsOptions, type ListModesOptions, type ListPersistedAgentsOptions, type PersistedAgentDescriptor, type ToolCallTimelineItem } from "../agent-sdk-types.js";
4
4
  import { isDefaultAgentCreateConfigUnattended } from "../create-agent-mode.js";
5
5
  import { type ProviderRuntimeSettings } from "../provider-launch-config.js";
6
6
  import { type OpenCodeRuntime } from "./opencode/runtime.js";
@@ -10,6 +10,10 @@ type OpenCodeAgentConfig = AgentSessionConfig & {
10
10
  provider: "opencode";
11
11
  };
12
12
  type OpenCodeMessageRole = "user" | "assistant";
13
+ interface OpenCodeSessionMessage {
14
+ info: OpenCodeMessage;
15
+ parts: OpenCodePart[];
16
+ }
13
17
  declare function reconcileOpenCodeSessionClose(params: {
14
18
  client: Pick<OpencodeClient, "session">;
15
19
  sessionId: string;
@@ -79,8 +83,10 @@ declare function mergeOpenCodeStepFinishUsage(usage: AgentUsage, part: {
79
83
  }): void;
80
84
  declare function hasNormalizedOpenCodeUsage(usage: AgentUsage): boolean;
81
85
  declare function buildOpenCodePromptParts(prompt: AgentPromptInput): Array<OpenCodeTextPartInput | OpenCodeFilePartInput>;
86
+ declare function buildOpenCodeSessionTimeline(messages: ReadonlyArray<OpenCodeSessionMessage>): AgentTimelineItem[];
82
87
  export declare const __openCodeInternals: {
83
88
  buildOpenCodePromptParts: typeof buildOpenCodePromptParts;
89
+ buildOpenCodeSessionTimeline: typeof buildOpenCodeSessionTimeline;
84
90
  buildOpenCodeModelContextWindowLookup: typeof buildOpenCodeModelContextWindowLookup;
85
91
  buildOpenCodeModelDefinition: typeof buildOpenCodeModelDefinition;
86
92
  buildOpenCodeModelLookupKey: typeof buildOpenCodeModelLookupKey;
@@ -133,6 +139,11 @@ export interface OpenCodeEventTranslationState {
133
139
  sessionTotalCostUsd?: number;
134
140
  streamedPartKeys: Set<string>;
135
141
  emittedStructuredMessageIds: Set<string>;
142
+ compactionSummaryMessageIds: Set<string>;
143
+ emittedCompactionPartIds: Set<string>;
144
+ suppressAssistantMessagesUntilIdle?: {
145
+ active: boolean;
146
+ };
136
147
  /** Tracks the type of each part by ID, learned from message.part.updated events. */
137
148
  partTypes: Map<string, string>;
138
149
  subAgentsByCallId?: Map<string, OpenCodeSubAgentActivityState>;
@@ -186,6 +197,9 @@ declare class OpenCodeAgentSession implements AgentSession {
186
197
  private streamedPartKeys;
187
198
  /** Tracks assistant messages already emitted from structured payloads. */
188
199
  private emittedStructuredMessageIds;
200
+ private compactionSummaryMessageIds;
201
+ private emittedCompactionPartIds;
202
+ private suppressAssistantMessagesUntilIdle;
189
203
  /** Tracks the type of each part by ID, learned from message.part.updated events. */
190
204
  private partTypes;
191
205
  private availableModesCache;
@@ -728,6 +728,13 @@ function buildOpenCodeReplayPartTimelineEvent(params) {
728
728
  part,
729
729
  });
730
730
  }
731
+ function isOpenCodeCompactionSummaryMessage(message) {
732
+ return (message.role === "assistant" &&
733
+ (message.summary === true || message.agent === "compaction" || message.mode === "compaction"));
734
+ }
735
+ function findOpenCodeCompactionPart(message) {
736
+ return message.parts.find((part) => part.type === "compaction");
737
+ }
731
738
  async function readOpenCodeSessionMessagesFromSdk(client, session) {
732
739
  const response = await client.session.messages({
733
740
  sessionID: session.id,
@@ -739,7 +746,24 @@ async function readOpenCodeSessionMessagesFromSdk(client, session) {
739
746
  return filterOpenCodeRevertedMessages(response.data, session.revert);
740
747
  }
741
748
  function buildOpenCodeSessionTimeline(messages) {
742
- return messages.flatMap((message) => buildOpenCodeReplayTimelineEvents(message).map((event) => event.item));
749
+ const timeline = [];
750
+ let hideNextAssistantAfterCompaction = false;
751
+ for (const message of messages) {
752
+ const compactionPart = findOpenCodeCompactionPart(message);
753
+ if (message.info.role === "assistant" && hideNextAssistantAfterCompaction) {
754
+ hideNextAssistantAfterCompaction = false;
755
+ continue;
756
+ }
757
+ if (message.info.role === "user" && !compactionPart) {
758
+ hideNextAssistantAfterCompaction = false;
759
+ }
760
+ timeline.push(...buildOpenCodeReplayTimelineEvents(message).map((event) => event.item));
761
+ if (message.info.role === "user" && compactionPart) {
762
+ timeline.push(createCompactionTimelineItem("completed", compactionPart.auto ? "auto" : "manual"));
763
+ hideNextAssistantAfterCompaction = true;
764
+ }
765
+ }
766
+ return timeline;
743
767
  }
744
768
  function filterOpenCodeRevertedMessages(messages, revert) {
745
769
  if (!revert?.messageID || revert.partID) {
@@ -778,6 +802,9 @@ function readOpenCodeMessageModel(message) {
778
802
  }
779
803
  function buildOpenCodeReplayTimelineEvents(message) {
780
804
  const { info, parts } = message;
805
+ if (isOpenCodeCompactionSummaryMessage(info)) {
806
+ return [];
807
+ }
781
808
  if (info.role === "user") {
782
809
  const text = parts
783
810
  .filter((part) => part.type === "text")
@@ -816,6 +843,7 @@ function buildOpenCodeReplayTimelineEvents(message) {
816
843
  }
817
844
  export const __openCodeInternals = {
818
845
  buildOpenCodePromptParts,
846
+ buildOpenCodeSessionTimeline,
819
847
  buildOpenCodeModelContextWindowLookup,
820
848
  buildOpenCodeModelDefinition,
821
849
  buildOpenCodeModelLookupKey,
@@ -1370,6 +1398,11 @@ export function translateOpenCodeEvent(event, state) {
1370
1398
  function resetOpenCodeTurnTrackingState(state) {
1371
1399
  state.streamedPartKeys.clear();
1372
1400
  state.partTypes.clear();
1401
+ state.compactionSummaryMessageIds.clear();
1402
+ state.emittedCompactionPartIds.clear();
1403
+ if (state.suppressAssistantMessagesUntilIdle) {
1404
+ state.suppressAssistantMessagesUntilIdle.active = false;
1405
+ }
1373
1406
  }
1374
1407
  function getOpenCodeSubAgentMaps(state) {
1375
1408
  state.subAgentsByCallId ?? (state.subAgentsByCallId = new Map());
@@ -1592,6 +1625,14 @@ function appendOpenCodeMessageUpdated(event, state, events) {
1592
1625
  if (info.role !== "assistant") {
1593
1626
  return;
1594
1627
  }
1628
+ if (state.suppressAssistantMessagesUntilIdle?.active) {
1629
+ state.compactionSummaryMessageIds.add(info.id);
1630
+ return;
1631
+ }
1632
+ if (isOpenCodeCompactionSummaryMessage(info)) {
1633
+ state.compactionSummaryMessageIds.add(info.id);
1634
+ return;
1635
+ }
1595
1636
  const modelLookupKey = resolveOpenCodeModelLookupKeyFromAssistantMessage(info);
1596
1637
  if (modelLookupKey) {
1597
1638
  const contextWindowMaxTokens = state.modelContextWindowsByModelKey?.get(modelLookupKey);
@@ -1638,6 +1679,13 @@ function appendOpenCodeMessagePartUpdated(event, state, events) {
1638
1679
  }
1639
1680
  const messageRole = state.messageRoles.get(part.messageID);
1640
1681
  state.partTypes.set(part.id, part.type);
1682
+ if (state.compactionSummaryMessageIds.has(part.messageID)) {
1683
+ return;
1684
+ }
1685
+ if (shouldSuppressOpenCodeAssistantPart(part, messageRole, state)) {
1686
+ state.compactionSummaryMessageIds.add(part.messageID);
1687
+ return;
1688
+ }
1641
1689
  if (part.type === "text") {
1642
1690
  appendOpenCodeTextPart(part, messageRole, state, events);
1643
1691
  return;
@@ -1654,6 +1702,10 @@ function appendOpenCodeMessagePartUpdated(event, state, events) {
1654
1702
  return;
1655
1703
  }
1656
1704
  if (part.type === "compaction") {
1705
+ if (state.emittedCompactionPartIds.has(part.id)) {
1706
+ return;
1707
+ }
1708
+ state.emittedCompactionPartIds.add(part.id);
1657
1709
  events.push({
1658
1710
  type: "timeline",
1659
1711
  provider: "opencode",
@@ -1678,6 +1730,11 @@ function appendOpenCodeMessagePartUpdated(event, state, events) {
1678
1730
  }
1679
1731
  }
1680
1732
  }
1733
+ function shouldSuppressOpenCodeAssistantPart(part, messageRole, state) {
1734
+ return (state.suppressAssistantMessagesUntilIdle?.active === true &&
1735
+ part.type === "text" &&
1736
+ messageRole !== "user");
1737
+ }
1681
1738
  function appendOpenCodeTextPart(part, messageRole, state, events) {
1682
1739
  if (messageRole === "user") {
1683
1740
  return;
@@ -1724,6 +1781,9 @@ function appendOpenCodeMessagePartDelta(event, state, events) {
1724
1781
  const messageRole = messageID ? state.messageRoles.get(messageID) : undefined;
1725
1782
  const knownPartType = partID ? state.partTypes.get(partID) : undefined;
1726
1783
  const isReasoning = knownPartType === "reasoning" || field === "reasoning";
1784
+ if (messageID && state.compactionSummaryMessageIds.has(messageID)) {
1785
+ return;
1786
+ }
1727
1787
  if (isReasoning) {
1728
1788
  if (partID) {
1729
1789
  state.streamedPartKeys.add(`reasoning:${partID}`);
@@ -1741,6 +1801,10 @@ function appendOpenCodeMessagePartDelta(event, state, events) {
1741
1801
  if (messageRole === "user") {
1742
1802
  return;
1743
1803
  }
1804
+ if (messageID && state.suppressAssistantMessagesUntilIdle?.active === true) {
1805
+ state.compactionSummaryMessageIds.add(messageID);
1806
+ return;
1807
+ }
1744
1808
  if (partID) {
1745
1809
  state.streamedPartKeys.add(`text:${partID}`);
1746
1810
  }
@@ -1938,6 +2002,9 @@ class OpenCodeAgentSession {
1938
2002
  this.streamedPartKeys = new Set();
1939
2003
  /** Tracks assistant messages already emitted from structured payloads. */
1940
2004
  this.emittedStructuredMessageIds = new Set();
2005
+ this.compactionSummaryMessageIds = new Set();
2006
+ this.emittedCompactionPartIds = new Set();
2007
+ this.suppressAssistantMessagesUntilIdle = { active: false };
1941
2008
  /** Tracks the type of each part by ID, learned from message.part.updated events. */
1942
2009
  this.partTypes = new Map();
1943
2010
  this.availableModesCache = null;
@@ -2069,6 +2136,7 @@ class OpenCodeAgentSession {
2069
2136
  this.accumulatedUsage = contextWindowMaxTokens !== undefined ? { contextWindowMaxTokens } : {};
2070
2137
  const parts = buildOpenCodePromptParts(prompt);
2071
2138
  this.pendingUserMessageText = buildOpenCodeUserTimelineText(prompt);
2139
+ this.suppressAssistantMessagesUntilIdle.active = false;
2072
2140
  const model = this.parseModel(this.config.model);
2073
2141
  const thinkingOptionId = this.config.thinkingOptionId;
2074
2142
  const effectiveVariant = thinkingOptionId ?? undefined;
@@ -2088,6 +2156,7 @@ class OpenCodeAgentSession {
2088
2156
  const slashCommand = await this.resolveSlashCommandInvocation(prompt);
2089
2157
  if (slashCommand) {
2090
2158
  if (slashCommand.commandName === "compact" || slashCommand.commandName === "summarize") {
2159
+ this.suppressAssistantMessagesUntilIdle.active = true;
2091
2160
  void this.client.session
2092
2161
  .summarize({
2093
2162
  sessionID: this.sessionId,
@@ -2096,18 +2165,17 @@ class OpenCodeAgentSession {
2096
2165
  })
2097
2166
  .then((response) => {
2098
2167
  if (response.error) {
2168
+ this.suppressAssistantMessagesUntilIdle.active = false;
2099
2169
  this.finishForegroundTurn({
2100
2170
  type: "turn_failed",
2101
2171
  provider: "opencode",
2102
2172
  error: toDiagnosticErrorMessage(response.error),
2103
2173
  }, turnId);
2104
2174
  }
2105
- else {
2106
- this.finishForegroundTurn({ type: "turn_completed", provider: "opencode", usage: undefined }, turnId);
2107
- }
2108
2175
  return;
2109
2176
  })
2110
2177
  .catch((error) => {
2178
+ this.suppressAssistantMessagesUntilIdle.active = false;
2111
2179
  this.finishForegroundTurn({
2112
2180
  type: "turn_failed",
2113
2181
  provider: "opencode",
@@ -2710,6 +2778,9 @@ class OpenCodeAgentSession {
2710
2778
  sessionTotalCostUsd: this.sessionTotalCostUsd,
2711
2779
  streamedPartKeys: this.streamedPartKeys,
2712
2780
  emittedStructuredMessageIds: this.emittedStructuredMessageIds,
2781
+ compactionSummaryMessageIds: this.compactionSummaryMessageIds,
2782
+ emittedCompactionPartIds: this.emittedCompactionPartIds,
2783
+ suppressAssistantMessagesUntilIdle: this.suppressAssistantMessagesUntilIdle,
2713
2784
  partTypes: this.partTypes,
2714
2785
  subAgentsByCallId: this.subAgentsByCallId,
2715
2786
  subAgentCallIdByChildSessionId: this.subAgentCallIdByChildSessionId,