@love-moon/ai-sdk 0.13.1 → 0.13.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,53 @@
1
1
  # @love-moon/ai-sdk
2
2
 
3
+ ## 0.13.3
4
+
5
+ ### Patch Changes
6
+
7
+ - 0859251: Show Codex progress notes as soon as Codex finishes writing them. The session
8
+ did not recognize the app-server's `agentMessage` items, so a commentary message
9
+ was held back until the next message started or the turn ended, often minutes
10
+ later during long tool runs.
11
+ - c1b66d1: Fix Codex tasks that stopped mid-reply after the agent spawned sub-agents. In
12
+ multi-agent mode the Codex app-server also streams the sub-agent threads, and
13
+ the first sub-agent to finish ended the parent turn. The parent's later
14
+ messages and final answer were dropped, and a sub-agent's report could show up
15
+ as the AI reply. The session now ignores notifications from other threads.
16
+ - f6dbd6b: Show completed Codex replies while subsequent tools are still running, and keep replies intact when their final text and completion arrive together.
17
+
18
+ ## 0.13.2
19
+
20
+ ### Patch Changes
21
+
22
+ - 8c2833d: Support `/compact [focus instructions]` in task chats. Fire detects the command
23
+ per message (like `/goal`), runs the backend's native context compaction
24
+ instead of sending the text to the model, and posts one confirmation with the
25
+ token savings. Sessions advertise `capabilities.compact` and implement the new
26
+ optional `runCompact()`: claude (native `/compact`), codex app-server
27
+ (`thread/compact/start`), copilot (`session.history.compact`), kimi wire and
28
+ legacy print (built-in `/compact`), opencode (`session.summarize`) and dsh
29
+ (summary turn, then a fresh session seeded with the summary). Backends without
30
+ the capability (chat-web, codex exec, Kimi Code prompt mode) reply with a
31
+ not-supported notice. Opencode compaction summaries no longer surface as chat
32
+ replies, and dsh clears its "compacting context" status when automatic
33
+ compaction ends.
34
+ - 8e3a4c3: Report how many model tokens each turn consumed. After every turn (or `/goal`)
35
+ `conductor fire` sends the turn's token count (fresh input + cache reads/writes +
36
+ output) over the new `task_turn_usage` websocket event, and the server adds it
37
+ to the task's running total shown in the task detail card. A persistent task's
38
+ total restarts with each round.
39
+
40
+ Failed or interrupted turns and `/compact` are counted too; when a turn's usage
41
+ is unknown the fire reports `null`, which clears the task's last-turn count.
42
+
43
+ - `@love-moon/conductor-sdk`: new `ConductorClient.sendTurnUsage(taskId, { tokens })`.
44
+ - `@love-moon/ai-sdk`: the Codex app-server provider's turn/goal `usage` now
45
+ carries `turnTotalTokens`, the turn's share of Codex's thread-cumulative total.
46
+ Claude and Codex turn errors carry the tokens spent before the failure as
47
+ `error.usage` (Claude sums streamed usage when an interrupted query ends
48
+ without a result).
49
+ - Claude and Codex backends are counted; other backends report nothing yet.
50
+
3
51
  ## 0.13.1
4
52
 
5
53
  ## 0.13.0
package/dist/client.d.ts CHANGED
@@ -18,6 +18,7 @@ export class RemoteAiSession extends EventEmitter<[never]> {
18
18
  workerReady: boolean;
19
19
  capabilities: {
20
20
  goal?: boolean | undefined;
21
+ compact?: boolean | undefined;
21
22
  };
22
23
  };
23
24
  sessionMessageHandler: any;
@@ -51,6 +52,7 @@ export class RemoteAiSession extends EventEmitter<[never]> {
51
52
  workerReady: boolean;
52
53
  capabilities: {
53
54
  goal?: boolean | undefined;
55
+ compact?: boolean | undefined;
54
56
  };
55
57
  };
56
58
  usesSessionFileReplyStream(): boolean;
@@ -69,6 +71,7 @@ export class RemoteAiSession extends EventEmitter<[never]> {
69
71
  interruptCurrentTurn(): Promise<any>;
70
72
  runTurn(promptText: any, options?: {}): Promise<any>;
71
73
  runGoal(goal: any, options?: {}): Promise<any>;
74
+ runCompact(request?: {}, options?: {}): Promise<any>;
72
75
  getGoal(): Promise<any>;
73
76
  clearGoal(): Promise<any>;
74
77
  close(): Promise<void>;
@@ -108,6 +111,7 @@ declare class LocalAiSessionProxy extends EventEmitter<[never]> {
108
111
  workerReady: boolean;
109
112
  capabilities: {
110
113
  goal?: boolean | undefined;
114
+ compact?: boolean | undefined;
111
115
  };
112
116
  };
113
117
  readyPromise: Promise<any>;
@@ -126,6 +130,7 @@ declare class LocalAiSessionProxy extends EventEmitter<[never]> {
126
130
  interruptCurrentTurn(): Promise<any>;
127
131
  runTurn(promptText: any, options?: {}): Promise<any>;
128
132
  runGoal(goal: any, options?: {}): Promise<any>;
133
+ runCompact(request?: {}, options?: {}): Promise<any>;
129
134
  getGoal(): Promise<any>;
130
135
  clearGoal(): Promise<any>;
131
136
  close(): Promise<void>;
package/dist/client.js CHANGED
@@ -9,6 +9,11 @@ function createUnsupportedGoalError(name) {
9
9
  err.reason = "unsupported_goal";
10
10
  return err;
11
11
  }
12
+ function createUnsupportedCompactError(name) {
13
+ const err = new Error(`runCompact() is not supported by backend session (${name || "unknown"}).`);
14
+ err.reason = "unsupported_compact";
15
+ return err;
16
+ }
12
17
  const WORKER_PATH = fileURLToPath(new URL("./worker.js", import.meta.url));
13
18
  function sanitizeOptionsForWorker(options = {}) {
14
19
  const sanitized = {};
@@ -217,6 +222,16 @@ export class RemoteAiSession extends EventEmitter {
217
222
  progressHandler: typeof onProgress === "function" ? onProgress : null,
218
223
  });
219
224
  }
225
+ async runCompact(request = {}, options = {}) {
226
+ await this.readyPromise;
227
+ if (!this.snapshot?.capabilities || this.snapshot.capabilities.compact !== true) {
228
+ throw createUnsupportedCompactError(this.snapshot?.provider || this.snapshot?.backend);
229
+ }
230
+ const { onProgress, ...restOptions } = options || {};
231
+ return this.callWorker("runCompact", [request, restOptions], {
232
+ progressHandler: typeof onProgress === "function" ? onProgress : null,
233
+ });
234
+ }
220
235
  async getGoal() {
221
236
  // Propagate worker-creation errors instead of masking them as "no goal".
222
237
  // See N3 in the review.
@@ -675,6 +690,15 @@ class LocalAiSessionProxy extends EventEmitter {
675
690
  }
676
691
  return await session.runGoal(goal, options);
677
692
  }
693
+ async runCompact(request = {}, options = {}) {
694
+ const session = await this.readyPromise;
695
+ // Capabilities resolved at init (snapshot first) are authoritative; some
696
+ // providers only advertise them via getSnapshot().
697
+ if (this.snapshot?.capabilities?.compact !== true || typeof session.runCompact !== "function") {
698
+ throw createUnsupportedCompactError(session?.constructor?.name);
699
+ }
700
+ return await session.runCompact(request, options);
701
+ }
678
702
  async getGoal() {
679
703
  const session = await this.readyPromise;
680
704
  const capabilities = resolveSessionCapabilities(session);
@@ -9,6 +9,7 @@ export function resolveClaudeCommandForRoot(commandLine: any, env?: NodeJS.Proce
9
9
  export class ClaudeAgentSdkSession extends EventEmitter<[never]> {
10
10
  static capabilities: Readonly<{
11
11
  goal: true;
12
+ compact: true;
12
13
  media: Readonly<{
13
14
  image: "native";
14
15
  }>;
@@ -16,6 +17,7 @@ export class ClaudeAgentSdkSession extends EventEmitter<[never]> {
16
17
  constructor(backend: any, options?: {});
17
18
  getCapabilities(): {
18
19
  goal: true;
20
+ compact: true;
19
21
  media: Readonly<{
20
22
  image: "native";
21
23
  }>;
@@ -42,12 +44,16 @@ export class ClaudeAgentSdkSession extends EventEmitter<[never]> {
42
44
  manualResumeReady: boolean;
43
45
  currentTurn: {
44
46
  abortController: AbortController;
47
+ suppressReply: boolean;
48
+ compactMetadata: null;
49
+ compactError: string;
45
50
  emittedAssistantMessage: boolean;
46
51
  fullText: string;
47
52
  items: never[];
48
53
  query: null;
49
54
  resultMessage: null;
50
55
  terminalWorkingStatusEmitted: boolean;
56
+ usageByMessageId: Map<any, any>;
51
57
  } | null;
52
58
  lastResult: any;
53
59
  rateLimitInfo: any;
@@ -88,6 +94,7 @@ export class ClaudeAgentSdkSession extends EventEmitter<[never]> {
88
94
  currentTurnStatus: any;
89
95
  capabilities: {
90
96
  goal: true;
97
+ compact: true;
91
98
  media: Readonly<{
92
99
  image: "native";
93
100
  }>;
@@ -154,10 +161,11 @@ export class ClaudeAgentSdkSession extends EventEmitter<[never]> {
154
161
  onProgress: any;
155
162
  }): Promise<void>;
156
163
  interruptCurrentTurn(): Promise<boolean>;
157
- runTurn(promptText: any, { useInitialImages, media: mediaInput, contextFiles, onProgress, jsonSchema }?: {
164
+ runTurn(promptText: any, { useInitialImages, media: mediaInput, contextFiles, onProgress, jsonSchema, suppressReply }?: {
158
165
  useInitialImages?: boolean | undefined;
159
166
  onProgress?: null | undefined;
160
167
  jsonSchema?: null | undefined;
168
+ suppressReply?: boolean | undefined;
161
169
  }): Promise<{
162
170
  text: string;
163
171
  usage: null;
@@ -165,6 +173,8 @@ export class ClaudeAgentSdkSession extends EventEmitter<[never]> {
165
173
  events: never[];
166
174
  provider?: undefined;
167
175
  metadata?: undefined;
176
+ compactMetadata?: undefined;
177
+ compactError?: undefined;
168
178
  } | {
169
179
  text: any;
170
180
  usage: any;
@@ -178,6 +188,8 @@ export class ClaudeAgentSdkSession extends EventEmitter<[never]> {
178
188
  modelUsage: any;
179
189
  structuredOutput: any;
180
190
  };
191
+ compactMetadata: undefined;
192
+ compactError: string | undefined;
181
193
  }>;
182
194
  /**
183
195
  * Trigger Claude's native `/goal` slash command. Implemented by sending the
@@ -204,6 +216,18 @@ export class ClaudeAgentSdkSession extends EventEmitter<[never]> {
204
216
  * @returns {Promise<import("../shared.js").GoalResult>}
205
217
  */
206
218
  runGoal(goal: import("../shared.js").GoalRequest, options?: object): Promise<import("../shared.js").GoalResult>;
219
+ /**
220
+ * Manually compact the conversation via Claude's native `/compact` slash
221
+ * command. Custom focus instructions are forwarded as the command argument.
222
+ * No assistant message is emitted; the caller renders the confirmation.
223
+ *
224
+ * @param {import("../shared.js").CompactRequest} [request]
225
+ * @param {{ onProgress?: Function }} [options]
226
+ * @returns {Promise<import("../shared.js").CompactResult>}
227
+ */
228
+ runCompact(request?: import("../shared.js").CompactRequest, { onProgress }?: {
229
+ onProgress?: Function;
230
+ }): Promise<import("../shared.js").CompactResult>;
207
231
  close(): Promise<void>;
208
232
  }
209
233
  import { EventEmitter } from "node:events";
@@ -32,6 +32,22 @@ function createTurnError(message, extras = {}) {
32
32
  }
33
33
  return error;
34
34
  }
35
+ /** Per-turn usage summed from streamed assistant messages (one entry per API response). */
36
+ function sumStreamedUsage(usageByMessageId) {
37
+ if (!usageByMessageId.size) {
38
+ return null;
39
+ }
40
+ const total = {};
41
+ for (const usage of usageByMessageId.values()) {
42
+ for (const key of ["input_tokens", "cache_creation_input_tokens", "cache_read_input_tokens", "output_tokens"]) {
43
+ const value = Number(usage?.[key]);
44
+ if (Number.isFinite(value)) {
45
+ total[key] = (total[key] || 0) + value;
46
+ }
47
+ }
48
+ }
49
+ return total;
50
+ }
35
51
  function normalizeClaudeBackend(backend) {
36
52
  const normalized = String(backend || "").trim().toLowerCase();
37
53
  if (normalized === "claude-code") {
@@ -214,9 +230,10 @@ function sanitizeSummary(value, maxLen = 180) {
214
230
  export class ClaudeAgentSdkSession extends EventEmitter {
215
231
  // Capability advertised via getSnapshot().capabilities so worker proxies
216
232
  // can short-circuit runGoal without an IPC round trip. Claude exposes
217
- // native `/goal` slash command, so this is true.
233
+ // native `/goal` and `/compact` slash commands, so both are true.
218
234
  static capabilities = Object.freeze({
219
235
  goal: true,
236
+ compact: true,
220
237
  media: PROVIDER_MEDIA_CAPABILITIES[CLAUDE_PROVIDER_VARIANT],
221
238
  });
222
239
  getCapabilities() {
@@ -720,6 +737,16 @@ export class ClaudeAgentSdkSession extends EventEmitter {
720
737
  this.updateSessionInfo(message.session_id || message.sessionId);
721
738
  return;
722
739
  }
740
+ if (message.subtype === "compact_boundary") {
741
+ currentTurn.compactMetadata =
742
+ message.compact_metadata && typeof message.compact_metadata === "object"
743
+ ? { ...message.compact_metadata }
744
+ : {};
745
+ return;
746
+ }
747
+ if (message.subtype === "status" && message.compact_result === "failed") {
748
+ currentTurn.compactError = normalizeText(message.compact_error) || "compaction failed";
749
+ }
723
750
  if (message.subtype === "status" && message.status === "compacting") {
724
751
  await this.emitWorkingStatus({
725
752
  phase: "context_compaction",
@@ -775,6 +802,9 @@ export class ClaudeAgentSdkSession extends EventEmitter {
775
802
  }
776
803
  case "assistant": {
777
804
  this.updateSessionInfo(message.session_id || message.sessionId);
805
+ if (message.message?.id && message.message.usage) {
806
+ currentTurn.usageByMessageId.set(message.message.id, message.message.usage);
807
+ }
778
808
  for (const block of contentBlocks(message.message)) {
779
809
  if (block?.type === "tool_use") {
780
810
  noteToolStarted(currentTurn, block.id, block.name, block.input);
@@ -784,8 +814,11 @@ export class ClaudeAgentSdkSession extends EventEmitter {
784
814
  if (!text) {
785
815
  return;
786
816
  }
787
- currentTurn.emittedAssistantMessage = true;
788
817
  currentTurn.fullText = text;
818
+ if (currentTurn.suppressReply) {
819
+ return;
820
+ }
821
+ currentTurn.emittedAssistantMessage = true;
789
822
  await this.emitWorkingStatus({
790
823
  phase: "message_aggregation",
791
824
  reply_in_progress: true,
@@ -843,7 +876,7 @@ export class ClaudeAgentSdkSession extends EventEmitter {
843
876
  }
844
877
  return true;
845
878
  }
846
- async runTurn(promptText, { useInitialImages = false, media: mediaInput, contextFiles, onProgress = null, jsonSchema = null } = {}) {
879
+ async runTurn(promptText, { useInitialImages = false, media: mediaInput, contextFiles, onProgress = null, jsonSchema = null, suppressReply = false } = {}) {
847
880
  if (this.closeRequested) {
848
881
  throw this.createSessionClosedError();
849
882
  }
@@ -869,16 +902,22 @@ export class ClaudeAgentSdkSession extends EventEmitter {
869
902
  if (!sdkModule || typeof sdkModule.query !== "function") {
870
903
  throw new Error("Claude Agent SDK is unavailable");
871
904
  }
872
- this.history.push({ role: "user", content: promptText });
905
+ if (!suppressReply) {
906
+ this.history.push({ role: "user", content: promptText });
907
+ }
873
908
  const abortController = new AbortController();
874
909
  const currentTurn = {
875
910
  abortController,
911
+ suppressReply,
912
+ compactMetadata: null,
913
+ compactError: "",
876
914
  emittedAssistantMessage: false,
877
915
  fullText: "",
878
916
  items: [],
879
917
  query: null,
880
918
  resultMessage: null,
881
919
  terminalWorkingStatusEmitted: false,
920
+ usageByMessageId: new Map(),
882
921
  };
883
922
  this.currentTurn = currentTurn;
884
923
  this.markTurnStartedStatus();
@@ -934,6 +973,8 @@ export class ClaudeAgentSdkSession extends EventEmitter {
934
973
  permissionDenials: Array.isArray(resultMessage.permission_denials)
935
974
  ? [...resultMessage.permission_denials]
936
975
  : [],
976
+ // A failed turn still spent tokens.
977
+ usage: resultMessage.usage ? { ...resultMessage.usage } : null,
937
978
  });
938
979
  }
939
980
  const structuredOutput = jsonSchema && resultMessage.structured_output && typeof resultMessage.structured_output === "object"
@@ -944,10 +985,10 @@ export class ClaudeAgentSdkSession extends EventEmitter {
944
985
  : normalizeText(resultMessage.result) ||
945
986
  currentTurn.fullText ||
946
987
  extractAssistantText(currentTurn.items.find((item) => item?.type === "assistant")?.message);
947
- if (!currentTurn.emittedAssistantMessage && responseText) {
988
+ if (!suppressReply && !currentTurn.emittedAssistantMessage && responseText) {
948
989
  await this.emitAssistantMessage(responseText);
949
990
  }
950
- if (responseText) {
991
+ if (!suppressReply && responseText) {
951
992
  this.history.push({ role: "assistant", content: responseText });
952
993
  }
953
994
  this.manualResumeReady = Boolean(this.sessionId);
@@ -971,9 +1012,14 @@ export class ClaudeAgentSdkSession extends EventEmitter {
971
1012
  modelUsage: resultMessage.modelUsage ? { ...resultMessage.modelUsage } : undefined,
972
1013
  structuredOutput: structuredOutput !== null ? structuredOutput : undefined,
973
1014
  },
1015
+ compactMetadata: currentTurn.compactMetadata || undefined,
1016
+ compactError: currentTurn.compactError || undefined,
974
1017
  };
975
1018
  }
976
1019
  catch (error) {
1020
+ // A failed turn still spent tokens; an interrupted query ends without a
1021
+ // result, so fall back to the usage streamed so far.
1022
+ const usage = error?.usage ?? sumStreamedUsage(currentTurn.usageByMessageId);
977
1023
  if (error?.reason === "turn_timeout") {
978
1024
  await this.interruptCurrentTurn();
979
1025
  }
@@ -985,9 +1031,12 @@ export class ClaudeAgentSdkSession extends EventEmitter {
985
1031
  }, onProgress);
986
1032
  }
987
1033
  if (this.closeRequested && error?.reason !== "session_closed") {
988
- throw this.createSessionClosedError();
1034
+ throw Object.assign(this.createSessionClosedError(), { usage });
989
1035
  }
990
1036
  this.maybeEmitAuthRequired(error?.message || "", error?.message || "");
1037
+ if (error && typeof error === "object") {
1038
+ error.usage = usage;
1039
+ }
991
1040
  throw error;
992
1041
  }
993
1042
  finally {
@@ -1113,6 +1162,46 @@ export class ClaudeAgentSdkSession extends EventEmitter {
1113
1162
  },
1114
1163
  };
1115
1164
  }
1165
+ /**
1166
+ * Manually compact the conversation via Claude's native `/compact` slash
1167
+ * command. Custom focus instructions are forwarded as the command argument.
1168
+ * No assistant message is emitted; the caller renders the confirmation.
1169
+ *
1170
+ * @param {import("../shared.js").CompactRequest} [request]
1171
+ * @param {{ onProgress?: Function }} [options]
1172
+ * @returns {Promise<import("../shared.js").CompactResult>}
1173
+ */
1174
+ async runCompact(request = {}, { onProgress = null } = {}) {
1175
+ const instructions = normalizeText(request?.instructions).trim();
1176
+ if (!this.sessionId || this.pendingHistorySeed) {
1177
+ return { compact: { status: "noop", instructionsApplied: false }, usage: null, metadata: {} };
1178
+ }
1179
+ const turnResult = await this.runTurn(instructions ? `/compact ${instructions}` : "/compact", {
1180
+ onProgress,
1181
+ suppressReply: true,
1182
+ });
1183
+ if (/not enough messages/i.test(turnResult.compactError || "")) {
1184
+ return { compact: { status: "noop", instructionsApplied: false }, usage: turnResult.usage, metadata: turnResult.metadata };
1185
+ }
1186
+ if (turnResult.compactError) {
1187
+ throw createTurnError(`Claude compaction failed: ${turnResult.compactError}`, {
1188
+ reason: "compact_failed",
1189
+ });
1190
+ }
1191
+ const boundary = turnResult.compactMetadata;
1192
+ const preTokens = Number(boundary?.pre_tokens);
1193
+ const postTokens = Number(boundary?.post_tokens);
1194
+ return {
1195
+ compact: {
1196
+ status: boundary ? "compacted" : "noop",
1197
+ instructionsApplied: Boolean(boundary && instructions),
1198
+ preTokens: Number.isFinite(preTokens) ? preTokens : undefined,
1199
+ postTokens: Number.isFinite(postTokens) ? postTokens : undefined,
1200
+ },
1201
+ usage: turnResult.usage,
1202
+ metadata: turnResult.metadata,
1203
+ };
1204
+ }
1116
1205
  async close() {
1117
1206
  if (this.closed) {
1118
1207
  return;
@@ -31,6 +31,8 @@ export class CodexAppServerSession extends EventEmitter<[never]> {
31
31
  nativeSessionId: string;
32
32
  rateLimits: any;
33
33
  tokenUsage: any;
34
+ turnTokenBaseline: number | null;
35
+ turnTokensSeen: boolean;
34
36
  currentTurnStatus: any;
35
37
  currentTurnActivityAt: number;
36
38
  now: any;
@@ -42,6 +44,14 @@ export class CodexAppServerSession extends EventEmitter<[never]> {
42
44
  activeAssistantMessageText: string;
43
45
  resolve: null;
44
46
  reject: null;
47
+ } | {
48
+ turnId: string;
49
+ fullText: string;
50
+ activeAssistantMessageId: string;
51
+ activeAssistantMessageText: string;
52
+ resolve: null;
53
+ reject: null;
54
+ suppressReply: boolean;
45
55
  } | {
46
56
  turnId: string;
47
57
  fullText: string;
@@ -127,6 +137,15 @@ export class CodexAppServerSession extends EventEmitter<[never]> {
127
137
  resumeReady?: boolean | undefined;
128
138
  }): void;
129
139
  applySessionConfigured(params: any): void;
140
+ /** Start counting a turn's tokens from the thread total known right now. */
141
+ beginTurnTokenAccounting(): void;
142
+ /**
143
+ * Thread token usage plus `turnTotalTokens`: tokens spent since the current
144
+ * turn (or goal) started. Codex only reports thread-cumulative totals, and
145
+ * only when a response completes, so a turn that ended before any response
146
+ * did has unknown usage.
147
+ */
148
+ snapshotTokenUsage(): any;
130
149
  resolveContextUsagePercent(): number | undefined;
131
150
  /**
132
151
  * Whether a turn failure was caused by the provider thread outgrowing the
@@ -175,6 +194,14 @@ export class CodexAppServerSession extends EventEmitter<[never]> {
175
194
  activeAssistantMessageText: string;
176
195
  resolve: null;
177
196
  reject: null;
197
+ } | {
198
+ turnId: string;
199
+ fullText: string;
200
+ activeAssistantMessageId: string;
201
+ activeAssistantMessageText: string;
202
+ resolve: null;
203
+ reject: null;
204
+ suppressReply: boolean;
178
205
  } | {
179
206
  turnId: string;
180
207
  fullText: string;
@@ -207,6 +234,14 @@ export class CodexAppServerSession extends EventEmitter<[never]> {
207
234
  activeAssistantMessageText: string;
208
235
  resolve: null;
209
236
  reject: null;
237
+ } | {
238
+ turnId: string;
239
+ fullText: string;
240
+ activeAssistantMessageId: string;
241
+ activeAssistantMessageText: string;
242
+ resolve: null;
243
+ reject: null;
244
+ suppressReply: boolean;
210
245
  } | {
211
246
  turnId: string;
212
247
  fullText: string;
@@ -244,6 +279,15 @@ export class CodexAppServerSession extends EventEmitter<[never]> {
244
279
  useInitialImages?: boolean | undefined;
245
280
  jsonSchema?: null | undefined;
246
281
  }): any;
282
+ /**
283
+ * Manually compact the thread via `thread/compact/start`. The app-server
284
+ * runs compaction as a regular turn, so completion is the usual
285
+ * `turn/completed`. The protocol takes no focus instructions.
286
+ *
287
+ * @param {import("../shared.js").CompactRequest} [request]
288
+ * @returns {Promise<import("../shared.js").CompactResult>}
289
+ */
290
+ runCompact(request?: import("../shared.js").CompactRequest): Promise<import("../shared.js").CompactResult>;
247
291
  /**
248
292
  * Normalize a goal payload from a codex `thread/goal/*` notification into
249
293
  * the cross-layer {@link GoalState} shape.