@cjhyy/code-shell-server 0.9.44 → 0.9.47

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 (94) hide show
  1. package/dist/attachment-service.d.ts +2 -0
  2. package/dist/attachment-service.js +40 -27
  3. package/dist/bin/code-shell-link-cleanup.d.ts +2 -0
  4. package/dist/bin/code-shell-link-cleanup.js +3 -0
  5. package/dist/desktop-web/http-api.d.ts +3 -0
  6. package/dist/desktop-web/http-api.js +16 -1
  7. package/dist/hub/configuration.d.ts +6 -3
  8. package/dist/hub/configuration.js +19 -6
  9. package/dist/hub/mcp-probe.d.ts +4 -24
  10. package/dist/hub/mcp-probe.js +18 -389
  11. package/dist/hub/model-probe.d.ts +2 -20
  12. package/dist/hub/model-probe.js +2 -193
  13. package/dist/hub/run-replay.d.ts +7 -1
  14. package/dist/hub/run-replay.js +10 -2
  15. package/dist/hub/session-management.js +18 -53
  16. package/dist/hub/skills-management.js +6 -8
  17. package/dist/hub/uploads.d.ts +10 -0
  18. package/dist/hub/uploads.js +107 -15
  19. package/dist/index.desktop-web.d.ts +1 -0
  20. package/dist/index.desktop-web.js +1 -0
  21. package/dist/index.links.d.ts +3 -0
  22. package/dist/index.links.js +2 -0
  23. package/dist/index.panels.d.ts +1 -0
  24. package/dist/index.panels.js +1 -0
  25. package/dist/index.serve.d.ts +1 -0
  26. package/dist/index.serve.js +1 -0
  27. package/dist/index.storage.d.ts +1 -0
  28. package/dist/index.storage.js +1 -0
  29. package/dist/links/catalog.js +2 -0
  30. package/dist/links/diagnostics.d.ts +60 -0
  31. package/dist/links/diagnostics.js +184 -0
  32. package/dist/links/http.d.ts +10 -8
  33. package/dist/links/http.js +144 -125
  34. package/dist/links/legacy-cleanup-cli.d.ts +3 -0
  35. package/dist/links/legacy-cleanup-cli.js +64 -0
  36. package/dist/links/providers/defaults.d.ts +5 -0
  37. package/dist/links/providers/defaults.js +782 -0
  38. package/dist/links/providers/index.d.ts +7 -0
  39. package/dist/links/providers/index.js +22 -0
  40. package/dist/links/remote-catalog.d.ts +10 -3
  41. package/dist/links/remote-catalog.js +76 -29
  42. package/dist/links/runtime/operation.d.ts +9 -0
  43. package/dist/links/runtime/operation.js +16 -0
  44. package/dist/links/service.d.ts +18 -2
  45. package/dist/links/service.js +593 -62
  46. package/dist/links/types.d.ts +21 -1
  47. package/dist/mobile-remote/codex-parse.d.ts +3 -1
  48. package/dist/mobile-remote/codex-parse.js +45 -11
  49. package/dist/mobile-remote/codex-room-agent.d.ts +0 -6
  50. package/dist/mobile-remote/codex-room-agent.js +30 -32
  51. package/dist/mobile-remote/mobile-chat-turn.d.ts +1 -0
  52. package/dist/mobile-remote/mobile-chat-turn.js +3 -0
  53. package/dist/mobile-remote/mobile-client-event-validator.js +21 -0
  54. package/dist/mobile-remote/mobile-history.d.ts +10 -1
  55. package/dist/mobile-remote/mobile-history.js +24 -16
  56. package/dist/mobile-remote/remote-host-manager.d.ts +10 -0
  57. package/dist/mobile-remote/remote-host-manager.js +50 -13
  58. package/dist/mobile-remote/resident-agent.js +38 -22
  59. package/dist/mobile-remote/room-manager.d.ts +23 -13
  60. package/dist/mobile-remote/room-manager.js +106 -22
  61. package/dist/mobile-remote/room-model-tracing.d.ts +18 -0
  62. package/dist/mobile-remote/room-model-tracing.js +196 -0
  63. package/dist/mobile-remote/room-process-stop.d.ts +3 -0
  64. package/dist/mobile-remote/room-process-stop.js +28 -0
  65. package/dist/model-trace-history.d.ts +29 -0
  66. package/dist/model-trace-history.js +198 -0
  67. package/dist/model-trace-http.d.ts +14 -0
  68. package/dist/model-trace-http.js +105 -0
  69. package/dist/panels/agent-task-host.js +1 -0
  70. package/dist/panels/agent-task-service.d.ts +2 -1
  71. package/dist/panels/agent-task-service.js +10 -3
  72. package/dist/panels/directory-bookmarks.js +62 -29
  73. package/dist/panels/execution-gate.d.ts +19 -1
  74. package/dist/panels/execution-gate.js +33 -5
  75. package/dist/panels/http.d.ts +1 -0
  76. package/dist/panels/hub-automations.js +1 -0
  77. package/dist/panels/hub-binding.js +5 -5
  78. package/dist/panels/model-trace-channel.d.ts +21 -0
  79. package/dist/panels/model-trace-channel.js +110 -0
  80. package/dist/panels/model-traces.d.ts +39 -0
  81. package/dist/panels/model-traces.js +222 -0
  82. package/dist/panels/process-service.d.ts +12 -2
  83. package/dist/panels/process-service.js +49 -6
  84. package/dist/panels/resources/uploads.js +15 -6
  85. package/dist/panels/runtime.d.ts +1 -0
  86. package/dist/panels/runtime.js +66 -8
  87. package/dist/panels/task-cookies.js +8 -16
  88. package/dist/panels/tool-jobs.d.ts +2 -0
  89. package/dist/panels/tool-jobs.js +28 -3
  90. package/dist/serve/cli.js +8 -1
  91. package/dist/serve/headless-server.js +68 -14
  92. package/dist/sessions-service.js +17 -27
  93. package/dist/worker-bridge-core.js +11 -0
  94. package/package.json +8 -7
@@ -65,6 +65,8 @@ export declare const DRAFT_ATTACHMENT_TTL_MS: number;
65
65
  export declare const SENT_ATTACHMENT_TTL_MS: number;
66
66
  export declare const MAX_STAGED_IMAGE_BYTES: number;
67
67
  export declare const MAX_STAGED_FILE_BYTES: number;
68
+ /** Whether image staging accepts this MIME type (after normalization). */
69
+ export declare function isStageableImageMime(mime: string): boolean;
68
70
  export declare function stageImageDataUrl(input: StageImageDataUrlInput): Promise<InputAttachmentMeta>;
69
71
  /** Stage already-transferred bytes without expanding them back through base64. */
70
72
  export declare function stageImageBytes(input: StageImageBytesInput): Promise<InputAttachmentMeta>;
@@ -1,4 +1,4 @@
1
- import { createHash } from "node:crypto";
1
+ import { createHash, randomUUID } from "node:crypto";
2
2
  import { constants as fsConstants } from "node:fs";
3
3
  import { appendFile, lstat, mkdir, open, readdir, realpath, rename, rm, stat, writeFile, } from "node:fs/promises";
4
4
  import { basename, extname, join, relative, resolve, sep } from "node:path";
@@ -49,7 +49,10 @@ const IMAGE_MIME_EXT = {
49
49
  "image/gif": ".gif",
50
50
  };
51
51
  const ALLOWED_IMAGE_MIMES = new Set(Object.keys(IMAGE_MIME_EXT));
52
- let idCounter = 0;
52
+ /** Whether image staging accepts this MIME type (after normalization). */
53
+ export function isStageableImageMime(mime) {
54
+ return ALLOWED_IMAGE_MIMES.has(normalizeMime(mime));
55
+ }
53
56
  export async function stageImageDataUrl(input) {
54
57
  const parsed = parseDataUrl(input.dataUrl);
55
58
  if (!parsed || !ALLOWED_IMAGE_MIMES.has(parsed.mime)) {
@@ -128,9 +131,8 @@ async function stageImageBuffer(input, mime, buffer) {
128
131
  }
129
132
  const relPath = normalizePath(relative(cwd, absPath));
130
133
  const createdAt = Date.now();
131
- idCounter += 1;
132
134
  const meta = {
133
- id: `att_${sha16}_${idCounter.toString(36)}`,
135
+ id: `att_${sha16}_${randomUUID()}`,
134
136
  sessionId: input.sessionId,
135
137
  kind: "image",
136
138
  origin: input.origin,
@@ -181,9 +183,8 @@ export async function stageFileBytes(input) {
181
183
  }
182
184
  const relPath = normalizePath(relative(cwd, absPath));
183
185
  const createdAt = Date.now();
184
- idCounter += 1;
185
186
  const meta = {
186
- id: `att_${sha16}_${idCounter.toString(36)}`,
187
+ id: `att_${sha16}_${randomUUID()}`,
187
188
  sessionId: input.sessionId,
188
189
  kind: "file",
189
190
  origin: input.origin,
@@ -286,29 +287,41 @@ export async function cleanupAttachments(input) {
286
287
  sessionsRemoved.push(sessionId);
287
288
  continue;
288
289
  }
289
- const latest = latestAttachmentState(records);
290
- for (const state of latest.values()) {
291
- const createdAt = state.createdAt ?? 0;
292
- const sentAt = state.sentAt;
293
- const isSent = typeof sentAt === "number";
294
- const ttl = isSent ? SENT_ATTACHMENT_TTL_MS : DRAFT_ATTACHMENT_TTL_MS;
295
- const basis = isSent ? sentAt : createdAt;
296
- if (!basis || now - basis < ttl)
297
- continue;
298
- if (!state.absPath || !(await pathExistsInside(state.absPath, root)))
290
+ const byPath = new Map();
291
+ for (const state of latestAttachmentState(records).values()) {
292
+ if (!state.absPath)
299
293
  continue;
300
- await rm(state.absPath, { force: true }).catch(() => undefined);
301
- removed.push(state.absPath);
302
- await appendManifest(sessionDir, {
303
- event: "cleanup",
304
- id: state.id,
305
- sessionId,
306
- absPath: state.absPath,
307
- relPath: state.relPath,
308
- path: state.path,
309
- sha256: state.sha256,
310
- cleanedAt: now,
294
+ const references = byPath.get(state.absPath) ?? [];
295
+ references.push(state);
296
+ byPath.set(state.absPath, references);
297
+ }
298
+ for (const [absPath, references] of byPath) {
299
+ const expired = references.filter((state) => {
300
+ const isSent = typeof state.sentAt === "number";
301
+ const ttl = isSent ? SENT_ATTACHMENT_TTL_MS : DRAFT_ATTACHMENT_TTL_MS;
302
+ const basis = isSent ? state.sentAt : (state.createdAt ?? 0);
303
+ return !!basis && now - basis >= ttl;
311
304
  });
305
+ if (expired.length === 0 || !(await pathExistsInside(absPath, root)))
306
+ continue;
307
+ // Staging deduplicates image bytes across IDs at this same canonical path.
308
+ // Retire expired IDs independently; a live or unknown-age reference keeps the file.
309
+ if (expired.length === references.length) {
310
+ await rm(absPath, { force: true }).catch(() => undefined);
311
+ removed.push(absPath);
312
+ }
313
+ for (const state of expired) {
314
+ await appendManifest(sessionDir, {
315
+ event: "cleanup",
316
+ id: state.id,
317
+ sessionId,
318
+ absPath: state.absPath,
319
+ relPath: state.relPath,
320
+ path: state.path,
321
+ sha256: state.sha256,
322
+ cleanedAt: now,
323
+ });
324
+ }
312
325
  }
313
326
  }
314
327
  return { removed, sessionsRemoved };
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { runLegacyLinkCleanupCli } from "../links/legacy-cleanup-cli.js";
3
+ process.exitCode = await runLegacyLinkCleanupCli();
@@ -1,4 +1,5 @@
1
1
  import type { IncomingMessage, ServerResponse } from "node:http";
2
+ import type { ModelTraceListQuery } from "@cjhyy/code-shell-core/internal";
2
3
  import type { TrustedDeviceStore } from "../mobile-remote/trusted-device-store.js";
3
4
  export interface DesktopWebRequestContext {
4
5
  deviceId: string;
@@ -14,6 +15,8 @@ export interface DesktopWebApiOptions {
14
15
  sessionRootDir: string;
15
16
  /** Input is untrusted. Return only a workspace already known to Desktop. */
16
17
  resolveWorkspace: (input: string | undefined, deviceId: string) => string | undefined | Promise<string | undefined>;
18
+ /** Related diagnostic scopes are resolved by Desktop's project authority, per device. */
19
+ resolveModelTraceScopes?: (cwd: string, deviceId: string) => ModelTraceListQuery["workspaceScopes"] | Promise<ModelTraceListQuery["workspaceScopes"]>;
17
20
  /**
18
21
  * The existing Desktop AgentBridge owns this run/configuration admission lock.
19
22
  * Reject active runs with status 409, hold the lock through write + hot reload,
@@ -1,6 +1,8 @@
1
1
  import { describeEnvironment, environmentIdentity } from "../environment-identity.js";
2
2
  import { randomBytes, randomUUID } from "node:crypto";
3
3
  import { isAbsolute } from "node:path";
4
+ import { join } from "node:path";
5
+ import { createModelTraceHttp } from "../model-trace-http.js";
4
6
  import { createHubConfiguration, HubConfigurationError } from "../hub/configuration.js";
5
7
  import { createHubFiles } from "../hub/files.js";
6
8
  import { createHubMcpConfiguration } from "../hub/mcp-configuration.js";
@@ -180,6 +182,8 @@ export function createDesktopWebApi(options) {
180
182
  function createServices(cwd) {
181
183
  const shared = {
182
184
  cwd,
185
+ traceStorageDir: join(options.sessionRootDir, ".model-traces"),
186
+ traceSource: "desktop-web",
183
187
  isAuthorized: authorized,
184
188
  ownerId: owner,
185
189
  withMutation: async (write) => {
@@ -213,12 +217,23 @@ export function createDesktopWebApi(options) {
213
217
  isRunning: options.isRunning,
214
218
  onChanged: (id) => options.onSessionsChanged?.(cwd, id),
215
219
  });
220
+ const traces = createModelTraceHttp({
221
+ storageDir: join(options.sessionRootDir, ".model-traces"),
222
+ workspacePath: cwd,
223
+ isAuthorized: authorized,
224
+ resolveWorkspaceScopes: async (request) => {
225
+ const identity = identities.get(request);
226
+ if (!identity || !(await authorized(request)))
227
+ return undefined;
228
+ return options.resolveModelTraceScopes?.(cwd, identity.session.deviceId);
229
+ },
230
+ });
216
231
  return {
217
232
  configuration,
218
233
  mcp,
219
234
  active: 0,
220
235
  async handle(request, response) {
221
- for (const service of [configuration, skills, mcp, files, history])
236
+ for (const service of [configuration, skills, mcp, files, history, traces])
222
237
  if (await service.handle(request, response))
223
238
  return true;
224
239
  return false;
@@ -1,5 +1,5 @@
1
1
  import type { IncomingMessage, ServerResponse } from "node:http";
2
- import { probeConfiguredModel } from "./model-probe.js";
2
+ import { probeConfiguredModel } from "@cjhyy/code-shell-host";
3
3
  export declare class HubConfigurationError extends Error {
4
4
  readonly status: number;
5
5
  constructor(status: number, message: string);
@@ -13,6 +13,9 @@ export interface HubConfigurationOptions {
13
13
  ownerId?: (request: IncomingMessage) => Promise<string | undefined>;
14
14
  /** Test seam; production always uses the shared Core client. */
15
15
  probe?: typeof probeConfiguredModel;
16
+ /** Trusted Host route for auxiliary connection probes; never browser-selected. */
17
+ traceStorageDir?: string;
18
+ traceSource?: string;
16
19
  }
17
20
  /** The caller must authenticate and check Origin before dispatching any request here. */
18
21
  export declare function createHubConfiguration(options: HubConfigurationOptions): {
@@ -31,7 +34,7 @@ export declare function createHubConfiguration(options: HubConfigurationOptions)
31
34
  id: string;
32
35
  revision: string;
33
36
  catalogId: string;
34
- tag: "image" | "text" | "video" | "audio" | "speech";
37
+ tag: "image" | "speech" | "audio" | "text" | "video";
35
38
  model: string;
36
39
  baseUrl: string;
37
40
  hasApiKey: boolean;
@@ -48,7 +51,7 @@ export declare function createHubConfiguration(options: HubConfigurationOptions)
48
51
  disabledReason?: string | undefined;
49
52
  name: string;
50
53
  description: string;
51
- source: "user" | "project" | "plugin" | "panel-app";
54
+ source: "project" | "user" | "plugin" | "panel-app";
52
55
  enabled: boolean;
53
56
  }[];
54
57
  mcpServers: {
@@ -2,7 +2,8 @@ import { createHash, randomUUID } from "node:crypto";
2
2
  import { join } from "node:path";
3
3
  import { SettingsManager, invalidateSkillCache, mergePluginMcpServers, scanSkills, userHome, } from "@cjhyy/code-shell-core";
4
4
  import { computeEffectiveDisabledLists, getMergedCatalog } from "@cjhyy/code-shell-core/internal";
5
- import { probeConfiguredModel } from "./model-probe.js";
5
+ import { withModelTraceContext } from "@cjhyy/code-shell-core/internal";
6
+ import { probeConfiguredModel } from "@cjhyy/code-shell-host";
6
7
  const ROOT = "/api/v1/configuration";
7
8
  const MAX_BODY_BYTES = 64 * 1024;
8
9
  const MAX_SKILL_BYTES = 2 * 1024 * 1024;
@@ -173,19 +174,26 @@ export function createHubConfiguration(options) {
173
174
  }
174
175
  function updateDefaults(input) {
175
176
  knownFields(input, ["text", "auxText"]);
176
- const text = boundedString(input.text, "text", 128);
177
+ // Each field is optional so a form submits only what it changed; the merge
178
+ // happens here under the configuration lock, and a stale form can no longer
179
+ // revert a default another device saved meanwhile.
180
+ if (input.text === undefined && input.auxText === undefined) {
181
+ throw new HubConfigurationError(400, "text or auxText is required");
182
+ }
183
+ const text = input.text === undefined ? undefined : boundedString(input.text, "text", 128);
177
184
  const auxText = input.auxText === undefined || input.auxText === null
178
185
  ? input.auxText
179
186
  : boundedString(input.auxText, "auxText", 128);
180
187
  const manager = settingsManager();
181
188
  manager.mutateSettingsForScope("local", options.cwd, (current) => {
182
189
  const effective = settingsManager().get();
183
- requireTextConnection(effective, text);
190
+ if (text !== undefined)
191
+ requireTextConnection(effective, text);
184
192
  if (auxText)
185
193
  requireTextConnection(effective, auxText);
186
194
  current.defaults = {
187
195
  ...objectOrEmpty(current.defaults),
188
- text,
196
+ ...(text !== undefined ? { text } : {}),
189
197
  // Empty means the core background-model resolver follows the main model.
190
198
  ...(auxText !== undefined ? { auxText: auxText ?? "" } : {}),
191
199
  };
@@ -377,9 +385,14 @@ export function createHubConfiguration(options) {
377
385
  try {
378
386
  // Register before this final auth await, so a concurrent revoke also aborts this probe.
379
387
  await authorize(req);
380
- const result = await (options.probe ?? probeConfiguredModel)(settings, id, {
388
+ const result = await withModelTraceContext({
389
+ ...(options.traceStorageDir ? { storageDir: options.traceStorageDir } : {}),
390
+ workspacePath: options.cwd,
391
+ source: options.traceSource ?? "server",
392
+ purpose: "connection_probe",
393
+ }, () => (options.probe ?? probeConfiguredModel)(settings, id, {
381
394
  signal: controller.signal,
382
- });
395
+ }));
383
396
  await authorize(req);
384
397
  json(res, 200, result);
385
398
  }
@@ -1,24 +1,4 @@
1
- import { type MCPServerConfig } from "@cjhyy/code-shell-core";
2
- export interface HubMcpProbeResult {
3
- name: string;
4
- status: "ok" | "error" | "cancelled";
5
- checkedAt: string;
6
- durationMs: number;
7
- toolCount?: number;
8
- truncated?: boolean;
9
- tools?: {
10
- name: string;
11
- description?: string;
12
- allowed: boolean;
13
- }[];
14
- error?: {
15
- code: string;
16
- message: string;
17
- };
18
- }
19
- /** Explicit, bounded sibling connection; configuration reads never invoke this function. */
20
- export declare function probeHubMcp(config: MCPServerConfig, options: {
21
- cwd: string;
22
- signal: AbortSignal;
23
- timeoutMs?: number;
24
- }): Promise<HubMcpProbeResult>;
1
+ import { probeMcp, type McpProbeResult } from "@cjhyy/code-shell-host";
2
+ export type HubMcpProbeResult = McpProbeResult;
3
+ /** HTTP authority and cancellation stay in mcp-configuration; only server-facing wording lives here. */
4
+ export declare function probeHubMcp(...args: Parameters<typeof probeMcp>): Promise<HubMcpProbeResult>;