@cjhyy/code-shell-server 0.9.22 → 0.9.23

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 (104) hide show
  1. package/README.md +45 -1
  2. package/dist/bin/code-shell-settings-recovery.d.ts +2 -0
  3. package/dist/bin/code-shell-settings-recovery.js +4 -0
  4. package/dist/desktop-web/http-api.js +14 -0
  5. package/dist/environment-identity.d.ts +4 -0
  6. package/dist/environment-identity.js +67 -0
  7. package/dist/hub/auth-store.d.ts +2 -0
  8. package/dist/hub/auth-store.js +7 -0
  9. package/dist/index.auth.d.ts +3 -0
  10. package/dist/index.auth.js +2 -0
  11. package/dist/index.links.d.ts +1 -0
  12. package/dist/index.links.js +1 -0
  13. package/dist/index.panels.d.ts +7 -0
  14. package/dist/index.panels.js +7 -0
  15. package/dist/index.remote-relay.d.ts +5 -0
  16. package/dist/index.remote-relay.js +6 -0
  17. package/dist/index.serve.d.ts +2 -0
  18. package/dist/index.serve.js +2 -0
  19. package/dist/links/http.d.ts +3 -0
  20. package/dist/links/http.js +11 -2
  21. package/dist/links/remote-configuration.d.ts +4 -0
  22. package/dist/links/remote-configuration.js +32 -0
  23. package/dist/links/service.d.ts +6 -1
  24. package/dist/links/service.js +241 -19
  25. package/dist/mobile-remote/mobile-upload-service.d.ts +4 -0
  26. package/dist/mobile-remote/mobile-upload-service.js +69 -11
  27. package/dist/mobile-remote/remote-host-manager.d.ts +10 -5
  28. package/dist/mobile-remote/remote-host-manager.js +53 -5
  29. package/dist/panels/agent-task-service.js +9 -2
  30. package/dist/panels/automations.d.ts +49 -0
  31. package/dist/panels/automations.js +134 -0
  32. package/dist/panels/bridge-contract.d.ts +5 -3
  33. package/dist/panels/bridge-contract.js +11 -1
  34. package/dist/panels/directory-bookmarks.d.ts +9 -1
  35. package/dist/panels/directory-bookmarks.js +64 -5
  36. package/dist/panels/execution-gate.d.ts +26 -0
  37. package/dist/panels/execution-gate.js +70 -0
  38. package/dist/panels/http.d.ts +26 -2
  39. package/dist/panels/http.js +7 -1
  40. package/dist/panels/hub-automations.d.ts +31 -0
  41. package/dist/panels/hub-automations.js +352 -0
  42. package/dist/panels/management-http.d.ts +20 -2
  43. package/dist/panels/management-http.js +15 -2
  44. package/dist/panels/management.d.ts +26 -4
  45. package/dist/panels/management.js +382 -39
  46. package/dist/panels/package-mutation.d.ts +8 -0
  47. package/dist/panels/package-mutation.js +23 -0
  48. package/dist/panels/process-service.d.ts +3 -0
  49. package/dist/panels/process-service.js +77 -4
  50. package/dist/panels/resources/http.d.ts +11 -0
  51. package/dist/panels/resources/http.js +74 -0
  52. package/dist/panels/resources/references.d.ts +5 -0
  53. package/dist/panels/resources/references.js +26 -0
  54. package/dist/panels/runtime-services.js +16 -3
  55. package/dist/panels/runtime.d.ts +16 -4
  56. package/dist/panels/runtime.js +522 -64
  57. package/dist/panels/shared-tool-jobs.d.ts +40 -0
  58. package/dist/panels/shared-tool-jobs.js +37 -0
  59. package/dist/panels/storage-store.d.ts +21 -0
  60. package/dist/panels/storage-store.js +43 -0
  61. package/dist/panels/task-cookie-host.d.ts +26 -0
  62. package/dist/panels/task-cookie-host.js +145 -0
  63. package/dist/panels/task-cookies.d.ts +51 -0
  64. package/dist/panels/task-cookies.js +183 -0
  65. package/dist/panels/tool-executor.d.ts +21 -5
  66. package/dist/panels/tool-executor.js +171 -30
  67. package/dist/panels/tool-jobs-storage.d.ts +2 -0
  68. package/dist/panels/tool-jobs-storage.js +34 -0
  69. package/dist/panels/tool-jobs.d.ts +49 -3
  70. package/dist/panels/tool-jobs.js +270 -15
  71. package/dist/panels/types.d.ts +43 -0
  72. package/dist/project-runtime/backup.d.ts +26 -0
  73. package/dist/project-runtime/backup.js +500 -0
  74. package/dist/project-runtime/control-server.d.ts +4 -0
  75. package/dist/project-runtime/control-server.js +19 -1
  76. package/dist/project-runtime/docker-provider.d.ts +4 -0
  77. package/dist/project-runtime/docker-provider.js +16 -1
  78. package/dist/project-runtime/managed-entry.js +4 -0
  79. package/dist/project-runtime/registry.d.ts +2 -0
  80. package/dist/project-runtime/registry.js +15 -0
  81. package/dist/project-runtime/seccomp.d.ts +8 -0
  82. package/dist/project-runtime/seccomp.js +72 -0
  83. package/dist/project-runtime/types.d.ts +2 -0
  84. package/dist/remote-relay/byte-stream.d.ts +9 -0
  85. package/dist/remote-relay/byte-stream.js +71 -0
  86. package/dist/remote-relay/connector.d.ts +27 -0
  87. package/dist/remote-relay/connector.js +197 -0
  88. package/dist/remote-relay/local-stream.d.ts +10 -0
  89. package/dist/remote-relay/local-stream.js +120 -0
  90. package/dist/remote-relay/protocol.d.ts +39 -0
  91. package/dist/remote-relay/protocol.js +64 -0
  92. package/dist/serve/automation-worker.d.ts +16 -0
  93. package/dist/serve/automation-worker.js +126 -0
  94. package/dist/serve/cli.d.ts +3 -0
  95. package/dist/serve/cli.js +17 -0
  96. package/dist/serve/headless-server.d.ts +3 -0
  97. package/dist/serve/headless-server.js +140 -10
  98. package/dist/serve/settings-recovery-cli.d.ts +29 -0
  99. package/dist/serve/settings-recovery-cli.js +128 -0
  100. package/dist/session-http-responses.d.ts +7 -0
  101. package/dist/session-http-responses.js +40 -0
  102. package/dist/worker-bridge-core.d.ts +5 -0
  103. package/dist/worker-bridge-core.js +23 -0
  104. package/package.json +15 -6
@@ -1,9 +1,23 @@
1
1
  import { createHash } from "node:crypto";
2
- import { mkdir, lstat, realpath } from "node:fs/promises";
2
+ import { mkdir, mkdtemp, lstat, realpath, rm, writeFile } from "node:fs/promises";
3
3
  import { join } from "node:path";
4
4
  import { processLimits } from "./process-state.js";
5
5
  import { resourceRelativePath } from "./resources/directories.js";
6
6
  import { materializePanelConnections, panelConnectionIds } from "./connections.js";
7
+ import { taskCookieSelection, } from "./task-cookies.js";
8
+ function directReads(input) {
9
+ return (input.resources ?? []).filter((resource) => resource.access === "read");
10
+ }
11
+ function originalUnavailable(state, during = false) {
12
+ return state === "missing"
13
+ ? Object.assign(new Error("A read-only original is unavailable; reconnect it and retry"), {
14
+ code: "INPUT_MISSING",
15
+ retryable: true,
16
+ })
17
+ : Object.assign(new Error(during
18
+ ? "A read-only original changed while the tool ran; check it before running again"
19
+ : "A read-only original changed; select it again"), { code: "INPUT_CHANGED", retryable: false });
20
+ }
7
21
  function toolInput(value) {
8
22
  if (!value ||
9
23
  typeof value !== "object" ||
@@ -14,6 +28,8 @@ function toolInput(value) {
14
28
  "directoryArguments",
15
29
  "connectionIds",
16
30
  "connectionArgument",
31
+ "originalsArgument",
32
+ "cookieArgument",
17
33
  ].includes(key)))
18
34
  throw new Error("Invalid tool job input");
19
35
  const input = value;
@@ -25,8 +41,11 @@ function toolInput(value) {
25
41
  const paths = new Set();
26
42
  for (const resource of input.resources ?? []) {
27
43
  if (!resource ||
28
- Object.keys(resource).some((key) => !["assetId", "path"].includes(key)) ||
29
- !/^(?:asset|external)-[a-f0-9]{64}$/.test(resource.assetId))
44
+ Object.keys(resource).some((key) => !["assetId", "path", "access"].includes(key)) ||
45
+ !/^(?:asset|external)-[a-f0-9]{64}$/.test(resource.assetId) ||
46
+ (resource.access !== undefined && !["copy", "read"].includes(resource.access)) ||
47
+ // Library assets are Host custody; only a user-selected original may be read in place.
48
+ (resource.access === "read" && !resource.assetId.startsWith("external-")))
30
49
  throw new Error("Invalid tool resource");
31
50
  resourceRelativePath(resource.path);
32
51
  if (paths.has(resource.path))
@@ -40,9 +59,13 @@ function toolInput(value) {
40
59
  for (const item of input.directoryArguments ?? []) {
41
60
  if (!item ||
42
61
  !/^--[a-z][a-z0-9-]{0,63}$/.test(item.argumentName) ||
43
- !["job", "app-data"].includes(item.directory) ||
44
- Object.keys(item).some((key) => !["argumentName", "directory", "path"].includes(key)))
62
+ !["job", "app-data", "bookmark"].includes(item.directory) ||
63
+ Object.keys(item).some((key) => !["argumentName", "directory", "path", "bookmark"].includes(key)))
45
64
  throw new Error("Invalid tool directory argument");
65
+ if (item.directory === "bookmark"
66
+ ? typeof item.bookmark !== "string" || !/^[a-f0-9-]{36}$/i.test(item.bookmark)
67
+ : item.bookmark !== undefined)
68
+ throw new Error("Invalid tool directory bookmark");
46
69
  if (argumentsSeen.has(item.argumentName))
47
70
  throw new Error("Duplicate tool argument");
48
71
  argumentsSeen.add(item.argumentName);
@@ -55,9 +78,31 @@ function toolInput(value) {
55
78
  !/^--[a-z][a-z0-9-]{0,63}$/.test(input.connectionArgument) ||
56
79
  argumentsSeen.has(input.connectionArgument))
57
80
  throw new Error("Invalid connection argument");
81
+ argumentsSeen.add(input.connectionArgument);
58
82
  }
59
83
  else if (input.connectionArgument !== undefined)
60
84
  throw new Error("Connection selection is required");
85
+ if (directReads(input).length) {
86
+ if (!input.originalsArgument ||
87
+ !/^--[a-z][a-z0-9-]{0,63}$/.test(input.originalsArgument) ||
88
+ argumentsSeen.has(input.originalsArgument))
89
+ throw new Error("Invalid originals argument");
90
+ argumentsSeen.add(input.originalsArgument);
91
+ }
92
+ else if (input.originalsArgument !== undefined)
93
+ throw new Error("Invalid originals argument: no resource uses read access");
94
+ if (input.cookieArgument !== undefined) {
95
+ const value = input.cookieArgument;
96
+ if (!value ||
97
+ typeof value !== "object" ||
98
+ Array.isArray(value) ||
99
+ typeof value.argumentName !== "string" ||
100
+ !/^--[a-z][a-z0-9-]{0,63}$/.test(value.argumentName) ||
101
+ argumentsSeen.has(value.argumentName))
102
+ throw new Error("Invalid Cookie argument");
103
+ const { argumentName: _argumentName, ...selection } = value;
104
+ taskCookieSelection(selection);
105
+ }
61
106
  return input;
62
107
  }
63
108
  async function childDirectory(root, relative) {
@@ -76,32 +121,77 @@ async function childDirectory(root, relative) {
76
121
  }
77
122
  /** The only background processor: launch a reviewed tool and transport bounded JSON/files. */
78
123
  export function createPanelToolExecutor(options) {
124
+ const bookmarkDirectory = async (scope, bookmark) => {
125
+ if (!options.resolveDirectoryBookmark)
126
+ throw new Error("This Host does not support task directory bookmarks");
127
+ return options.resolveDirectoryBookmark(scope, bookmark);
128
+ };
129
+ const authorize = async (scope, input) => {
130
+ await options.authorize(scope);
131
+ await authorizeDirectRead(scope, input);
132
+ for (const directory of input.directoryArguments ?? [])
133
+ if (directory.directory === "bookmark")
134
+ await bookmarkDirectory(scope, directory.bookmark);
135
+ if (input.cookieArgument) {
136
+ if (!options.cookies)
137
+ throw new Error("This Host does not support task Cookie authorization");
138
+ const { argumentName: _argumentName, ...selection } = input.cookieArgument;
139
+ await options.cookies.check(scope, selection);
140
+ }
141
+ };
142
+ const authorizeDirectRead = async (scope, input) => {
143
+ if (!directReads(input).length)
144
+ return;
145
+ if (!options.authorizeDirectRead)
146
+ throw new Error("This host cannot hand originals to tools; copy the resource instead");
147
+ await options.authorizeDirectRead(scope);
148
+ };
149
+ const materialize = async (scope, resources, workDir, signal) => {
150
+ const handle = createHash("sha256").update(workDir).digest("hex");
151
+ try {
152
+ for (const { assetId, path } of resources) {
153
+ await options.resources.dispatch(scope, "resources.materialize", { assetId, path, directoryHandle: handle }, {
154
+ signal,
155
+ resolveDirectory: async () => {
156
+ await options.authorize(scope);
157
+ return workDir;
158
+ },
159
+ });
160
+ }
161
+ }
162
+ finally {
163
+ options.resources.releaseDirectory(scope, handle);
164
+ }
165
+ };
166
+ /** Verify each original now; keys are the Guest's names, paths never leave the Host. */
167
+ const verifiedOriginals = async (scope, input, signal) => {
168
+ const originals = Object.create(null);
169
+ for (const resource of directReads(input)) {
170
+ const reference = await options.resources.references.get(scope, resource.assetId);
171
+ if (reference.state !== "available")
172
+ throw originalUnavailable(reference.state);
173
+ originals[resource.path] = {
174
+ path: await options.resources.references.location(scope, resource.assetId, { signal }),
175
+ bytes: reference.bytes,
176
+ };
177
+ }
178
+ return originals;
179
+ };
79
180
  return {
80
181
  async prepareInput(scope, raw, workDir, signal) {
81
182
  const input = toolInput(raw);
82
- await options.authorize(scope);
83
- const handle = createHash("sha256").update(workDir).digest("hex");
84
- try {
85
- for (const resource of input.resources ?? []) {
86
- await options.resources.dispatch(scope, "resources.materialize", { ...resource, directoryHandle: handle }, {
87
- signal,
88
- resolveDirectory: async () => {
89
- await options.authorize(scope);
90
- return workDir;
91
- },
92
- });
93
- }
94
- if (input.connectionIds)
95
- await options.authorizeConnections(scope);
96
- return input;
97
- }
98
- finally {
99
- options.resources.releaseDirectory(scope, handle);
100
- }
183
+ await authorize(scope, input);
184
+ await materialize(scope, (input.resources ?? []).filter((resource) => resource.access !== "read"), workDir, signal);
185
+ // Originals are verified again and sealed at each launch, so a retry cannot run
186
+ // against a file that changed after the task was admitted.
187
+ await verifiedOriginals(scope, input, signal);
188
+ if (input.connectionIds)
189
+ await options.authorizeConnections(scope);
190
+ return input;
101
191
  },
102
192
  async execute(job, context) {
103
193
  const input = toolInput(job.input);
104
- await options.authorize(job.scope);
194
+ await authorize(job.scope, input);
105
195
  let stdout = "", outputBytes = 0, result, failed;
106
196
  let processId = "", terminal = false, stopped;
107
197
  let progress = Promise.resolve();
@@ -257,8 +347,7 @@ export function createPanelToolExecutor(options) {
257
347
  if (checking || terminal)
258
348
  return;
259
349
  checking = true;
260
- void options
261
- .authorize(job.scope)
350
+ void authorize(job.scope, input)
262
351
  .catch(() => {
263
352
  failed ??= Object.assign(new Error("Tool task authorization was revoked"), {
264
353
  code: "APP_REVOKED",
@@ -281,11 +370,17 @@ export function createPanelToolExecutor(options) {
281
370
  });
282
371
  if (entry.sha256 !== job.entry.sha256)
283
372
  throw new Error("Installed tool changed; start a new job");
373
+ await authorizeDirectRead(job.scope, input);
374
+ const originals = await verifiedOriginals(job.scope, input, context.signal);
284
375
  const directory = await options.processes.grantDirectory(owner, context.workDir);
285
376
  resourceDirectoryHandles.push(directory.handle);
286
377
  const fileArgumentHandles = [];
287
378
  for (const item of input.directoryArguments ?? []) {
288
- const root = item.directory === "job" ? context.workDir : await options.appDataDirectory(job.scope);
379
+ const root = item.directory === "job"
380
+ ? context.workDir
381
+ : item.directory === "bookmark"
382
+ ? await bookmarkDirectory(job.scope, item.bookmark)
383
+ : await options.appDataDirectory(job.scope);
289
384
  const location = await childDirectory(root, item.path);
290
385
  const grant = await options.processes.grantDirectory(owner, location);
291
386
  const argument = await options.processes.grantDirectoryArgument(owner, {
@@ -307,6 +402,47 @@ export function createPanelToolExecutor(options) {
307
402
  });
308
403
  fileArgumentHandles.push(argument.handle);
309
404
  }
405
+ if (Object.keys(originals).length) {
406
+ // A manifest rather than links: a Guest-chosen name must never redirect a tool's
407
+ // writes onto the user's file. The tool opens an original only on purpose.
408
+ await mkdir(options.sealedRoot, { recursive: true, mode: 0o700 });
409
+ const sealed = await mkdtemp(join(options.sealedRoot, "originals-"));
410
+ const cleanup = () => rm(sealed, { recursive: true, force: true }).catch(() => { });
411
+ cleanups.push(cleanup);
412
+ const path = join(sealed, "originals.json");
413
+ await writeFile(path, JSON.stringify({ originals }), { mode: 0o600, flag: "wx" });
414
+ const argument = await options.processes.grantFileArgument(owner, {
415
+ executableHandle: executable.handle,
416
+ argumentName: input.originalsArgument,
417
+ path,
418
+ cleanup,
419
+ // The approval may wait, and a reference may be forgotten or relinked
420
+ // while the child runs. Reuse the process grant's launch/live checks.
421
+ validate: async () => {
422
+ await authorize(job.scope, input);
423
+ const current = await verifiedOriginals(job.scope, input, context.signal);
424
+ for (const [key, original] of Object.entries(originals)) {
425
+ if (current[key]?.path !== original.path || current[key]?.bytes !== original.bytes)
426
+ throw originalUnavailable("changed", true);
427
+ }
428
+ },
429
+ });
430
+ fileArgumentHandles.push(argument.handle);
431
+ }
432
+ if (input.cookieArgument) {
433
+ const { argumentName, ...selection } = input.cookieArgument;
434
+ const sealed = await options.cookies.materialize(job.scope, selection);
435
+ cleanups.push(sealed.cleanup);
436
+ const argument = await options.processes.grantFileArgument(owner, {
437
+ executableHandle: executable.handle,
438
+ argumentName,
439
+ path: sealed.path,
440
+ cleanup: () => {
441
+ void sealed.cleanup().catch(() => { });
442
+ },
443
+ });
444
+ fileArgumentHandles.push(argument.handle);
445
+ }
310
446
  context.signal.throwIfAborted();
311
447
  const launched = await options.processes.start(owner, {
312
448
  executableHandle: executable.handle,
@@ -365,6 +501,11 @@ export function createPanelToolExecutor(options) {
365
501
  context.signal.throwIfAborted();
366
502
  if (failed)
367
503
  throw failed;
504
+ for (const resource of directReads(input)) {
505
+ const reference = await options.resources.references.get(job.scope, resource.assetId);
506
+ if (reference.state !== "available")
507
+ throw originalUnavailable(reference.state, true);
508
+ }
368
509
  if (receipt.code !== 0 || result === undefined)
369
510
  throw new Error("Tool did not return a completed result");
370
511
  const value = result;
@@ -398,7 +539,7 @@ export function createPanelToolExecutor(options) {
398
539
  }
399
540
  value.artifacts = assets;
400
541
  }
401
- await options.authorize(job.scope);
542
+ await authorize(job.scope, input);
402
543
  return value;
403
544
  }
404
545
  finally {
@@ -414,7 +555,7 @@ export function createPanelToolExecutor(options) {
414
555
  options.resources.releaseDirectory(job.scope, handle);
415
556
  options.releaseOwner(owner);
416
557
  for (const cleanup of cleanups)
417
- cleanup();
558
+ await cleanup();
418
559
  }
419
560
  },
420
561
  };
@@ -11,6 +11,8 @@ export declare class ToolJobStorage {
11
11
  directory(id?: string, work?: boolean, create?: boolean): Promise<string>;
12
12
  read(path: string, limit: number): Promise<unknown>;
13
13
  write(id: string, value: unknown, limit: number): Promise<void>;
14
+ readQueueCatalog(limit: number): Promise<unknown>;
15
+ writeQueueCatalog(value: unknown, limit: number): Promise<void>;
14
16
  close(): Promise<void>;
15
17
  remove(id: string): Promise<void>;
16
18
  }
@@ -149,6 +149,40 @@ export class ToolJobStorage {
149
149
  await rm(temporary, { force: true }).catch(() => { });
150
150
  }
151
151
  }
152
+ async readQueueCatalog(limit) {
153
+ try {
154
+ return await this.read(join(await this.directory(), "queues.json"), limit);
155
+ }
156
+ catch (error) {
157
+ if (error.code === "ENOENT")
158
+ return [];
159
+ throw error;
160
+ }
161
+ }
162
+ async writeQueueCatalog(value, limit) {
163
+ const directory = await this.directory();
164
+ const temporary = join(directory, `${randomUUID()}.tmp`);
165
+ try {
166
+ const file = await open(temporary, "wx", 0o600);
167
+ try {
168
+ await file.writeFile(JSON.stringify(toolJobJson(value, limit)));
169
+ await file.sync();
170
+ }
171
+ finally {
172
+ await file.close();
173
+ }
174
+ await this.directory();
175
+ await rename(temporary, join(directory, "queues.json"));
176
+ const parent = await open(directory, "r").catch(() => null);
177
+ if (parent) {
178
+ await parent.sync().catch(() => { });
179
+ await parent.close();
180
+ }
181
+ }
182
+ finally {
183
+ await rm(temporary, { force: true }).catch(() => { });
184
+ }
185
+ }
152
186
  async close() {
153
187
  if (!this.nonce)
154
188
  return;
@@ -1,6 +1,8 @@
1
1
  export declare const toolJobLimits: Readonly<{
2
2
  maxJobs: 2000;
3
- maxQueuedPerScope: 32;
3
+ maxQueuedPerScope: 128;
4
+ maxQueues: 512;
5
+ maxQueueCatalogBytes: number;
4
6
  maxConcurrent: 2;
5
7
  maxInputBytes: number;
6
8
  maxResultBytes: number;
@@ -13,10 +15,30 @@ export interface ToolJobScope {
13
15
  projectPath: string;
14
16
  revision: string;
15
17
  }
18
+ /** Scope-owned scheduling; pausing stops admission to execution, not active processes. */
19
+ export interface ToolQueueState {
20
+ revision: number;
21
+ paused: boolean;
22
+ maxConcurrent: number;
23
+ }
24
+ export interface ToolQueueWriteResult {
25
+ saved: boolean;
26
+ queue: ToolQueueState;
27
+ }
28
+ export interface ToolQueueUpdate {
29
+ expectedRevision: number;
30
+ paused: boolean;
31
+ maxConcurrent: number;
32
+ }
16
33
  export interface ToolJobEntry {
17
34
  name: string;
18
35
  sha256: string;
19
36
  }
37
+ /** Host-selected package identity, never supplied by the task request. */
38
+ export interface ToolJobPackage {
39
+ version: string;
40
+ packageDigest: string;
41
+ }
20
42
  export interface ToolJobProgress {
21
43
  fraction?: number;
22
44
  stage?: string;
@@ -27,6 +49,7 @@ export interface ToolJob {
27
49
  id: string;
28
50
  scope: ToolJobScope;
29
51
  entry: ToolJobEntry;
52
+ package?: ToolJobPackage;
30
53
  input: unknown;
31
54
  recovery: "manual" | "retry";
32
55
  status: ToolJobStatus;
@@ -44,6 +67,7 @@ export interface ToolJob {
44
67
  };
45
68
  result?: unknown;
46
69
  }
70
+ export type ToolJobEvent = Omit<ToolJob, "input" | "result">;
47
71
  export interface ToolJobRequest {
48
72
  entry: ToolJobEntry;
49
73
  input: unknown;
@@ -62,6 +86,8 @@ export interface PanelToolJobServiceOptions {
62
86
  /** Freeze authorized inputs into workDir; persist only relative names and safe JSON. */
63
87
  prepareInput?(scope: ToolJobScope, input: unknown, workDir: string, signal: AbortSignal): Promise<unknown>;
64
88
  isAuthorized?(scope: ToolJobScope): boolean | Promise<boolean>;
89
+ /** Resolve and inspect the authorized project package, including its full content digest. */
90
+ describePackage?(scope: ToolJobScope): Promise<ToolJobPackage>;
65
91
  onEvent?(job: ToolJob): void;
66
92
  now?: () => number;
67
93
  }
@@ -70,6 +96,7 @@ export declare class PanelToolJobService {
70
96
  private readonly options;
71
97
  private readonly storage;
72
98
  private readonly jobs;
99
+ private readonly queues;
73
100
  private readonly active;
74
101
  private readonly preparing;
75
102
  private ready?;
@@ -77,28 +104,47 @@ export declare class PanelToolJobService {
77
104
  private stopping;
78
105
  private pumping;
79
106
  private shutdownPromise?;
107
+ private readonly unregisterExecution;
80
108
  constructor(options: PanelToolJobServiceOptions);
81
- activeCount(): number;
109
+ private readonly listeners;
110
+ /** Trusted Host subscription. Transports must recheck their viewer's authorization. */
111
+ subscribe(rawScope: ToolJobScope, send: (job: ToolJobEvent) => void): () => void;
112
+ private executionScopes;
113
+ activeCount(projectPath?: string): number;
82
114
  private now;
83
115
  private exclusive;
84
116
  private authorize;
117
+ private describePackage;
118
+ private verifyPackage;
119
+ private readOnly;
85
120
  initialize(): Promise<void>;
86
121
  private load;
87
122
  private commit;
88
123
  private lookup;
124
+ /** Scoped existence check for transports merging read-only legacy history. */
125
+ has(rawScope: ToolJobScope, id: string): Promise<boolean>;
89
126
  list(rawScope: ToolJobScope): Promise<Array<ToolJob & {
90
127
  readOnly: boolean;
91
128
  }>>;
92
129
  get(rawScope: ToolJobScope, id: string): Promise<ToolJob & {
93
130
  readOnly: boolean;
94
131
  }>;
132
+ find(rawScope: ToolJobScope, requestKey: string): Promise<ToolJob | null>;
133
+ private queueFor;
134
+ getQueue(rawScope: ToolJobScope): Promise<ToolQueueState>;
135
+ setQueue(rawScope: ToolJobScope, update: ToolQueueUpdate): Promise<ToolQueueWriteResult>;
136
+ private runnable;
137
+ private pendingCount;
95
138
  start(rawScope: ToolJobScope, request: ToolJobRequest, signal?: AbortSignal): Promise<ToolJob>;
139
+ private startAdmitted;
96
140
  private schedule;
97
141
  private pump;
98
142
  private run;
99
143
  cancel(rawScope: ToolJobScope, id: string): Promise<ToolJob>;
100
144
  retry(rawScope: ToolJobScope, id: string): Promise<ToolJob>;
101
- cancelApp(appId: string): Promise<void>;
145
+ private retryAdmitted;
146
+ cancelProject(projectPath: string, appId?: string): Promise<void>;
147
+ cancelApp(appId: string, projectPath?: string): Promise<void>;
102
148
  shutdown(): Promise<void>;
103
149
  private stop;
104
150
  }