@linkshell/host 0.4.17 → 0.4.18

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 (39) hide show
  1. package/README.md +27 -1
  2. package/dist/host/src/drivers/acp/connection.d.ts +1 -0
  3. package/dist/host/src/drivers/acp/connection.js +6 -0
  4. package/dist/host/src/drivers/acp/connection.js.map +1 -1
  5. package/dist/host/src/drivers/acp/driver.d.ts +10 -5
  6. package/dist/host/src/drivers/acp/driver.js +51 -25
  7. package/dist/host/src/drivers/acp/driver.js.map +1 -1
  8. package/dist/host/src/drivers/acp/questions.d.ts +14 -0
  9. package/dist/host/src/drivers/acp/questions.js +114 -0
  10. package/dist/host/src/drivers/acp/questions.js.map +1 -0
  11. package/dist/host/src/drivers/claude/tasks.d.ts +2 -2
  12. package/dist/host/src/drivers/claude/transcript.d.ts +76 -69
  13. package/dist/host/src/drivers/codex/attention.d.ts +7 -0
  14. package/dist/host/src/drivers/codex/attention.js +82 -0
  15. package/dist/host/src/drivers/codex/attention.js.map +1 -0
  16. package/dist/host/src/drivers/codex/driver.d.ts +2 -0
  17. package/dist/host/src/drivers/codex/driver.js +45 -2
  18. package/dist/host/src/drivers/codex/driver.js.map +1 -1
  19. package/dist/host/src/drivers/types.d.ts +2 -1
  20. package/dist/host/src/hub.d.ts +3 -1
  21. package/dist/host/src/hub.js +33 -3
  22. package/dist/host/src/hub.js.map +1 -1
  23. package/dist/host/tsconfig.tsbuildinfo +1 -1
  24. package/dist/wire/src/async-questions.d.ts +40 -0
  25. package/dist/wire/src/async-questions.js +66 -0
  26. package/dist/wire/src/async-questions.js.map +1 -0
  27. package/dist/wire/src/goal.d.ts +2 -2
  28. package/dist/wire/src/index.d.ts +1 -0
  29. package/dist/wire/src/index.js +1 -0
  30. package/dist/wire/src/index.js.map +1 -1
  31. package/dist/wire/src/model.d.ts +108 -84
  32. package/dist/wire/src/model.js +3 -0
  33. package/dist/wire/src/model.js.map +1 -1
  34. package/dist/wire/src/rpc.d.ts +2110 -1722
  35. package/dist/wire/src/updates.d.ts +633 -561
  36. package/dist/wire/src/updates.js +6 -1
  37. package/dist/wire/src/updates.js.map +1 -1
  38. package/dist/wire/src/workflow.d.ts +8 -8
  39. package/package.json +2 -2
@@ -46,7 +46,7 @@ export declare const transcriptTimes: WeakMap<{
46
46
  type: "resource_link";
47
47
  name: string;
48
48
  uri: string;
49
- kind?: "agent" | "file" | "skill" | undefined;
49
+ kind?: "file" | "skill" | "agent" | undefined;
50
50
  };
51
51
  sessionUpdate: "user_message_chunk";
52
52
  messageId?: string | undefined;
@@ -63,7 +63,7 @@ export declare const transcriptTimes: WeakMap<{
63
63
  type: "resource_link";
64
64
  name: string;
65
65
  uri: string;
66
- kind?: "agent" | "file" | "skill" | undefined;
66
+ kind?: "file" | "skill" | "agent" | undefined;
67
67
  };
68
68
  sessionUpdate: "agent_message_chunk";
69
69
  messageId: string;
@@ -81,7 +81,7 @@ export declare const transcriptTimes: WeakMap<{
81
81
  type: "resource_link";
82
82
  name: string;
83
83
  uri: string;
84
- kind?: "agent" | "file" | "skill" | undefined;
84
+ kind?: "file" | "skill" | "agent" | undefined;
85
85
  };
86
86
  sessionUpdate: "agent_thought_chunk";
87
87
  messageId: string;
@@ -90,23 +90,60 @@ export declare const transcriptTimes: WeakMap<{
90
90
  status: "completed" | "failed" | "pending" | "in_progress";
91
91
  toolCallId: string;
92
92
  title: string;
93
- kind: "read" | "edit" | "delete" | "move" | "search" | "execute" | "think" | "fetch" | "other";
93
+ kind: "delete" | "other" | "read" | "edit" | "move" | "search" | "execute" | "think" | "fetch";
94
94
  sessionUpdate: "tool_call";
95
+ content?: ({
96
+ type: "content";
97
+ content: {
98
+ type: "text";
99
+ text: string;
100
+ } | {
101
+ type: "image";
102
+ mimeType: string;
103
+ data?: string | undefined;
104
+ uri?: string | undefined;
105
+ } | {
106
+ type: "resource_link";
107
+ name: string;
108
+ uri: string;
109
+ kind?: "file" | "skill" | "agent" | undefined;
110
+ };
111
+ } | {
112
+ path: string;
113
+ type: "diff";
114
+ newText: string;
115
+ oldText?: string | null | undefined;
116
+ } | {
117
+ path: string;
118
+ type: "patch";
119
+ diff: string;
120
+ change: "add" | "delete" | "update";
121
+ movePath?: string | undefined;
122
+ } | {
123
+ type: "terminal";
124
+ terminalId: string;
125
+ })[] | undefined;
126
+ parentToolCallId?: string | undefined;
127
+ rawInput?: unknown;
128
+ locations?: {
129
+ path: string;
130
+ line?: number | undefined;
131
+ }[] | undefined;
95
132
  detail?: {
96
133
  type: "mcp";
97
- tool: string;
98
134
  server: string;
135
+ tool: string;
99
136
  } | {
100
137
  type: "subagent";
101
138
  action: "message" | "resume" | "spawn" | "wait" | "stop" | "list";
102
- state?: "running" | "paused" | "completed" | "failed" | "stopped" | "pending" | "unknown" | undefined;
139
+ state?: "unknown" | "paused" | "running" | "completed" | "failed" | "stopped" | "pending" | undefined;
103
140
  model?: string | undefined;
104
141
  name?: string | undefined;
105
142
  task?: string | undefined;
106
143
  agentType?: string | undefined;
107
144
  workflow?: {
108
145
  completed?: number | undefined;
109
- state?: "running" | "paused" | "completed" | "failed" | "stopped" | undefined;
146
+ state?: "paused" | "running" | "completed" | "failed" | "stopped" | undefined;
110
147
  tokens?: number | undefined;
111
148
  durationMs?: number | undefined;
112
149
  startedAt?: number | undefined;
@@ -122,7 +159,7 @@ export declare const transcriptTimes: WeakMap<{
122
159
  agents?: {
123
160
  id: string;
124
161
  title: string;
125
- state: "running" | "paused" | "completed" | "failed" | "stopped" | "pending" | "unknown";
162
+ state: "unknown" | "paused" | "running" | "completed" | "failed" | "stopped" | "pending";
126
163
  toolCallId?: string | undefined;
127
164
  phaseId?: string | undefined;
128
165
  model?: string | undefined;
@@ -154,7 +191,11 @@ export declare const transcriptTimes: WeakMap<{
154
191
  text: string;
155
192
  more?: number | undefined;
156
193
  } | undefined;
157
- parentToolCallId?: string | undefined;
194
+ } | {
195
+ toolCallId: string;
196
+ sessionUpdate: "tool_call_update";
197
+ status?: "completed" | "failed" | "pending" | "in_progress" | undefined;
198
+ title?: string | undefined;
158
199
  content?: ({
159
200
  type: "content";
160
201
  content: {
@@ -169,7 +210,7 @@ export declare const transcriptTimes: WeakMap<{
169
210
  type: "resource_link";
170
211
  name: string;
171
212
  uri: string;
172
- kind?: "agent" | "file" | "skill" | undefined;
213
+ kind?: "file" | "skill" | "agent" | undefined;
173
214
  };
174
215
  } | {
175
216
  path: string;
@@ -180,37 +221,28 @@ export declare const transcriptTimes: WeakMap<{
180
221
  path: string;
181
222
  type: "patch";
182
223
  diff: string;
183
- change: "delete" | "add" | "update";
224
+ change: "add" | "delete" | "update";
184
225
  movePath?: string | undefined;
185
226
  } | {
186
227
  type: "terminal";
187
228
  terminalId: string;
188
229
  })[] | undefined;
189
- rawInput?: unknown;
190
- locations?: {
191
- path: string;
192
- line?: number | undefined;
193
- }[] | undefined;
194
- } | {
195
- toolCallId: string;
196
- sessionUpdate: "tool_call_update";
197
- status?: "completed" | "failed" | "pending" | "in_progress" | undefined;
198
- title?: string | undefined;
230
+ parentToolCallId?: string | undefined;
199
231
  detail?: {
200
232
  type: "mcp";
201
- tool: string;
202
233
  server: string;
234
+ tool: string;
203
235
  } | {
204
236
  type: "subagent";
205
237
  action: "message" | "resume" | "spawn" | "wait" | "stop" | "list";
206
- state?: "running" | "paused" | "completed" | "failed" | "stopped" | "pending" | "unknown" | undefined;
238
+ state?: "unknown" | "paused" | "running" | "completed" | "failed" | "stopped" | "pending" | undefined;
207
239
  model?: string | undefined;
208
240
  name?: string | undefined;
209
241
  task?: string | undefined;
210
242
  agentType?: string | undefined;
211
243
  workflow?: {
212
244
  completed?: number | undefined;
213
- state?: "running" | "paused" | "completed" | "failed" | "stopped" | undefined;
245
+ state?: "paused" | "running" | "completed" | "failed" | "stopped" | undefined;
214
246
  tokens?: number | undefined;
215
247
  durationMs?: number | undefined;
216
248
  startedAt?: number | undefined;
@@ -226,7 +258,7 @@ export declare const transcriptTimes: WeakMap<{
226
258
  agents?: {
227
259
  id: string;
228
260
  title: string;
229
- state: "running" | "paused" | "completed" | "failed" | "stopped" | "pending" | "unknown";
261
+ state: "unknown" | "paused" | "running" | "completed" | "failed" | "stopped" | "pending";
230
262
  toolCallId?: string | undefined;
231
263
  phaseId?: string | undefined;
232
264
  model?: string | undefined;
@@ -258,38 +290,6 @@ export declare const transcriptTimes: WeakMap<{
258
290
  text: string;
259
291
  more?: number | undefined;
260
292
  } | undefined;
261
- parentToolCallId?: string | undefined;
262
- content?: ({
263
- type: "content";
264
- content: {
265
- type: "text";
266
- text: string;
267
- } | {
268
- type: "image";
269
- mimeType: string;
270
- data?: string | undefined;
271
- uri?: string | undefined;
272
- } | {
273
- type: "resource_link";
274
- name: string;
275
- uri: string;
276
- kind?: "agent" | "file" | "skill" | undefined;
277
- };
278
- } | {
279
- path: string;
280
- type: "diff";
281
- newText: string;
282
- oldText?: string | null | undefined;
283
- } | {
284
- path: string;
285
- type: "patch";
286
- diff: string;
287
- change: "delete" | "add" | "update";
288
- movePath?: string | undefined;
289
- } | {
290
- type: "terminal";
291
- terminalId: string;
292
- })[] | undefined;
293
293
  appendOutput?: string | undefined;
294
294
  rawOutput?: unknown;
295
295
  } | {
@@ -302,8 +302,8 @@ export declare const transcriptTimes: WeakMap<{
302
302
  } | {
303
303
  sessionUpdate: "ls_goal";
304
304
  goal: {
305
- status: "paused" | "active" | "blocked" | "usageLimited" | "budgetLimited" | "complete";
306
305
  objective: string;
306
+ status: "active" | "paused" | "blocked" | "usageLimited" | "budgetLimited" | "complete";
307
307
  tokenBudget?: number | null | undefined;
308
308
  tokensUsed?: number | undefined;
309
309
  timeUsedSeconds?: number | undefined;
@@ -328,16 +328,23 @@ export declare const transcriptTimes: WeakMap<{
328
328
  sessionUpdate: "usage_update";
329
329
  usedTokens?: number | undefined;
330
330
  contextWindow?: number | undefined;
331
+ } | {
332
+ sessionUpdate: "ls_async_questions";
333
+ questions: {
334
+ options: string[];
335
+ id: string;
336
+ title: string;
337
+ }[];
331
338
  } | {
332
339
  sessionUpdate: "ls_message_done";
333
340
  messageId: string;
334
341
  role: "agent" | "thought" | "user";
342
+ parentToolCallId?: string | undefined;
335
343
  questions?: {
336
344
  options: string[];
337
345
  id: string;
338
346
  title: string;
339
347
  }[] | undefined;
340
- parentToolCallId?: string | undefined;
341
348
  } | {
342
349
  options: {
343
350
  values: {
@@ -347,7 +354,7 @@ export declare const transcriptTimes: WeakMap<{
347
354
  }[];
348
355
  id: string;
349
356
  name: string;
350
- category: "model" | "other" | "mode" | "effort";
357
+ category: "model" | "mode" | "effort" | "other";
351
358
  current: string;
352
359
  }[];
353
360
  sessionUpdate: "ls_config";
@@ -361,22 +368,22 @@ export declare const transcriptTimes: WeakMap<{
361
368
  sessionUpdate: "ls_turn";
362
369
  parentToolCallId?: string | undefined;
363
370
  turnId?: string | undefined;
364
- stopReason?: "error" | "end_turn" | "cancelled" | "max_tokens" | "refusal" | undefined;
371
+ stopReason?: "end_turn" | "cancelled" | "error" | "max_tokens" | "refusal" | undefined;
365
372
  } | {
366
373
  options: {
367
374
  name: string;
368
- optionId: string;
369
375
  kind: "allow_once" | "allow_always" | "reject_once" | "reject_always";
376
+ optionId: string;
370
377
  }[];
371
378
  title: string;
372
- requestId: string;
373
379
  sessionUpdate: "ls_permission";
380
+ requestId: string;
374
381
  toolCallId?: string | undefined;
375
382
  detail?: string | undefined;
376
383
  questions?: {
377
384
  id: string;
378
- kind: "text" | "choice" | "choices";
379
385
  text: string;
386
+ kind: "text" | "choice" | "choices";
380
387
  options?: {
381
388
  value: string;
382
389
  label: string;
@@ -388,8 +395,8 @@ export declare const transcriptTimes: WeakMap<{
388
395
  required?: boolean | undefined;
389
396
  }[] | undefined;
390
397
  } | {
391
- requestId: string;
392
398
  sessionUpdate: "ls_permission_resolved";
399
+ requestId: string;
393
400
  optionId?: string | undefined;
394
401
  answers?: {
395
402
  values: string[];
@@ -397,27 +404,27 @@ export declare const transcriptTimes: WeakMap<{
397
404
  other?: string | undefined;
398
405
  }[] | undefined;
399
406
  } | {
400
- driver: "remote" | "desktop" | "none";
401
407
  sessionUpdate: "ls_driver";
408
+ driver: "desktop" | "remote" | "none";
402
409
  deviceId?: string | undefined;
403
410
  } | {
404
- state: "running" | "idle" | "waiting" | "error" | "offline";
411
+ state: "running" | "error" | "idle" | "waiting" | "offline";
405
412
  sessionUpdate: "ls_status";
406
413
  } | {
407
414
  task: {
408
415
  id: string;
409
416
  title: string;
410
- state: "running" | "completed" | "failed" | "stopped" | "unknown";
417
+ state: "unknown" | "running" | "completed" | "failed" | "stopped";
411
418
  startedAt: number;
412
419
  kind: "shell" | "monitor";
413
420
  toolCallId?: string | undefined;
414
421
  endedAt?: number | undefined;
415
- lastSeq?: number | undefined;
416
422
  command?: string | undefined;
417
423
  summary?: string | undefined;
418
424
  exitCode?: number | undefined;
419
425
  output?: boolean | undefined;
420
426
  canStop?: boolean | undefined;
427
+ lastSeq?: number | undefined;
421
428
  };
422
429
  sessionUpdate: "ls_task";
423
430
  } | {
@@ -0,0 +1,7 @@
1
+ import { type AsyncQuestion } from "@linkshell/wire";
2
+ export interface CodexAttention {
3
+ running: boolean;
4
+ questions: AsyncQuestion[];
5
+ }
6
+ /** Read only the current turn, so list badges work without taking ownership of the thread. */
7
+ export declare function rolloutAttention(path: string, chunk?: number, limit?: number): CodexAttention | undefined;
@@ -0,0 +1,82 @@
1
+ import { closeSync, fstatSync, openSync, readSync } from "node:fs";
2
+ import { answeredQuestions } from "@linkshell/wire";
3
+ import { asyncQuestions } from "./mapper.js";
4
+ /** Read only the current turn, so list badges work without taking ownership of the thread. */
5
+ export function rolloutAttention(path, chunk = 256 * 1024, limit = 16 * 1024 * 1024) {
6
+ let fd;
7
+ try {
8
+ fd = openSync(path, "r");
9
+ }
10
+ catch {
11
+ return undefined;
12
+ }
13
+ try {
14
+ const size = fstatSync(fd).size;
15
+ let end = size;
16
+ let prefix = Buffer.alloc(0);
17
+ const questions = [];
18
+ const answered = new Set();
19
+ while (end > 0 && size - end < limit) {
20
+ const start = Math.max(0, end - Math.min(chunk, limit - (size - end)));
21
+ const bytes = Buffer.alloc(end - start);
22
+ readSync(fd, bytes, 0, bytes.length, start);
23
+ let data = Buffer.concat([bytes, prefix]);
24
+ // A writer may still be appending the last record.
25
+ if (end === size)
26
+ data = data.subarray(0, data.lastIndexOf(10) + 1);
27
+ end = start;
28
+ const first = start === 0 ? 0 : data.indexOf(10) + 1;
29
+ if (first === 0 && start > 0) {
30
+ prefix = data;
31
+ continue;
32
+ }
33
+ const lines = data.subarray(first).toString("utf8").split("\n");
34
+ prefix = data.subarray(0, first);
35
+ for (let index = lines.length - 1; index >= 0; index--) {
36
+ const line = lines[index];
37
+ if (!line.includes('"task_') && !line.includes("request_user_input_async") && !line.includes("send_user_message_question_reply"))
38
+ continue;
39
+ let entry;
40
+ try {
41
+ entry = JSON.parse(line);
42
+ }
43
+ catch {
44
+ continue;
45
+ }
46
+ const payload = entry.payload;
47
+ if (!payload || typeof payload !== "object")
48
+ continue;
49
+ if (entry.type === "event_msg" && payload.type === "task_complete")
50
+ return { running: false, questions: [] };
51
+ if (entry.type === "event_msg" && payload.type === "task_started") {
52
+ return { running: true, questions: questions.filter((question) => !answered.has(question.id)) };
53
+ }
54
+ if (entry.type !== "response_item")
55
+ continue;
56
+ if (payload.type === "function_call" && typeof payload.name === "string" && /(?:^|\.)request_user_input_async$/.test(payload.name)) {
57
+ try {
58
+ const args = JSON.parse(payload.arguments);
59
+ questions.unshift(...(asyncQuestions({ id: payload.call_id, delivery: "async", questions: args.questions }) ?? []));
60
+ }
61
+ catch { /* Incomplete arguments cannot make an actionable question. */ }
62
+ }
63
+ if (payload.type === "message" && payload.role === "user" && Array.isArray(payload.content)) {
64
+ for (const block of payload.content) {
65
+ if (typeof block?.text === "string")
66
+ for (const [id] of answeredQuestions(block.text))
67
+ answered.add(id);
68
+ }
69
+ }
70
+ }
71
+ }
72
+ // Unknown is not idle: do not clear a known pending question on a bounded read.
73
+ return undefined;
74
+ }
75
+ catch {
76
+ return undefined;
77
+ }
78
+ finally {
79
+ closeSync(fd);
80
+ }
81
+ }
82
+ //# sourceMappingURL=attention.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attention.js","sourceRoot":"","sources":["../../../../../src/drivers/codex/attention.ts"],"names":[],"mappings":"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"}
@@ -116,6 +116,7 @@ export declare class CodexDriver implements AgentDriver {
116
116
  start(host: DriverHost): Promise<DriverStatus>;
117
117
  authStatus(): Promise<AgentAuth>;
118
118
  stop(): Promise<void>;
119
+ private readonly attention;
119
120
  listSessions(limit: number): Promise<DiscoveredSession[]>;
120
121
  private stateElsewhere;
121
122
  /** Codex's own fork: a new thread with this one's turns, all or through `lastTurnId`, in `cwd`. */
@@ -139,6 +140,7 @@ export declare class CodexDriver implements AgentDriver {
139
140
  private observe;
140
141
  /** Reports what the other process has finished since the last look. An item still being written isn't on disk. */
141
142
  private refresh;
143
+ private announceQuestions;
142
144
  /**
143
145
  * The model, effort and permissions the other process runs the thread with,
144
146
  * from its rollout file: a thread that can only be read has no
@@ -1,6 +1,7 @@
1
+ import { rolloutAttention } from "./attention.js";
1
2
  import { closeSync, fstatSync, openSync, readSync, statSync } from "node:fs";
2
3
  import { codexPreview } from "./computer-preview.js";
3
- import { ABANDON, RpcError, sessionGoalSchema } from "@linkshell/wire";
4
+ import { ABANDON, RpcError, questionReplies, updateAsyncQuestions, sessionGoalSchema } from "@linkshell/wire";
4
5
  import { parseCodexLoginStatus, runStatusCommand } from "../auth.js";
5
6
  import { CodexAppServer, CodexSharedServer, detectCodex, sharedServerSocket } from "./app-server.js";
6
7
  import { APPROVAL_METHODS, QUESTION_METHODS, mapApprovalRequest, mapQuestionRequest, mapNotification, threadStateOf, threadToDiscovered, spawnedThreads, subagentHistory, threadToHistory, toCodexInput, toolStart, turnUnderWay, } from "./mapper.js";
@@ -215,12 +216,23 @@ export class CodexDriver {
215
216
  await this.server?.stop();
216
217
  this.server = undefined;
217
218
  }
219
+ attention = new Map();
218
220
  async listSessions(limit) {
219
221
  const result = await this.rpc("thread/list", { limit, archived: false });
220
222
  return result.data.map((thread) => {
221
223
  const discovered = threadToDiscovered(thread);
222
224
  // This host's app-server only knows the state of the threads loaded in it.
223
225
  const state = this.stateElsewhere(thread.id);
226
+ const stamp = thread.path ? fileStamp(thread.path) : undefined;
227
+ let attention = this.attention.get(thread.id);
228
+ if (thread.path && stamp && attention?.stamp !== stamp) {
229
+ attention = { stamp, value: rolloutAttention(thread.path) };
230
+ this.attention.set(thread.id, attention);
231
+ }
232
+ // Live notifications win over the file, which can lag while the agent writes.
233
+ if (!this.attached.has(thread.id) && !this.observed.has(thread.id) && attention?.value) {
234
+ return { ...discovered, state: attention.value.running ? "running" : "idle", asyncQuestions: attention.value.questions };
235
+ }
224
236
  return state ? { ...discovered, state } : discovered;
225
237
  });
226
238
  }
@@ -391,6 +403,7 @@ export class CodexDriver {
391
403
  if (active)
392
404
  this.host?.update(this.id, threadId, { sessionUpdate: "ls_turn", state: "started", turnId: active.id });
393
405
  this.host?.update(this.id, threadId, { sessionUpdate: "ls_status", state: threadStateOf(thread.status) });
406
+ this.announceQuestions(threadId, thread, true);
394
407
  const read = new Set((thread.turns ?? []).flatMap((turn) => turn.items.map((item) => item.id)));
395
408
  for (const item of underWay) {
396
409
  // A command still running: its card. (Its output so far was missed, so the whole of it comes when it finishes.)
@@ -435,6 +448,7 @@ export class CodexDriver {
435
448
  this.cwds.set(threadId, thread.cwd);
436
449
  this.host?.update(this.id, threadId, { sessionUpdate: "ls_status", state: running ? "running" : "idle" });
437
450
  this.reportHeld(threadId, true);
451
+ this.announceQuestions(threadId, thread, true);
438
452
  this.readHeldSettings(threadId, thread.path ?? undefined);
439
453
  void this.announceCommands(threadId);
440
454
  void this.announceGoal(threadId);
@@ -478,6 +492,7 @@ export class CodexDriver {
478
492
  watch.seen.add(item.itemId);
479
493
  this.report(threadId, item);
480
494
  }
495
+ this.announceQuestions(threadId, thread);
481
496
  const last = thread.turns?.at(-1);
482
497
  const running = turnUnderWay(last);
483
498
  watch.turnId = running ? last?.id : undefined;
@@ -498,6 +513,20 @@ export class CodexDriver {
498
513
  watch.reading = false;
499
514
  }
500
515
  }
516
+ announceQuestions(threadId, thread, force = false) {
517
+ const turn = thread.turns?.at(-1);
518
+ let questions = [];
519
+ if (turnUnderWay(turn)) {
520
+ for (const item of threadToHistory({ ...thread, turns: turn ? [turn] : [] })) {
521
+ for (const update of item.updates)
522
+ questions = updateAsyncQuestions(questions, update);
523
+ }
524
+ }
525
+ // Always restore on attach, even if this host already imported the history.
526
+ const changed = this.isNews(`questions:${threadId}`, questions);
527
+ if (force || changed)
528
+ this.host?.update(this.id, threadId, { sessionUpdate: "ls_async_questions", questions });
529
+ }
501
530
  /**
502
531
  * The model, effort and permissions the other process runs the thread with,
503
532
  * from its rollout file: a thread that can only be read has no
@@ -1046,7 +1075,21 @@ export class CodexDriver {
1046
1075
  async send(nativeId, input, clientMessageId) {
1047
1076
  if (input.length === 0)
1048
1077
  throw RpcError.app("invalid_params", "nothing to send");
1049
- if (this.observed.has(nativeId))
1078
+ const watch = this.observed.get(nativeId);
1079
+ if (watch?.running && questionReplies(textOf(input))) {
1080
+ try {
1081
+ if (!this.desktopBusPath)
1082
+ throw new Error("desktop bus unavailable");
1083
+ await steerThroughDesktop(this.desktopBusPath, nativeId, this.desktopMessage(nativeId, input, clientMessageId));
1084
+ }
1085
+ catch (error) {
1086
+ this.host?.log(`[codex] couldn't deliver a question answer to ${nativeId}: ${error instanceof Error ? error.message : String(error)}`);
1087
+ throw this.refused(nativeId, "回答未确认送达 · 请重试或在电脑上回答", "回答未确认送达,问题仍保留");
1088
+ }
1089
+ void this.refresh(nativeId);
1090
+ return "steered";
1091
+ }
1092
+ if (watch)
1050
1093
  return this.enqueue(nativeId, input, clientMessageId);
1051
1094
  const activeTurnId = this.stateOf(nativeId).activeTurnId;
1052
1095
  if (activeTurnId) {