@cjhyy/code-shell-server 0.9.51 → 0.10.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 (93) hide show
  1. package/dist/account/cli.d.ts +23 -0
  2. package/dist/account/cli.js +249 -0
  3. package/dist/account/cloud-contract.d.ts +39 -0
  4. package/dist/account/cloud-contract.js +134 -0
  5. package/dist/account/cloud-types.d.ts +47 -0
  6. package/dist/account/cloud-types.js +1 -0
  7. package/dist/account/feature-service.d.ts +47 -0
  8. package/dist/account/feature-service.js +184 -0
  9. package/dist/account/http.d.ts +17 -0
  10. package/dist/account/http.js +158 -0
  11. package/dist/account/links.d.ts +13 -0
  12. package/dist/account/links.js +137 -0
  13. package/dist/account/manager.d.ts +96 -0
  14. package/dist/account/manager.js +976 -0
  15. package/dist/account/native-lock.d.ts +6 -0
  16. package/dist/account/native-lock.js +26 -0
  17. package/dist/account/native-store.d.ts +32 -0
  18. package/dist/account/native-store.js +162 -0
  19. package/dist/account/profile.d.ts +4 -0
  20. package/dist/account/profile.js +46 -0
  21. package/dist/account/store.d.ts +34 -0
  22. package/dist/account/store.js +118 -0
  23. package/dist/account/types.d.ts +138 -0
  24. package/dist/account/types.js +2 -0
  25. package/dist/desktop-web/http-api.d.ts +4 -0
  26. package/dist/desktop-web/http-api.js +12 -4
  27. package/dist/environment-identity.d.ts +1 -1
  28. package/dist/environment-identity.js +2 -1
  29. package/dist/headless-worker-env.d.ts +2 -0
  30. package/dist/headless-worker-env.js +15 -0
  31. package/dist/hub/approval-lease.js +6 -1
  32. package/dist/hub/configuration.d.ts +11 -0
  33. package/dist/hub/configuration.js +34 -1
  34. package/dist/hub/model-parameter-metadata.d.ts +26 -0
  35. package/dist/hub/model-parameter-metadata.js +92 -0
  36. package/dist/hub/run-replay.js +1 -1
  37. package/dist/hub/session-management.js +1 -1
  38. package/dist/index.account.d.ts +12 -0
  39. package/dist/index.account.js +12 -0
  40. package/dist/index.mobile-remote.d.ts +2 -0
  41. package/dist/index.mobile-remote.js +1 -0
  42. package/dist/index.worker.d.ts +1 -0
  43. package/dist/index.worker.js +1 -0
  44. package/dist/mobile-remote/account-relay-access.d.ts +59 -0
  45. package/dist/mobile-remote/account-relay-access.js +174 -0
  46. package/dist/mobile-remote/approval-clock.d.ts +3 -0
  47. package/dist/mobile-remote/approval-clock.js +15 -0
  48. package/dist/mobile-remote/codex-room-agent.d.ts +6 -0
  49. package/dist/mobile-remote/codex-room-agent.js +17 -1
  50. package/dist/mobile-remote/mobile-chat-turn.d.ts +4 -0
  51. package/dist/mobile-remote/mobile-chat-turn.js +3 -0
  52. package/dist/mobile-remote/mobile-client-event-validator.js +47 -2
  53. package/dist/mobile-remote/mobile-upload-service.d.ts +9 -0
  54. package/dist/mobile-remote/mobile-upload-service.js +25 -0
  55. package/dist/mobile-remote/remote-host-manager.d.ts +21 -4
  56. package/dist/mobile-remote/remote-host-manager.js +142 -25
  57. package/dist/mobile-remote/resident-agent.d.ts +14 -0
  58. package/dist/mobile-remote/resident-agent.js +38 -11
  59. package/dist/mobile-remote/room-history.d.ts +7 -0
  60. package/dist/mobile-remote/room-history.js +56 -1
  61. package/dist/mobile-remote/room-manager.d.ts +216 -9
  62. package/dist/mobile-remote/room-manager.js +700 -65
  63. package/dist/mobile-remote/room-process-stop.d.ts +15 -0
  64. package/dist/mobile-remote/room-process-stop.js +41 -0
  65. package/dist/mobile-remote/trusted-device-store.d.ts +12 -0
  66. package/dist/mobile-remote/trusted-device-store.js +54 -1
  67. package/dist/mobile-remote/turn-queue.d.ts +183 -0
  68. package/dist/mobile-remote/turn-queue.js +422 -0
  69. package/dist/mobile-remote/types.d.ts +1 -1
  70. package/dist/panels/agent-task-host.js +6 -12
  71. package/dist/panels/agent-task-service.d.ts +2 -0
  72. package/dist/panels/agent-task-service.js +7 -0
  73. package/dist/panels/browser-capture.d.ts +1 -15
  74. package/dist/panels/browser-capture.js +1 -35
  75. package/dist/panels/directory-picker.d.ts +48 -0
  76. package/dist/panels/directory-picker.js +247 -0
  77. package/dist/panels/http.d.ts +15 -15
  78. package/dist/panels/management-http.d.ts +9 -9
  79. package/dist/panels/process-service.d.ts +5 -4
  80. package/dist/panels/process-service.js +54 -16
  81. package/dist/panels/runtime.js +148 -30
  82. package/dist/panels/tool-jobs-storage.d.ts +4 -0
  83. package/dist/panels/tool-jobs-storage.js +111 -34
  84. package/dist/panels/types.d.ts +1 -97
  85. package/dist/remote-relay/connector.d.ts +8 -0
  86. package/dist/remote-relay/connector.js +29 -2
  87. package/dist/remote-relay/protocol.d.ts +21 -0
  88. package/dist/remote-relay/protocol.js +31 -0
  89. package/dist/serve/cli.js +6 -0
  90. package/dist/serve/headless-server.js +24 -19
  91. package/dist/sessions-service.d.ts +4 -0
  92. package/dist/sessions-service.js +14 -1
  93. package/package.json +10 -6
@@ -0,0 +1,422 @@
1
+ import { randomUUID } from "node:crypto";
2
+ /**
3
+ * Host-owned FIFO of turns submitted while their conversation is busy.
4
+ *
5
+ * The queue never runs anything itself: an entry is handed to the caller's
6
+ * ORIGINAL submit path (same validation, approvals and workspace checks) only
7
+ * when the exact target is idle, so it cannot bypass or duplicate an existing
8
+ * Core/CLI queue. Entries are bound to the conversation they were submitted
9
+ * for, never to a viewer's later selection.
10
+ */
11
+ export const MAX_TURN_QUEUE_ENTRIES_PER_TARGET = 20;
12
+ export const MAX_TURN_QUEUE_ENTRIES_PER_DEVICE = 50;
13
+ export const MAX_TURN_QUEUE_ENTRIES = 200;
14
+ /** Remembered admission outcomes so a replayed submission is never run twice. */
15
+ const MAX_SETTLED_OUTCOMES = 512;
16
+ const INTENT_CONFLICT = "这个消息标识已用于另一条内容,未重复提交。请作为新消息发送。";
17
+ export function turnQueueTargetKey(target) {
18
+ return target.kind === "session" ? `session:${target.sessionId}` : `room:${target.roomId}`;
19
+ }
20
+ /** An unknown fingerprint on either side cannot prove a mismatch. */
21
+ function sameFingerprint(left, right) {
22
+ return left === undefined || right === undefined || left === right;
23
+ }
24
+ export function sameTurnQueueTarget(left, right) {
25
+ return !!left && !!right && turnQueueTargetKey(left) === turnQueueTargetKey(right);
26
+ }
27
+ export class MobileTurnQueue {
28
+ deps;
29
+ epoch = randomUUID();
30
+ queues = new Map();
31
+ settled = new Map();
32
+ pumping = new Set();
33
+ stopGenerations = new Map();
34
+ now;
35
+ timer;
36
+ disposed = false;
37
+ /** Monotonic across targets so a recreated queue never reuses a revision. */
38
+ revision = 0;
39
+ constructor(deps) {
40
+ this.deps = deps;
41
+ this.now = deps.now ?? (() => Date.now());
42
+ }
43
+ /**
44
+ * The live or settled outcome of one device's intent on one target. Callers
45
+ * check this BEFORE retaining resources: a replay of an admitted turn must be
46
+ * answered from its receipt, not re-validated against consumed uploads.
47
+ */
48
+ lookup(target, deviceId, clientMessageId, fingerprint) {
49
+ const key = turnQueueTargetKey(target);
50
+ const queue = this.queues.get(key);
51
+ const position = queue?.entries.findIndex((entry) => entry.deviceId === deviceId && entry.clientMessageId === clientMessageId) ?? -1;
52
+ if (queue && position >= 0) {
53
+ const entry = queue.entries[position];
54
+ if (!sameFingerprint(entry.fingerprint, fingerprint))
55
+ return { kind: "conflict", message: INTENT_CONFLICT };
56
+ return { kind: "duplicate", entry, position };
57
+ }
58
+ const settled = this.settled.get(this.identity(deviceId, key, clientMessageId));
59
+ if (!settled)
60
+ return undefined;
61
+ if (!sameFingerprint(settled.fingerprint, fingerprint))
62
+ return { kind: "conflict", message: INTENT_CONFLICT };
63
+ return { kind: "settled", outcome: settled.outcome };
64
+ }
65
+ /** Admit one submission, or return its existing/settled outcome for a replay. */
66
+ enqueue(submission) {
67
+ if (this.disposed)
68
+ return { kind: "rejected", message: "排队服务已停止。" };
69
+ const key = turnQueueTargetKey(submission.target);
70
+ const queue = this.queues.get(key);
71
+ const found = this.lookup(submission.target, submission.deviceId, submission.clientMessageId, submission.fingerprint);
72
+ if (found?.kind === "conflict")
73
+ return { kind: "rejected", message: found.message };
74
+ if (found)
75
+ return found;
76
+ if (!submission.text.trim() && submission.attachments.length === 0)
77
+ return { kind: "rejected", message: "消息必须包含文字或图片。" };
78
+ if ((queue?.entries.length ?? 0) >= MAX_TURN_QUEUE_ENTRIES_PER_TARGET)
79
+ return {
80
+ kind: "rejected",
81
+ message: `每个会话最多排队 ${MAX_TURN_QUEUE_ENTRIES_PER_TARGET} 条消息。`,
82
+ };
83
+ let total = 0;
84
+ let device = 0;
85
+ for (const item of this.queues.values()) {
86
+ total += item.entries.length;
87
+ device += item.entries.filter((entry) => entry.deviceId === submission.deviceId).length;
88
+ }
89
+ if (total >= MAX_TURN_QUEUE_ENTRIES || device >= MAX_TURN_QUEUE_ENTRIES_PER_DEVICE)
90
+ return { kind: "rejected", message: "待执行消息过多,请等待或取消部分排队消息。" };
91
+ const entry = {
92
+ ...submission,
93
+ attachments: submission.attachments.map((item) => ({ ...item })),
94
+ uploadIds: [...submission.uploadIds],
95
+ id: randomUUID(),
96
+ createdAt: this.now(),
97
+ status: "queued",
98
+ };
99
+ const target = queue ?? { target: submission.target, entries: [], revision: 0 };
100
+ target.entries.push(entry);
101
+ this.queues.set(key, target);
102
+ this.changed(target);
103
+ this.ensureTimer();
104
+ return { kind: "queued", entry, position: target.entries.length - 1 };
105
+ }
106
+ /** Remove one entry that has not been handed to its submit path. */
107
+ cancel(target, entryId) {
108
+ const queue = this.queues.get(turnQueueTargetKey(target));
109
+ const index = queue?.entries.findIndex((entry) => entry.id === entryId) ?? -1;
110
+ if (!queue || index < 0)
111
+ return { ok: false, message: "这条排队消息已执行或已移除。" };
112
+ const entry = queue.entries[index];
113
+ if (entry.status === "dispatching")
114
+ return { ok: false, message: "这条消息正在提交,无法再取消。可在执行后停止。" };
115
+ queue.entries.splice(index, 1);
116
+ this.retire(entry, "cancelled");
117
+ // The pause belonged to a failed/uncertain entry; once none is left the
118
+ // rest may run. A Stop pause stays until an explicit resume.
119
+ this.releaseFailurePause(queue);
120
+ this.changed(queue);
121
+ void this.pump(target);
122
+ return { ok: true };
123
+ }
124
+ /** Remove every entry that is not already being submitted. */
125
+ clear(target) {
126
+ const queue = this.queues.get(turnQueueTargetKey(target));
127
+ if (!queue)
128
+ return 0;
129
+ const removed = queue.entries.filter((entry) => entry.status !== "dispatching");
130
+ if (!removed.length)
131
+ return 0;
132
+ queue.entries = queue.entries.filter((entry) => entry.status === "dispatching");
133
+ for (const entry of removed)
134
+ this.retire(entry, "cancelled");
135
+ queue.paused = undefined;
136
+ this.changed(queue);
137
+ return removed.length;
138
+ }
139
+ /**
140
+ * Stop scheduling after the current turn; nothing is discarded. An explicit
141
+ * Stop outranks a failure/uncertain pause: removing or retrying the entry
142
+ * that caused that pause must not restart the rest. Every Stop advances the
143
+ * target's stop generation, which fences an in-flight dispatch off before it
144
+ * physically submits (see stopGeneration).
145
+ */
146
+ pause(target, reason) {
147
+ const key = turnQueueTargetKey(target);
148
+ this.stopGenerations.set(key, this.stopGeneration(target) + 1);
149
+ const queue = this.queues.get(key);
150
+ if (!queue?.entries.length || queue.paused === reason)
151
+ return;
152
+ queue.paused = reason;
153
+ this.changed(queue);
154
+ }
155
+ /** Monotonic count of Stops of this target during this Host lifetime. */
156
+ stopGeneration(target) {
157
+ return this.stopGenerations.get(turnQueueTargetKey(target)) ?? 0;
158
+ }
159
+ /**
160
+ * Continue after Stop or a rejected entry. An entry whose execution cannot
161
+ * be ruled out must be removed first; it is never resubmitted from here.
162
+ */
163
+ resume(target) {
164
+ const queue = this.queues.get(turnQueueTargetKey(target));
165
+ if (!queue)
166
+ return { ok: true };
167
+ if (queue.entries.some((entry) => entry.status === "uncertain"))
168
+ return {
169
+ ok: false,
170
+ message: "有消息无法确认是否已执行。请先核对会话记录,再移除那条消息。",
171
+ };
172
+ for (const entry of queue.entries)
173
+ if (entry.status === "failed") {
174
+ entry.status = "queued";
175
+ entry.message = undefined;
176
+ }
177
+ queue.paused = undefined;
178
+ this.changed(queue);
179
+ void this.pump(target);
180
+ return { ok: true };
181
+ }
182
+ /** Retry a rejected entry with the same clientMessageId. */
183
+ retry(target, entryId) {
184
+ const queue = this.queues.get(turnQueueTargetKey(target));
185
+ const entry = queue?.entries.find((item) => item.id === entryId);
186
+ if (!queue || !entry)
187
+ return { ok: false, message: "这条排队消息已执行或已移除。" };
188
+ if (entry.status !== "failed")
189
+ return {
190
+ ok: false,
191
+ message: entry.status === "uncertain"
192
+ ? "无法确认这条消息是否已执行,不能自动重试。请核对会话记录。"
193
+ : "这条消息仍在等待,无需重试。",
194
+ };
195
+ entry.status = "queued";
196
+ entry.message = undefined;
197
+ this.releaseFailurePause(queue);
198
+ this.changed(queue);
199
+ void this.pump(target);
200
+ return { ok: true };
201
+ }
202
+ releaseFailurePause(queue) {
203
+ if ((queue.paused === "failed" || queue.paused === "uncertain") &&
204
+ !queue.entries.some((entry) => entry.status === "failed" || entry.status === "uncertain"))
205
+ queue.paused = undefined;
206
+ }
207
+ /** Publish an entry whose submitter was told it is queued rather than running. */
208
+ reveal(target, entryId) {
209
+ const queue = this.queues.get(turnQueueTargetKey(target));
210
+ const entry = queue?.entries.find((item) => item.id === entryId);
211
+ if (!queue || !entry?.transient)
212
+ return;
213
+ entry.transient = undefined;
214
+ this.changed(queue);
215
+ }
216
+ has(target) {
217
+ return (this.queues.get(turnQueueTargetKey(target))?.entries.length ?? 0) > 0;
218
+ }
219
+ entries(target) {
220
+ return this.queues.get(turnQueueTargetKey(target))?.entries ?? [];
221
+ }
222
+ targets() {
223
+ return [...this.queues.values()].map((queue) => queue.target);
224
+ }
225
+ /** Public state as seen by one recipient device. */
226
+ snapshot(target, recipientDeviceId) {
227
+ const queue = this.queues.get(turnQueueTargetKey(target));
228
+ return {
229
+ target,
230
+ epoch: this.epoch,
231
+ revision: queue?.revision ?? this.revision,
232
+ ...(queue?.paused ? { paused: queue.paused } : {}),
233
+ entries: (queue?.entries ?? [])
234
+ .filter((entry) => !entry.transient)
235
+ .map((entry) => ({
236
+ id: entry.id,
237
+ clientMessageId: entry.clientMessageId,
238
+ text: entry.text,
239
+ attachments: entry.attachments.map((item) => ({ ...item })),
240
+ status: entry.status,
241
+ ...(entry.message ? { message: entry.message } : {}),
242
+ createdAt: entry.createdAt,
243
+ ownDevice: recipientDeviceId !== undefined && entry.deviceId === recipientDeviceId,
244
+ ...(entry.permissionMode ? { permissionMode: entry.permissionMode } : {}),
245
+ ...(entry.modelKey ? { modelKey: entry.modelKey } : {}),
246
+ ...(entry.nativeConnectionId ? { nativeConnectionId: entry.nativeConnectionId } : {}),
247
+ })),
248
+ };
249
+ }
250
+ /** Re-evaluate every queue; used by the idle timer and Host lifecycle signals. */
251
+ pumpAll() {
252
+ return Promise.all(this.targets().map((target) => this.pump(target))).then(() => undefined);
253
+ }
254
+ /**
255
+ * Hand the next runnable entry to its submit path when the exact target is
256
+ * idle. At most one entry per target is in flight. Entries of one device
257
+ * keep their order; a device that cannot run now (e.g. its tab is away)
258
+ * does not hold back other devices' turns. A rejected or uncertain entry
259
+ * pauses the whole target so nothing runs past an unresolved outcome.
260
+ */
261
+ async pump(target) {
262
+ const key = turnQueueTargetKey(target);
263
+ const queue = this.queues.get(key);
264
+ if (this.disposed || !queue || queue.paused || this.pumping.has(key))
265
+ return;
266
+ const settleMs = this.deps.settleMs ?? 750;
267
+ // Give the accepted turn time to register as busy before the next one.
268
+ if (queue.lastDispatchAt !== undefined && this.now() - queue.lastDispatchAt < settleMs)
269
+ return;
270
+ let busy;
271
+ try {
272
+ busy = this.deps.isBusy(queue.target);
273
+ }
274
+ catch {
275
+ busy = true;
276
+ }
277
+ if (busy)
278
+ return;
279
+ const blocked = new Set();
280
+ let head;
281
+ let gate;
282
+ let waitingChanged = false;
283
+ for (const entry of queue.entries) {
284
+ if (entry.status !== "queued" && entry.status !== "waiting")
285
+ return;
286
+ if (blocked.has(entry.deviceId))
287
+ continue;
288
+ let entryGate;
289
+ try {
290
+ entryGate = this.deps.canDispatch(entry);
291
+ }
292
+ catch (error) {
293
+ entryGate = { ok: false, message: error instanceof Error ? error.message : String(error) };
294
+ }
295
+ if (entryGate.ok) {
296
+ head = entry;
297
+ gate = entryGate;
298
+ break;
299
+ }
300
+ blocked.add(entry.deviceId);
301
+ if (entry.status !== "waiting" || entry.message !== entryGate.message) {
302
+ entry.status = "waiting";
303
+ entry.message = entryGate.message;
304
+ waitingChanged = true;
305
+ }
306
+ }
307
+ if (waitingChanged)
308
+ this.changed(queue);
309
+ if (!head || !gate?.ok || this.queues.get(key) !== queue)
310
+ return;
311
+ if (gate.viewerId)
312
+ head.viewerId = gate.viewerId;
313
+ head.status = "dispatching";
314
+ head.message = undefined;
315
+ this.pumping.add(key);
316
+ this.changed(queue);
317
+ let result;
318
+ try {
319
+ result = await this.deps.dispatch(head);
320
+ }
321
+ catch (error) {
322
+ result = { ok: false, message: error instanceof Error ? error.message : String(error) };
323
+ }
324
+ finally {
325
+ this.pumping.delete(key);
326
+ }
327
+ const index = queue.entries.indexOf(head);
328
+ if (index < 0)
329
+ return;
330
+ if (result.ok) {
331
+ queue.entries.splice(index, 1);
332
+ queue.lastDispatchAt = this.now();
333
+ this.remember(head, "dispatched");
334
+ }
335
+ else if (result.requeue) {
336
+ // Fenced off by a Stop before submission: unchanged, waiting for resume.
337
+ head.transient = undefined;
338
+ head.status = "queued";
339
+ head.message = undefined;
340
+ }
341
+ else if (head.transient && !result.uncertain) {
342
+ // Its submitter received the original definite rejection and still owns
343
+ // the draft; nothing ran.
344
+ queue.entries.splice(index, 1);
345
+ }
346
+ else {
347
+ // A possibly admitted turn stays visible (even a direct one) so its
348
+ // intent id keeps deduplicating replays; it is never resubmitted.
349
+ head.transient = undefined;
350
+ head.status = result.uncertain ? "uncertain" : "failed";
351
+ head.message = result.message;
352
+ if (queue.paused !== "stopped")
353
+ queue.paused = result.uncertain ? "uncertain" : "failed";
354
+ }
355
+ if (this.queues.get(key) === queue)
356
+ this.changed(queue);
357
+ }
358
+ dispose() {
359
+ this.disposed = true;
360
+ if (this.timer)
361
+ clearInterval(this.timer);
362
+ this.timer = undefined;
363
+ for (const queue of this.queues.values())
364
+ for (const entry of queue.entries)
365
+ this.deps.discard?.(entry);
366
+ this.queues.clear();
367
+ }
368
+ identity(deviceId, key, clientMessageId) {
369
+ return JSON.stringify([deviceId, key, clientMessageId]);
370
+ }
371
+ retire(entry, outcome) {
372
+ this.remember(entry, outcome);
373
+ try {
374
+ this.deps.discard?.(entry);
375
+ }
376
+ catch {
377
+ // Upload expiry still reclaims an entry whose release failed.
378
+ }
379
+ }
380
+ remember(entry, outcome) {
381
+ const identity = this.identity(entry.deviceId, turnQueueTargetKey(entry.target), entry.clientMessageId);
382
+ this.settled.delete(identity);
383
+ this.settled.set(identity, {
384
+ outcome,
385
+ ...(entry.fingerprint !== undefined ? { fingerprint: entry.fingerprint } : {}),
386
+ });
387
+ while (this.settled.size > MAX_SETTLED_OUTCOMES) {
388
+ const oldest = this.settled.keys().next().value;
389
+ if (oldest === undefined)
390
+ break;
391
+ this.settled.delete(oldest);
392
+ }
393
+ }
394
+ changed(queue) {
395
+ queue.revision = ++this.revision;
396
+ const key = turnQueueTargetKey(queue.target);
397
+ if (!queue.entries.length) {
398
+ // Keep the revision monotonic for clients while an empty state is published.
399
+ this.queues.delete(key);
400
+ }
401
+ try {
402
+ this.deps.publish(queue.target);
403
+ }
404
+ finally {
405
+ if (!queue.entries.length)
406
+ this.stopTimerIfIdle();
407
+ }
408
+ }
409
+ ensureTimer() {
410
+ const interval = this.deps.intervalMs ?? 1_000;
411
+ if (this.timer || interval <= 0)
412
+ return;
413
+ this.timer = setInterval(() => void this.pumpAll(), interval);
414
+ this.timer.unref?.();
415
+ }
416
+ stopTimerIfIdle() {
417
+ if (this.queues.size || !this.timer)
418
+ return;
419
+ clearInterval(this.timer);
420
+ this.timer = undefined;
421
+ }
422
+ }
@@ -1 +1 @@
1
- export type { TrustedDevice, TrustedDevicePublic, PairingToken, ApprovalScope, ApprovalPathScope, MobileRemotePermissionMode as PermissionMode, CcDiscoveredSession, CcHistoryMessage, CcApprovalDecision, MobilePermissionModeSnapshotEntry, MobileProjectMeta, MobileImageMime, MobileImageBase, MobileImageAttachment, MobileAttachmentSummary, MobileClientEvent, MobileServerEvent, RoomPublic, MobileSessionMeta, } from "@cjhyy/code-shell-core";
1
+ export type { TrustedDevice, TrustedDevicePublic, PairingToken, ApprovalScope, ApprovalPathScope, MobileRemotePermissionMode as PermissionMode, CcDiscoveredSession, CcHistoryMessage, CcApprovalDecision, MobilePermissionModeSnapshotEntry, MobileProjectMeta, MobileImageMime, MobileImageBase, MobileImageAttachment, MobileAttachmentSummary, MobileClientEvent, MobileServerEvent, MobileTurnQueueEntry, MobileTurnQueueEntryStatus, MobileTurnQueueState, MobileTurnQueueTarget, RoomPublic, MobileSessionMeta, } from "@cjhyy/code-shell-core";
@@ -5,6 +5,7 @@ import { isAbsolute } from "node:path";
5
5
  import { SettingsManager } from "@cjhyy/code-shell-core";
6
6
  import { getMergedCatalog } from "@cjhyy/code-shell-core/internal";
7
7
  import { WorkerBridgeCore } from "../worker-bridge-core.js";
8
+ import { headlessWorkerEnv } from "../headless-worker-env.js";
8
9
  import { PanelAppAgentTaskService, } from "./agent-task-service.js";
9
10
  import { buildPanelAgentTaskModelCatalog, } from "./agent-task-models.js";
10
11
  // Keep the request's hard tool ceiling. Scheduling, delegation, arbitrary MCP,
@@ -269,18 +270,11 @@ export function createPanelAgentTaskHost(options = {}) {
269
270
  entryPath,
270
271
  execPath: options.execPath,
271
272
  fallbackCwd: () => scope.cwd,
272
- buildEnv: () => ({
273
- ...(options.buildEnv?.() ?? {
274
- ...process.env,
275
- CODE_SHELL_CAPABILITY_MODULES: `${import.meta.resolve("@cjhyy/code-shell-capability-coding")}#createCodingModule`,
276
- }),
277
- // A task worker has no Desktop main bridge, even inside Desktop Web.
278
- CODE_SHELL_REMOTE_LINK_CLIENT_SECRET: undefined,
279
- CODE_SHELL_OPERATION_BATCH_ACCESS: undefined,
280
- CODE_SHELL_MODEL_REQUEST_SIGNING: undefined,
281
- CODE_SHELL_WORKSPACE_ACCESS: undefined,
282
- CODE_SHELL_HOST_MCP_SERVERS: undefined,
283
- CODE_SHELL_CREDENTIAL_ACCESS: "local",
273
+ buildEnv: () =>
274
+ // A task worker has no Desktop main bridge, even inside Desktop Web.
275
+ headlessWorkerEnv(options.buildEnv?.() ?? {
276
+ ...process.env,
277
+ CODE_SHELL_CAPABILITY_MODULES: `${import.meta.resolve("@cjhyy/code-shell-capability-coding")}#createCodingModule`,
284
278
  }),
285
279
  onWorkerStarted: ({ pid }) => {
286
280
  running.pid = pid;
@@ -94,6 +94,8 @@ export declare class PanelAppAgentTaskService {
94
94
  start(owner: PanelAgentTaskOwner, raw: PanelAgentTaskStartInput): PanelAgentTaskView;
95
95
  list(owner: PanelAgentTaskOwner): PanelAgentTaskView[];
96
96
  get(owner: PanelAgentTaskOwner, id: unknown): PanelAgentTaskView;
97
+ /** Native Host authority for a currently executing task, never a guest-facing lookup. */
98
+ activeOwner(sessionId: string): PanelAgentTaskOwner | undefined;
97
99
  cancel(owner: PanelAgentTaskOwner, id: unknown): Promise<PanelAgentTaskView>;
98
100
  rebind(owner: PanelAgentTaskOwner): void;
99
101
  /** `projects` limits cancellation to tasks bound to those (canonical) projects. */
@@ -152,6 +152,13 @@ export class PanelAppAgentTaskService {
152
152
  const task = this.scopedTask(owner, id);
153
153
  return publicTask(task);
154
154
  }
155
+ /** Native Host authority for a currently executing task, never a guest-facing lookup. */
156
+ activeOwner(sessionId) {
157
+ const task = [...this.tasks.values()].find((candidate) => candidate.sessionId === sessionId && candidate.status === "running");
158
+ if (!task || task.cancelRequested)
159
+ return undefined;
160
+ return { ...task.owner, availableSkills: [...task.owner.availableSkills] };
161
+ }
155
162
  async cancel(owner, id) {
156
163
  const task = this.scopedTask(owner, id);
157
164
  if (TERMINAL_STATUSES.has(task.status))
@@ -1,15 +1 @@
1
- /** Bounded, domain-neutral requests for the trusted browser's recording chooser. */
2
- export declare const panelVideoLimits: Readonly<{
3
- maxDurationSeconds: 1200;
4
- maxBytes: number;
5
- defaultDurationSeconds: 300;
6
- defaultBytes: number;
7
- }>;
8
- export interface PanelVideoOptions {
9
- source: "camera" | "screen";
10
- microphone: boolean;
11
- systemAudio: boolean;
12
- maxDurationSeconds: number;
13
- maxBytes: number;
14
- }
15
- export declare function panelVideoOptions(raw: unknown): PanelVideoOptions;
1
+ export { panelVideoLimits, panelVideoOptions, type PanelVideoOptions, } from "@cjhyy/code-shell-web/panels";
@@ -1,35 +1 @@
1
- /** Bounded, domain-neutral requests for the trusted browser's recording chooser. */
2
- export const panelVideoLimits = Object.freeze({
3
- maxDurationSeconds: 1200,
4
- maxBytes: 200 * 1024 * 1024,
5
- defaultDurationSeconds: 300,
6
- defaultBytes: 64 * 1024 * 1024,
7
- });
8
- export function panelVideoOptions(raw) {
9
- const value = raw;
10
- if (!value ||
11
- typeof value !== "object" ||
12
- Array.isArray(value) ||
13
- Object.keys(value).some((key) => !["source", "microphone", "systemAudio", "maxDurationSeconds", "maxBytes"].includes(key)) ||
14
- (value.source !== "camera" && value.source !== "screen") ||
15
- (value.microphone !== undefined && typeof value.microphone !== "boolean") ||
16
- (value.systemAudio !== undefined && typeof value.systemAudio !== "boolean") ||
17
- (value.source !== "screen" && value.systemAudio === true))
18
- throw new Error("视频录制请求无效。");
19
- const maxDurationSeconds = value.maxDurationSeconds ?? panelVideoLimits.defaultDurationSeconds;
20
- const maxBytes = value.maxBytes ?? panelVideoLimits.defaultBytes;
21
- if (!Number.isSafeInteger(maxDurationSeconds) ||
22
- Number(maxDurationSeconds) < 1 ||
23
- Number(maxDurationSeconds) > panelVideoLimits.maxDurationSeconds ||
24
- !Number.isSafeInteger(maxBytes) ||
25
- Number(maxBytes) < 1 ||
26
- Number(maxBytes) > panelVideoLimits.maxBytes)
27
- throw new Error("视频录制限制无效。");
28
- return {
29
- source: value.source,
30
- microphone: value.microphone !== false,
31
- systemAudio: value.systemAudio === true,
32
- maxDurationSeconds: Number(maxDurationSeconds),
33
- maxBytes: Number(maxBytes),
34
- };
35
- }
1
+ export { panelVideoLimits, panelVideoOptions, } from "@cjhyy/code-shell-web/panels";
@@ -0,0 +1,48 @@
1
+ export declare const directoryPickerLifetime: number;
2
+ export interface PanelDirectorySelection {
3
+ path: string;
4
+ assertCurrent(): void;
5
+ }
6
+ /** Used only after a Host bookmark proves a previous choice; a client path is never sufficient. */
7
+ export declare function capturePanelWorkspaceDirectory(cwd: string, path: string): PanelDirectorySelection;
8
+ /** A pending parent-UI choice of an existing project directory, not filesystem authority. */
9
+ export declare class PanelDirectoryPicker {
10
+ private readonly cwd;
11
+ private readonly now;
12
+ readonly rootPath: string;
13
+ readonly rootToken: string;
14
+ readonly initialToken: string;
15
+ readonly expiresAt: number;
16
+ private readonly root;
17
+ private readonly choices;
18
+ private closed;
19
+ private reading;
20
+ private tokenBytes;
21
+ constructor(cwd: string, now: () => number);
22
+ close(): void;
23
+ private assertRoot;
24
+ assertCurrent(): void;
25
+ private check;
26
+ private get;
27
+ private add;
28
+ list(token: unknown, authorize: () => Promise<void>): Promise<{
29
+ rootToken: string;
30
+ directories: {
31
+ name: string;
32
+ token: string;
33
+ }[];
34
+ truncated: boolean;
35
+ parentToken?: string | undefined;
36
+ directoryToken: string;
37
+ relativePath: string;
38
+ name: string;
39
+ }>;
40
+ assertListing(listing: {
41
+ directoryToken: string;
42
+ directories: Array<{
43
+ token: string;
44
+ }>;
45
+ }): void;
46
+ /** Called synchronously after final authorization, while this exact confirmation is pending. */
47
+ select(token: unknown): PanelDirectorySelection;
48
+ }