@cjhyy/code-shell-server 0.9.8 → 0.9.10

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 (111) hide show
  1. package/README.md +85 -23
  2. package/dist/bin/code-shell-project-runtime.d.ts +2 -0
  3. package/dist/bin/code-shell-project-runtime.js +8 -0
  4. package/dist/desktop-web/http-api.d.ts +43 -0
  5. package/dist/desktop-web/http-api.js +376 -0
  6. package/dist/hub/approval-lease.d.ts +30 -0
  7. package/dist/hub/approval-lease.js +89 -0
  8. package/dist/hub/auth-http.d.ts +23 -0
  9. package/dist/hub/auth-http.js +259 -0
  10. package/dist/hub/auth-store.d.ts +52 -0
  11. package/dist/hub/auth-store.js +316 -0
  12. package/dist/hub/configuration.d.ts +64 -0
  13. package/dist/hub/configuration.js +555 -0
  14. package/dist/hub/files.d.ts +9 -0
  15. package/dist/hub/files.js +278 -0
  16. package/dist/hub/mcp-configuration.d.ts +51 -0
  17. package/dist/hub/mcp-configuration.js +558 -0
  18. package/dist/hub/mcp-probe.d.ts +24 -0
  19. package/dist/hub/mcp-probe.js +392 -0
  20. package/dist/hub/model-probe.d.ts +20 -0
  21. package/dist/hub/model-probe.js +193 -0
  22. package/dist/hub/outbound-transport.d.ts +36 -0
  23. package/dist/hub/outbound-transport.js +122 -0
  24. package/dist/hub/run-replay.d.ts +42 -0
  25. package/dist/hub/run-replay.js +89 -0
  26. package/dist/hub/session-management.d.ts +36 -0
  27. package/dist/hub/session-management.js +405 -0
  28. package/dist/hub/skills-management.d.ts +43 -0
  29. package/dist/hub/skills-management.js +487 -0
  30. package/dist/hub/uploads.d.ts +22 -0
  31. package/dist/hub/uploads.js +241 -0
  32. package/dist/index.d.ts +3 -0
  33. package/dist/index.desktop-web.d.ts +2 -0
  34. package/dist/index.desktop-web.js +2 -0
  35. package/dist/index.js +3 -0
  36. package/dist/index.links.d.ts +6 -0
  37. package/dist/index.links.js +6 -0
  38. package/dist/index.panels.d.ts +9 -0
  39. package/dist/index.panels.js +8 -0
  40. package/dist/index.serve.d.ts +1 -0
  41. package/dist/index.serve.js +1 -0
  42. package/dist/index.storage.d.ts +2 -0
  43. package/dist/index.storage.js +2 -0
  44. package/dist/links/catalog.d.ts +10 -0
  45. package/dist/links/catalog.js +41 -0
  46. package/dist/links/device-oauth.d.ts +52 -0
  47. package/dist/links/device-oauth.js +255 -0
  48. package/dist/links/http.d.ts +34 -0
  49. package/dist/links/http.js +144 -0
  50. package/dist/links/service.d.ts +54 -0
  51. package/dist/links/service.js +578 -0
  52. package/dist/links/types.d.ts +6 -0
  53. package/dist/links/types.js +1 -0
  54. package/dist/mobile-remote/mobile-client-event-validator.js +5 -2
  55. package/dist/mobile-remote/pending-approvals.js +2 -0
  56. package/dist/mobile-remote/remote-host-manager.d.ts +8 -0
  57. package/dist/mobile-remote/remote-host-manager.js +90 -15
  58. package/dist/mobile-remote/room-manager.js +17 -5
  59. package/dist/panels/agent-task-host.d.ts +32 -0
  60. package/dist/panels/agent-task-host.js +457 -0
  61. package/dist/panels/agent-task-models.d.ts +27 -0
  62. package/dist/panels/agent-task-models.js +51 -0
  63. package/dist/panels/agent-task-service.d.ts +105 -0
  64. package/dist/panels/agent-task-service.js +283 -0
  65. package/dist/panels/http.d.ts +34 -0
  66. package/dist/panels/http.js +37 -0
  67. package/dist/panels/hub-binding.d.ts +12 -0
  68. package/dist/panels/hub-binding.js +174 -0
  69. package/dist/panels/management-http.d.ts +29 -0
  70. package/dist/panels/management-http.js +119 -0
  71. package/dist/panels/management.d.ts +39 -0
  72. package/dist/panels/management.js +494 -0
  73. package/dist/panels/process-files.d.ts +14 -0
  74. package/dist/panels/process-files.js +299 -0
  75. package/dist/panels/process-service.d.ts +96 -0
  76. package/dist/panels/process-service.js +501 -0
  77. package/dist/panels/runtime-services.d.ts +24 -0
  78. package/dist/panels/runtime-services.js +462 -0
  79. package/dist/panels/runtime.d.ts +51 -0
  80. package/dist/panels/runtime.js +1063 -0
  81. package/dist/panels/storage-store.d.ts +9 -0
  82. package/dist/panels/storage-store.js +154 -0
  83. package/dist/panels/types.d.ts +52 -0
  84. package/dist/panels/types.js +1 -0
  85. package/dist/project-runtime/control-server.d.ts +19 -0
  86. package/dist/project-runtime/control-server.js +300 -0
  87. package/dist/project-runtime/docker-provider.d.ts +33 -0
  88. package/dist/project-runtime/docker-provider.js +487 -0
  89. package/dist/project-runtime/lock.d.ts +6 -0
  90. package/dist/project-runtime/lock.js +43 -0
  91. package/dist/project-runtime/managed-entry.d.ts +10 -0
  92. package/dist/project-runtime/managed-entry.js +113 -0
  93. package/dist/project-runtime/manager.d.ts +40 -0
  94. package/dist/project-runtime/manager.js +249 -0
  95. package/dist/project-runtime/proxy.d.ts +22 -0
  96. package/dist/project-runtime/proxy.js +706 -0
  97. package/dist/project-runtime/registry.d.ts +54 -0
  98. package/dist/project-runtime/registry.js +286 -0
  99. package/dist/project-runtime/types.d.ts +43 -0
  100. package/dist/project-runtime/types.js +1 -0
  101. package/dist/serve/cli.d.ts +5 -0
  102. package/dist/serve/cli.js +82 -23
  103. package/dist/serve/headless-server.d.ts +11 -0
  104. package/dist/serve/headless-server.js +737 -83
  105. package/dist/session-titles-store.d.ts +17 -0
  106. package/dist/session-titles-store.js +174 -0
  107. package/dist/sessions-service.d.ts +2 -0
  108. package/dist/sessions-service.js +8 -0
  109. package/dist/static-files.js +1 -0
  110. package/dist/worker-bridge-core.js +17 -6
  111. package/package.json +19 -5
@@ -0,0 +1,457 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { createRequire } from "node:module";
3
+ import { isAbsolute } from "node:path";
4
+ import { SettingsManager } from "@cjhyy/code-shell-core";
5
+ import { getMergedCatalog } from "@cjhyy/code-shell-core/internal";
6
+ import { WorkerBridgeCore } from "../worker-bridge-core.js";
7
+ import { PanelAppAgentTaskService, } from "./agent-task-service.js";
8
+ import { buildPanelAgentTaskModelCatalog, } from "./agent-task-models.js";
9
+ // Keep the request's hard tool ceiling. Scheduling, delegation, arbitrary MCP,
10
+ // workspace switching and other panels are not granted by agent.task.
11
+ const TASK_TOOLS = new Set([
12
+ "Panel",
13
+ "Bash",
14
+ "BashOutput",
15
+ "ListShells",
16
+ "KillShell",
17
+ "Read",
18
+ "Write",
19
+ "Edit",
20
+ "MultiEdit",
21
+ "Glob",
22
+ "Grep",
23
+ "WebSearch",
24
+ "WebFetch",
25
+ "Skill",
26
+ ]);
27
+ const MAX_APPROVALS = 16;
28
+ function outcomeResult(outcome) {
29
+ if (outcome.status === "result")
30
+ return outcome.result;
31
+ if (outcome.status === "error")
32
+ throw new Error(outcome.error.message || "Agent task failed");
33
+ if (outcome.status === "timeout")
34
+ throw new Error("Agent task timed out");
35
+ throw new Error("Agent task worker stopped or could not start");
36
+ }
37
+ function progressFromEvent(event) {
38
+ switch (event.type) {
39
+ case "session_started":
40
+ return { kind: "model", status: "running", message: "Task started" };
41
+ case "stream_request_start":
42
+ return { kind: "model", status: "running", message: "AI is planning the next step" };
43
+ case "text_delta":
44
+ case "assistant_message":
45
+ return { kind: "model", status: "running", message: "AI is preparing the result" };
46
+ case "tool_use_start": {
47
+ const name = String(event.toolCall?.toolName || "Tool").slice(0, 128);
48
+ return { kind: "tool", status: "running", message: `Running ${name}`, toolName: name };
49
+ }
50
+ case "tool_result": {
51
+ const name = String(event.result?.toolName || "Tool").slice(0, 128);
52
+ return { kind: "tool", status: "completed", message: `${name} returned`, toolName: name };
53
+ }
54
+ case "error":
55
+ return { kind: "error", status: "failed", message: String(event.error || "Task failed") };
56
+ default:
57
+ return null;
58
+ }
59
+ }
60
+ /** Real independent Core workers shared by Desktop Web and standalone Hub. */
61
+ export function createPanelAgentTaskHost(options = {}) {
62
+ const instances = new Map();
63
+ let closed = false;
64
+ const models = options.models ??
65
+ ((cwd) => buildPanelAgentTaskModelCatalog(new SettingsManager(cwd, "full").get(), getMergedCatalog()));
66
+ const activeTaskCount = () => [...instances.values()].reduce((count, instance) => count +
67
+ instance.service
68
+ .list(instance.owner)
69
+ .filter((task) => ["queued", "running", "cancelling"].includes(task.status)).length, 0);
70
+ const assertAuthorized = async (instance) => {
71
+ let authorized = false;
72
+ if (!closed && !instance.closed) {
73
+ try {
74
+ authorized = await instance.scope.isAuthorized();
75
+ }
76
+ catch {
77
+ /* Fail closed. */
78
+ }
79
+ }
80
+ if (closed || instance.closed || !authorized) {
81
+ revokeInstance(instance.scope.instanceId);
82
+ throw new Error("Panel App owner is no longer authorized");
83
+ }
84
+ if (closed || instance.closed)
85
+ throw new Error("Panel App owner is no longer authorized");
86
+ };
87
+ const rpc = (running, method, params, timeoutMs = 5_000) => running.bridge.request(method, params, {
88
+ id: `panel-${randomUUID()}`,
89
+ timeoutMs,
90
+ consume: true,
91
+ settleOnExit: true,
92
+ failFast: true,
93
+ meta: { origin: "host", producer: "web-panel-agent-task" },
94
+ });
95
+ const stopWorker = (running) => {
96
+ running.cancelled = true;
97
+ running.bridge.kill();
98
+ const timer = setTimeout(() => {
99
+ // This bridge belongs to exactly one task and never respawns after close.
100
+ if (running.pid && running.bridge.hasChild()) {
101
+ try {
102
+ process.kill(running.pid, "SIGKILL");
103
+ }
104
+ catch {
105
+ /* Already exited. */
106
+ }
107
+ }
108
+ }, 500);
109
+ timer.unref();
110
+ };
111
+ function revokeInstance(instanceId) {
112
+ const instance = instances.get(instanceId);
113
+ if (!instance || instance.closed)
114
+ return;
115
+ instance.closed = true;
116
+ clearInterval(instance.timer);
117
+ instance.service.cancelApp(instance.scope.appId);
118
+ instance.approvals.clear();
119
+ for (const running of instance.running.values())
120
+ stopWorker(running);
121
+ instances.delete(instanceId);
122
+ }
123
+ const deliver = async (instance, event, payload) => {
124
+ try {
125
+ await assertAuthorized(instance);
126
+ instance.scope.emit(event, payload);
127
+ }
128
+ catch {
129
+ /* Revoked instances receive no additional task output. */
130
+ }
131
+ };
132
+ async function handleWorkerApproval(instance, running, params) {
133
+ await assertAuthorized(instance);
134
+ if (params.sessionId !== running.sessionId || typeof params.requestId !== "string")
135
+ return;
136
+ const request = params.request;
137
+ if (!request || typeof request.toolName !== "string")
138
+ return;
139
+ const pending = {
140
+ id: params.requestId,
141
+ taskId: running.taskId,
142
+ sessionId: running.sessionId,
143
+ settling: false,
144
+ ...(typeof params.connectionId === "string" ? { connectionId: params.connectionId } : {}),
145
+ ...(Number.isInteger(params.generation) ? { generation: params.generation } : {}),
146
+ };
147
+ if (request.toolName.startsWith("__")) {
148
+ let decision;
149
+ if (request.toolName === "__panel_action__" && options.onPanelAction) {
150
+ try {
151
+ const result = await options.onPanelAction(instance.scope, request.args ?? {});
152
+ await assertAuthorized(instance);
153
+ const answer = JSON.stringify(result);
154
+ if (!answer || Buffer.byteLength(answer) > 512 * 1024)
155
+ throw new Error("Panel callback returned an invalid or oversized result");
156
+ decision = { approved: true, answer };
157
+ }
158
+ catch (error) {
159
+ decision = {
160
+ approved: false,
161
+ failure: "unavailable",
162
+ reason: String(error).slice(0, 500),
163
+ };
164
+ }
165
+ }
166
+ else {
167
+ decision = {
168
+ approved: false,
169
+ failure: "unavailable",
170
+ reason: "This task host does not provide that capability",
171
+ };
172
+ }
173
+ await assertAuthorized(instance);
174
+ outcomeResult(await rpc(running, "agent/approve", {
175
+ sessionId: pending.sessionId,
176
+ requestId: pending.id,
177
+ connectionId: pending.connectionId,
178
+ generation: pending.generation,
179
+ decision,
180
+ }));
181
+ return;
182
+ }
183
+ if (instance.approvals.size >= MAX_APPROVALS)
184
+ throw new Error("Too many pending task approvals");
185
+ instance.approvals.set(pending.id, pending);
186
+ await deliver(instance, "agent.task.approvalRequested", {
187
+ taskId: running.taskId,
188
+ requestId: pending.id,
189
+ title: `批准 ${request.toolName.slice(0, 128)}`,
190
+ body: String(request.description || "").slice(0, 2_000),
191
+ toolName: request.toolName.slice(0, 128),
192
+ args: JSON.stringify(request.args ?? {}).slice(0, 12_000),
193
+ });
194
+ }
195
+ function getInstance(scope) {
196
+ if (closed)
197
+ throw new Error("Panel task host is closed");
198
+ if (!scope ||
199
+ !scope.instanceId ||
200
+ !scope.ownerId ||
201
+ !scope.appId ||
202
+ !isAbsolute(scope.cwd) ||
203
+ !isAbsolute(scope.projectPath) ||
204
+ !scope.permissions?.includes("agent.task") ||
205
+ !Array.isArray(scope.availableSkills) ||
206
+ typeof scope.isAuthorized !== "function" ||
207
+ typeof scope.emit !== "function")
208
+ throw new Error("Panel App permission denied: agent.task");
209
+ const existing = instances.get(scope.instanceId);
210
+ if (existing) {
211
+ if (["ownerId", "appId", "projectPath", "cwd"].some((key) => existing.scope[key] !==
212
+ scope[key]))
213
+ throw new Error("Panel App task owner does not match");
214
+ return existing;
215
+ }
216
+ const owner = {
217
+ guestId: 0,
218
+ ownerWebContentsId: 0,
219
+ appId: scope.appId,
220
+ appTitle: scope.appTitle,
221
+ projectPath: scope.projectPath,
222
+ cwd: scope.cwd,
223
+ bucket: `web-panel:${scope.instanceId}`,
224
+ availableSkills: [...scope.availableSkills],
225
+ };
226
+ const instance = {
227
+ scope: { ...scope },
228
+ owner,
229
+ running: new Map(),
230
+ approvals: new Map(),
231
+ cancelledSessions: new Set(),
232
+ closed: false,
233
+ };
234
+ const runtime = {
235
+ run: async (input) => {
236
+ await assertAuthorized(instance);
237
+ if ([...instances.values()].reduce((n, item) => n + item.running.size, 0) >=
238
+ (options.maxConcurrentTasks ?? 4))
239
+ throw new Error("Too many active panel tasks; wait for a task to finish");
240
+ const catalog = await models(scope.cwd);
241
+ await assertAuthorized(instance);
242
+ if (instance.cancelledSessions.has(input.sessionId))
243
+ throw new Error("Task cancelled");
244
+ if ([...instances.values()].reduce((n, item) => n + item.running.size, 0) >=
245
+ (options.maxConcurrentTasks ?? 4))
246
+ throw new Error("Too many active panel tasks; wait for a task to finish");
247
+ const model = input.model ?? catalog.defaultModel;
248
+ if (!model || !catalog.models.some((item) => item.id === model))
249
+ throw new Error("请先在设置中配置并选择可用的默认模型。");
250
+ const entryPath = options.workerEntryPath ??
251
+ createRequire(import.meta.url).resolve("@cjhyy/code-shell-core/bin/agent-server-stdio");
252
+ const running = {
253
+ taskId: input.taskId,
254
+ sessionId: input.sessionId,
255
+ cancelled: false,
256
+ };
257
+ running.bridge = new WorkerBridgeCore({
258
+ entryPath,
259
+ execPath: options.execPath,
260
+ fallbackCwd: () => scope.cwd,
261
+ buildEnv: options.buildEnv ??
262
+ (() => ({
263
+ ...process.env,
264
+ CODE_SHELL_CREDENTIAL_ACCESS: "local",
265
+ CODE_SHELL_CAPABILITY_MODULES: `${import.meta.resolve("@cjhyy/code-shell-capability-coding")}#createCodingModule`,
266
+ })),
267
+ onWorkerStarted: ({ pid }) => {
268
+ running.pid = pid;
269
+ },
270
+ });
271
+ instance.running.set(input.sessionId, running);
272
+ let latestError = "";
273
+ const unsubscribe = running.bridge.subscribeLines((line) => {
274
+ let message;
275
+ try {
276
+ message = JSON.parse(line);
277
+ }
278
+ catch {
279
+ return;
280
+ }
281
+ if (message.params?.sessionId !== input.sessionId)
282
+ return;
283
+ if (message.method === "agent/approvalRequest") {
284
+ void handleWorkerApproval(instance, running, message.params).catch(() => stopWorker(running));
285
+ }
286
+ else if (message.method === "agent/approvalResolved") {
287
+ instance.approvals.delete(message.params.requestId);
288
+ void deliver(instance, "agent.task.approvalResolved", {
289
+ taskId: input.taskId,
290
+ requestId: message.params.requestId,
291
+ });
292
+ }
293
+ else if (message.method === "agent/streamEvent") {
294
+ const event = message.params.event ?? {};
295
+ if (event.type === "error")
296
+ latestError = String(event.error || "Task failed");
297
+ const update = progressFromEvent(event);
298
+ if (update)
299
+ input.onProgress(update);
300
+ }
301
+ });
302
+ try {
303
+ await assertAuthorized(instance);
304
+ if (running.cancelled)
305
+ throw new Error("Task cancelled");
306
+ running.bridge.ensureWorker(scope.cwd);
307
+ const result = outcomeResult(await rpc(running, "agent/run", {
308
+ sessionId: input.sessionId,
309
+ cwd: scope.cwd,
310
+ bucket: owner.bucket,
311
+ task: input.prompt,
312
+ displayText: input.label,
313
+ clientMessageId: `panel-task:${scope.appId}:${input.sessionId}`,
314
+ behaviorMode: "isolatedTask",
315
+ ephemeral: true,
316
+ permissionMode: "default",
317
+ model,
318
+ toolAllowlist: input.toolNames,
319
+ skillAllowlist: input.skillNames,
320
+ maxTurns: input.maxTurns,
321
+ maxContextTokens: input.maxContextTokens,
322
+ }, options.runTimeoutMs ?? 30 * 60 * 1000));
323
+ await assertAuthorized(instance);
324
+ return {
325
+ text: typeof result?.text === "string" ? result.text : "",
326
+ reason: typeof result?.reason === "string" ? result.reason : "model_error",
327
+ ...(latestError ? { error: latestError } : {}),
328
+ ...(result?.usage &&
329
+ ["promptTokens", "completionTokens", "totalTokens"].every((key) => Number.isFinite(result.usage[key]))
330
+ ? { usage: result.usage }
331
+ : {}),
332
+ };
333
+ }
334
+ finally {
335
+ unsubscribe();
336
+ stopWorker(running);
337
+ instance.running.delete(input.sessionId);
338
+ }
339
+ },
340
+ cancel: async (sessionId) => {
341
+ instance.cancelledSessions.add(sessionId);
342
+ const running = instance.running.get(sessionId);
343
+ if (!running)
344
+ return;
345
+ running.cancelled = true;
346
+ try {
347
+ await rpc(running, "agent/cancel", { sessionId }, 1_000);
348
+ }
349
+ finally {
350
+ stopWorker(running);
351
+ }
352
+ },
353
+ close: async (sessionId) => {
354
+ instance.cancelledSessions.delete(sessionId);
355
+ const running = instance.running.get(sessionId);
356
+ if (running)
357
+ stopWorker(running);
358
+ instance.running.delete(sessionId);
359
+ for (const [id, approval] of instance.approvals)
360
+ if (approval.sessionId === sessionId)
361
+ instance.approvals.delete(id);
362
+ },
363
+ rebind: () => { },
364
+ };
365
+ instance.service = new PanelAppAgentTaskService(runtime, (_owner, task) => {
366
+ void deliver(instance, "agent.task.changed", task);
367
+ });
368
+ let checking = false;
369
+ instance.timer = setInterval(() => {
370
+ if (checking)
371
+ return;
372
+ checking = true;
373
+ void assertAuthorized(instance)
374
+ .catch(() => { })
375
+ .finally(() => {
376
+ checking = false;
377
+ });
378
+ }, options.authorizationPollMs ?? 1_000);
379
+ instance.timer.unref();
380
+ instances.set(scope.instanceId, instance);
381
+ return instance;
382
+ }
383
+ return {
384
+ async call(scope, method, params) {
385
+ const instance = getInstance(scope);
386
+ await assertAuthorized(instance);
387
+ if (method === "agent.task.models" || method === "agent.models") {
388
+ const result = await models(scope.cwd);
389
+ await assertAuthorized(instance);
390
+ return result;
391
+ }
392
+ if (method === "agent.task.start") {
393
+ const raw = (params ?? {});
394
+ if (Array.isArray(raw.toolNames) && raw.toolNames.some((tool) => !TASK_TOOLS.has(tool)))
395
+ throw new Error("A requested tool is unavailable to Panel App tasks");
396
+ const alreadyRunning = typeof raw.key === "string" &&
397
+ instance.service
398
+ .list(instance.owner)
399
+ .some((task) => task.key === raw.key && ["queued", "running", "cancelling"].includes(task.status));
400
+ if (!alreadyRunning && activeTaskCount() >= (options.maxConcurrentTasks ?? 4))
401
+ throw new Error("Too many active panel tasks; wait for a task to finish");
402
+ return instance.service.start(instance.owner, raw);
403
+ }
404
+ if (method === "agent.task.list")
405
+ return instance.service.list(instance.owner);
406
+ if (method === "agent.task.get")
407
+ return instance.service.get(instance.owner, params?.id);
408
+ if (method === "agent.task.cancel") {
409
+ const result = await instance.service.cancel(instance.owner, params?.id);
410
+ await assertAuthorized(instance);
411
+ return result;
412
+ }
413
+ // Host-private: never expose this method to an iframe's allowed SDK methods.
414
+ if (method === "agent.task.approvalRespond") {
415
+ const input = params;
416
+ const pending = typeof input?.requestId === "string"
417
+ ? instance.approvals.get(input.requestId)
418
+ : undefined;
419
+ if (!pending ||
420
+ pending.settling ||
421
+ pending.taskId !== input.taskId ||
422
+ typeof input.approved !== "boolean")
423
+ throw new Error("Task approval is unavailable or already answered");
424
+ const running = instance.running.get(pending.sessionId);
425
+ if (!running || running.cancelled)
426
+ throw new Error("Task is no longer running");
427
+ pending.settling = true;
428
+ try {
429
+ outcomeResult(await rpc(running, "agent/approve", {
430
+ sessionId: pending.sessionId,
431
+ requestId: pending.id,
432
+ connectionId: pending.connectionId,
433
+ generation: pending.generation,
434
+ decision: input.approved
435
+ ? { approved: true, scope: "once" }
436
+ : { approved: false, reason: "Declined by user" },
437
+ }));
438
+ instance.approvals.delete(pending.id);
439
+ await assertAuthorized(instance);
440
+ return true;
441
+ }
442
+ catch (error) {
443
+ pending.settling = false;
444
+ throw error;
445
+ }
446
+ }
447
+ throw new Error(`Panel App task method is unsupported: ${method}`);
448
+ },
449
+ revokeInstance,
450
+ close() {
451
+ for (const id of instances.keys())
452
+ revokeInstance(id);
453
+ closed = true;
454
+ },
455
+ activeTaskCount,
456
+ };
457
+ }
@@ -0,0 +1,27 @@
1
+ export interface PanelAgentTaskModelOption {
2
+ id: string;
3
+ providerId: string;
4
+ provider: string;
5
+ model: string;
6
+ label: string;
7
+ }
8
+ export interface PanelAgentTaskModelCatalog {
9
+ defaultModel?: string;
10
+ models: PanelAgentTaskModelOption[];
11
+ }
12
+ interface CatalogEntryLike {
13
+ id?: unknown;
14
+ displayName?: unknown;
15
+ modelPresets?: Array<{
16
+ value?: unknown;
17
+ label?: unknown;
18
+ }>;
19
+ }
20
+ /**
21
+ * Build the secret-free model catalog exposed to a Panel App.
22
+ *
23
+ * Connections are the engine's real pool keys. Credentials, base URLs, and
24
+ * parameter values deliberately never cross the guest boundary.
25
+ */
26
+ export declare function buildPanelAgentTaskModelCatalog(settings: unknown, catalog: readonly CatalogEntryLike[]): PanelAgentTaskModelCatalog;
27
+ export {};
@@ -0,0 +1,51 @@
1
+ function boundedText(value, max) {
2
+ if (typeof value !== "string")
3
+ return undefined;
4
+ const text = value.trim();
5
+ if (!text || text.length > max || /[\r\n\0]/.test(text))
6
+ return undefined;
7
+ return text;
8
+ }
9
+ /**
10
+ * Build the secret-free model catalog exposed to a Panel App.
11
+ *
12
+ * Connections are the engine's real pool keys. Credentials, base URLs, and
13
+ * parameter values deliberately never cross the guest boundary.
14
+ */
15
+ export function buildPanelAgentTaskModelCatalog(settings, catalog) {
16
+ const source = settings && typeof settings === "object" ? settings : {};
17
+ const entries = new Map(catalog.flatMap((entry) => {
18
+ const id = boundedText(entry.id, 256);
19
+ return id ? [[id, entry]] : [];
20
+ }));
21
+ const connections = Array.isArray(source.modelConnections)
22
+ ? source.modelConnections
23
+ : [];
24
+ const models = [];
25
+ const seen = new Set();
26
+ for (const connection of connections) {
27
+ if (connection?.tag !== "text")
28
+ continue;
29
+ const id = boundedText(connection.id, 256);
30
+ const providerId = boundedText(connection.catalogId, 256);
31
+ const model = boundedText(connection.model, 512);
32
+ if (!id || !providerId || !model || seen.has(id))
33
+ continue;
34
+ const entry = entries.get(providerId);
35
+ if (!entry)
36
+ continue;
37
+ const provider = boundedText(entry.displayName, 256) ?? providerId;
38
+ const preset = Array.isArray(entry.modelPresets)
39
+ ? entry.modelPresets.find((candidate) => candidate?.value === model)
40
+ : undefined;
41
+ const label = boundedText(preset?.label, 512) ?? model;
42
+ seen.add(id);
43
+ models.push({ id, providerId, provider, model, label });
44
+ }
45
+ const defaults = source.defaults && typeof source.defaults === "object"
46
+ ? source.defaults
47
+ : {};
48
+ const requestedDefault = boundedText(defaults.text, 256);
49
+ const defaultModel = requestedDefault && seen.has(requestedDefault) ? requestedDefault : undefined;
50
+ return { ...(defaultModel ? { defaultModel } : {}), models };
51
+ }
@@ -0,0 +1,105 @@
1
+ export type PanelAgentTaskStatus = "queued" | "running" | "cancelling" | "completed" | "failed" | "cancelled";
2
+ export type PanelAgentTaskActivityKind = "model" | "tool" | "plan" | "error";
3
+ export type PanelAgentTaskActivityStatus = "running" | "completed" | "failed";
4
+ export interface PanelAgentTaskActivity {
5
+ kind: PanelAgentTaskActivityKind;
6
+ status: PanelAgentTaskActivityStatus;
7
+ message: string;
8
+ toolName?: string;
9
+ at: number;
10
+ }
11
+ export interface PanelAgentTaskProgressInput {
12
+ kind: PanelAgentTaskActivityKind;
13
+ status: PanelAgentTaskActivityStatus;
14
+ message: string;
15
+ toolName?: string;
16
+ }
17
+ export interface PanelAgentTaskView {
18
+ id: string;
19
+ key?: string;
20
+ label: string;
21
+ status: PanelAgentTaskStatus;
22
+ model?: string;
23
+ skill?: string;
24
+ createdAt: number;
25
+ updatedAt: number;
26
+ completedAt?: number;
27
+ result?: {
28
+ text: string;
29
+ reason?: string;
30
+ usage?: {
31
+ promptTokens: number;
32
+ completionTokens: number;
33
+ totalTokens: number;
34
+ };
35
+ };
36
+ error?: string;
37
+ activity?: PanelAgentTaskActivity[];
38
+ }
39
+ export interface PanelAgentTaskOwner {
40
+ guestId: number;
41
+ ownerWebContentsId: number;
42
+ appId: string;
43
+ appTitle: string;
44
+ projectPath: string;
45
+ cwd: string;
46
+ bucket: string;
47
+ availableSkills: readonly string[];
48
+ }
49
+ export interface PanelAgentTaskStartInput {
50
+ prompt?: unknown;
51
+ label?: unknown;
52
+ key?: unknown;
53
+ model?: unknown;
54
+ skill?: unknown;
55
+ skills?: unknown;
56
+ toolNames?: unknown;
57
+ maxTurns?: unknown;
58
+ maxContextTokens?: unknown;
59
+ }
60
+ export interface PanelAgentTaskRuntime {
61
+ run(input: {
62
+ taskId: string;
63
+ sessionId: string;
64
+ owner: PanelAgentTaskOwner;
65
+ prompt: string;
66
+ label: string;
67
+ model?: string;
68
+ toolNames: string[];
69
+ skillNames: string[];
70
+ maxTurns: number;
71
+ maxContextTokens: number;
72
+ onProgress: (progress: PanelAgentTaskProgressInput) => void;
73
+ }): Promise<{
74
+ text: string;
75
+ reason?: string;
76
+ error?: string;
77
+ usage?: {
78
+ promptTokens: number;
79
+ completionTokens: number;
80
+ totalTokens: number;
81
+ };
82
+ }>;
83
+ cancel(sessionId: string): Promise<void>;
84
+ close(sessionId: string): Promise<void>;
85
+ rebind(sessionId: string, owner: PanelAgentTaskOwner): void;
86
+ }
87
+ export declare class PanelAppAgentTaskService {
88
+ private readonly runtime;
89
+ private readonly emitChanged;
90
+ private readonly now;
91
+ private readonly makeId;
92
+ private readonly tasks;
93
+ constructor(runtime: PanelAgentTaskRuntime, emitChanged: (owner: PanelAgentTaskOwner, task: PanelAgentTaskView) => void, now?: () => number, makeId?: () => string);
94
+ start(owner: PanelAgentTaskOwner, raw: PanelAgentTaskStartInput): PanelAgentTaskView;
95
+ list(owner: PanelAgentTaskOwner): PanelAgentTaskView[];
96
+ get(owner: PanelAgentTaskOwner, id: unknown): PanelAgentTaskView;
97
+ cancel(owner: PanelAgentTaskOwner, id: unknown): Promise<PanelAgentTaskView>;
98
+ rebind(owner: PanelAgentTaskOwner): void;
99
+ cancelApp(appId: string): void;
100
+ private execute;
101
+ private scopedTask;
102
+ private emit;
103
+ private recordProgress;
104
+ private trimHistory;
105
+ }