@spotpatch/dev-server 0.9.0 → 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.
package/README.md CHANGED
@@ -8,6 +8,8 @@ Applications should install a framework adapter such as [`@spotpatch/vite`](http
8
8
 
9
9
  The service is development-only. Production adapters must omit it together with all private endpoints and source-registration state.
10
10
 
11
+ `createDevelopmentSession` owns the common AI, read-only Ask, external handoff and middleware lifecycle for Vite, Next and Astro. Frameworks inject bridge/validation/source-projection factories, retain their own transports, and call the idempotent `close()` on shutdown. Astro source imports remain subject to the same registry, path and read-budget authorization.
12
+
11
13
  Requires Node.js `>=20.19.0`.
12
14
 
13
15
  ## 简体中文
package/dist/index.cjs CHANGED
@@ -39,6 +39,7 @@ __export(index_exports, {
39
39
  createAgentJobManager: () => createAgentJobManager,
40
40
  createConfiguredKeyAskExecutors: () => createConfiguredKeyAskExecutors,
41
41
  createContextualAskManager: () => createContextualAskManager,
42
+ createDevelopmentSession: () => createDevelopmentSession,
42
43
  createExternalHandoffService: () => createExternalHandoffService,
43
44
  createIntegrationFileChange: () => createIntegrationFileChange,
44
45
  createRuntimeAiConfig: () => createRuntimeAiConfig,
@@ -1087,6 +1088,7 @@ var import_node_path = __toESM(require("path"), 1);
1087
1088
  var import_shared5 = require("@spotpatch/shared");
1088
1089
  var import_external_agent_node2 = require("@spotpatch/shared/external-agent-node");
1089
1090
  async function syncDirectory(directory) {
1091
+ if (process.platform === "win32") return;
1090
1092
  const handle = await (0, import_promises.open)(directory, "r");
1091
1093
  try {
1092
1094
  await handle.sync();
@@ -1136,14 +1138,11 @@ async function publishExternalHandoffDescriptor(options) {
1136
1138
  } finally {
1137
1139
  await handle.close();
1138
1140
  }
1141
+ await (0, import_external_agent_node2.initializePrivateExternalHandoffFile)(temporary);
1139
1142
  await (0, import_promises.rename)(temporary, destination);
1140
1143
  temporaryExists = false;
1141
1144
  published = true;
1142
- const status = await (0, import_promises.lstat)(destination);
1143
- const uid = process.getuid?.();
1144
- if (!status.isFile() || status.isSymbolicLink() || uid === void 0 || status.uid !== uid || (status.mode & 63) !== 0) {
1145
- throw new Error("SpotPatch external Agent descriptor is not private.");
1146
- }
1145
+ const status = await (0, import_external_agent_node2.assertPrivateExternalHandoffPath)(destination, "file");
1147
1146
  descriptorIdentity = Object.freeze({ device: status.dev, inode: status.ino });
1148
1147
  await syncDirectory(directory);
1149
1148
  } catch (error) {
@@ -2457,14 +2456,14 @@ async function resolveProjectOptions(input) {
2457
2456
  "SpotPatch trustedFastMode cannot be combined with applyMode auto."
2458
2457
  );
2459
2458
  }
2460
- const checks = await resolveProjectValidationChecks({
2459
+ const checks = await (input.resolveValidationChecks ?? resolveProjectValidationChecks)({
2461
2460
  appRoot: input.appRoot,
2462
2461
  checks: resolved.ai.execution.checks,
2463
2462
  timeoutMs: resolved.ai.execution.limits.checkTimeoutMs
2464
2463
  });
2465
2464
  if (!Object.values(checks).some((check) => check.required)) {
2466
2465
  throw new RangeError(
2467
- "SpotPatch trustedFastMode requires a configured required check or a local TypeScript project with tsconfig.json."
2466
+ "SpotPatch trustedFastMode requires a configured required check or an available framework-compatible local project check."
2468
2467
  );
2469
2468
  }
2470
2469
  const ai = Object.freeze({
@@ -2578,6 +2577,7 @@ var import_node_path5 = __toESM(require("path"), 1);
2578
2577
  var import_shared10 = require("@spotpatch/shared");
2579
2578
  var import_oxc_parser = require("oxc-parser");
2580
2579
  var SOURCE_EXTENSIONS = Object.freeze([
2580
+ ".astro",
2581
2581
  ".ts",
2582
2582
  ".tsx",
2583
2583
  ".js",
@@ -2886,7 +2886,8 @@ async function captureAskReadSnapshot(options) {
2886
2886
  content: read.content
2887
2887
  });
2888
2888
  captured.push(entry);
2889
- for (const specifier of localSpecifiers(candidate.absolutePath, read.content)) {
2889
+ const specifiers = options.resolveSourceImports?.(candidate.absolutePath, read.content) ?? localSpecifiers(candidate.absolutePath, read.content);
2890
+ for (const specifier of specifiers.filter((value) => value.startsWith("."))) {
2890
2891
  options.signal?.throwIfAborted();
2891
2892
  for (const importPath of importCandidates(candidate.absolutePath, specifier)) {
2892
2893
  if (capturedPaths.has(importPath)) continue;
@@ -3022,6 +3023,7 @@ function fingerprint(request) {
3022
3023
  JSON.stringify({
3023
3024
  envelope: request.envelope,
3024
3025
  executorId: request.executorId,
3026
+ model: request.model,
3025
3027
  providerDataConsent: request.providerDataConsent
3026
3028
  })
3027
3029
  ).digest("hex");
@@ -3312,7 +3314,8 @@ function createContextualAskManager(options) {
3312
3314
  registry: options.registry,
3313
3315
  selection: job.request.envelope.selection,
3314
3316
  signal: job.controller.signal,
3315
- createHandleId: dependencies.createId
3317
+ createHandleId: dependencies.createId,
3318
+ ...options.resolveSourceImports === void 0 ? {} : { resolveSourceImports: options.resolveSourceImports }
3316
3319
  });
3317
3320
  if (job.controller.signal.aborted) {
3318
3321
  captured.dispose();
@@ -3326,6 +3329,7 @@ function createContextualAskManager(options) {
3326
3329
  job.executor.execute(
3327
3330
  {
3328
3331
  jobId: job.id,
3332
+ ...job.request.model === void 0 ? {} : { model: job.request.model },
3329
3333
  envelope: job.request.envelope,
3330
3334
  grant: captured.grant,
3331
3335
  snapshot: instrumentSnapshot(job, captured.snapshot, () => {
@@ -3445,6 +3449,9 @@ function createContextualAskManager(options) {
3445
3449
  )
3446
3450
  );
3447
3451
  if (isClosed()) throw new ContextualAskError("ASK_DISABLED");
3452
+ if (request.model !== void 0 && !capability.models?.includes(request.model)) {
3453
+ throw new ContextualAskError("ASK_EXECUTOR_UNAVAILABLE");
3454
+ }
3448
3455
  if (capability.executorId !== executor.executorId || capability.state !== "ready" || !capability.readOnlyProven) {
3449
3456
  throw new ContextualAskError("ASK_EXECUTOR_UNAVAILABLE");
3450
3457
  }
@@ -3461,7 +3468,11 @@ function createContextualAskManager(options) {
3461
3468
  createdAt,
3462
3469
  events: [],
3463
3470
  executor,
3464
- executorCapability: capability,
3471
+ executorCapability: request.model === void 0 ? capability : {
3472
+ ...capability,
3473
+ requestedModelLabel: request.model,
3474
+ effectiveModelLabel: request.model
3475
+ },
3465
3476
  fingerprint: requestFingerprint,
3466
3477
  id: dependencies.createId(),
3467
3478
  lease,
@@ -4070,6 +4081,7 @@ function nearbyContext(options) {
4070
4081
  });
4071
4082
  }
4072
4083
  function extractCodeContext(options) {
4084
+ if (options.language === "astro") return nearbyContext(options);
4073
4085
  const component = findComponentSpan(options);
4074
4086
  if (component !== void 0) {
4075
4087
  const range = componentRange(options.source, component);
@@ -4093,7 +4105,7 @@ function extractCodeContext(options) {
4093
4105
  var import_promises5 = require("fs/promises");
4094
4106
  var import_node_path6 = __toESM(require("path"), 1);
4095
4107
  var import_shared13 = require("@spotpatch/shared");
4096
- var ALLOWED_EXTENSIONS = /* @__PURE__ */ new Set([".jsx", ".tsx"]);
4108
+ var ALLOWED_EXTENSIONS = /* @__PURE__ */ new Set([".jsx", ".tsx", ".astro"]);
4097
4109
  function isMissingFileError(error) {
4098
4110
  return error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR");
4099
4111
  }
@@ -4179,7 +4191,7 @@ async function readSourceContext(options) {
4179
4191
  source,
4180
4192
  sourcePath,
4181
4193
  relativePath: toDisplayPath(await (0, import_promises6.realpath)(options.root), sourcePath),
4182
- language: extension === ".tsx" ? "tsx" : "jsx",
4194
+ language: extension === ".astro" ? "astro" : extension === ".tsx" ? "tsx" : "jsx",
4183
4195
  line: options.request.line,
4184
4196
  column: options.request.column,
4185
4197
  maxLines: Math.min(options.request.maxLines, options.maxLines),
@@ -4209,7 +4221,7 @@ function compactSourceRef(source) {
4209
4221
  });
4210
4222
  }
4211
4223
  async function authorizeSourceRef(source, registry, root) {
4212
- const markerOrigin = source.origin === "jsx-host" || source.origin === "dom-ancestor";
4224
+ const markerOrigin = source.origin === "jsx-host" || source.origin === "astro-host" || source.origin === "dom-ancestor";
4213
4225
  if (markerOrigin && (source.fileId === void 0 || source.line === void 0 || source.column === void 0)) {
4214
4226
  throw new import_shared15.SpotPatchError(import_shared15.ERROR_CODES.INVALID_REQUEST);
4215
4227
  }
@@ -4894,7 +4906,8 @@ function createDataFlowAnalyzer(options) {
4894
4906
  root: options.root,
4895
4907
  registryEpoch: options.session.id,
4896
4908
  registerSource: (absolutePath) => options.registry.register(absolutePath),
4897
- limits: options.options.dataFlow.limits
4909
+ limits: options.options.dataFlow.limits,
4910
+ ...options.projectDataFlowSource === void 0 ? {} : { projectSource: options.projectDataFlowSource }
4898
4911
  });
4899
4912
  }
4900
4913
  async function analyzeTarget(request, analyzer, options) {
@@ -5849,6 +5862,113 @@ function parseSerializedSpotPatchOptions(value) {
5849
5862
  });
5850
5863
  }
5851
5864
  }
5865
+
5866
+ // src/development-session.ts
5867
+ async function createDevelopmentSession(input) {
5868
+ const cleanups = [
5869
+ () => {
5870
+ input.registry.clear();
5871
+ }
5872
+ ];
5873
+ let closing;
5874
+ const close = () => closing ??= (async () => {
5875
+ const errors = [];
5876
+ for (const cleanup of cleanups.splice(0).reverse()) {
5877
+ try {
5878
+ await cleanup();
5879
+ } catch (error) {
5880
+ errors.push(error);
5881
+ }
5882
+ }
5883
+ if (errors.length > 0)
5884
+ throw new AggregateError(errors, "SpotPatch service cleanup failed.");
5885
+ })();
5886
+ const warn = (message) => {
5887
+ input.logger?.warn(`[spotpatch:${input.framework}] ${message}`);
5888
+ };
5889
+ try {
5890
+ const { options, root, registry } = input;
5891
+ const coordinator = createWorkspaceActivityCoordinator();
5892
+ const agentManager = options.ai === false ? void 0 : createAgentJobManager({
5893
+ ai: options.ai,
5894
+ environment: input.environment,
5895
+ root: input.executionRoot ?? root,
5896
+ coordinator
5897
+ });
5898
+ if (agentManager !== void 0) cleanups.push(() => agentManager.close());
5899
+ const preference = options.contextualAsk.defaultExecutor === void 0 ? {} : { defaultExecutor: options.contextualAsk.defaultExecutor };
5900
+ const contextualAskManager = !options.contextualAsk.enabled ? void 0 : createContextualAskManager({
5901
+ coordinator,
5902
+ enabled: true,
5903
+ registry,
5904
+ root,
5905
+ ...input.resolveSourceImports === void 0 ? {} : { resolveSourceImports: input.resolveSourceImports },
5906
+ executors: composeContextualAskExecutors({
5907
+ configuredKey: options.ai === false ? [] : createConfiguredKeyAskExecutors({
5908
+ ai: options.ai,
5909
+ environment: input.environment,
5910
+ ...preference
5911
+ }),
5912
+ managedCodex: input.createManagedAskExecutor(),
5913
+ ...preference
5914
+ })
5915
+ });
5916
+ if (contextualAskManager !== void 0)
5917
+ cleanups.push(() => contextualAskManager.close());
5918
+ const externalHandoffService = !options.externalAgent.enabled ? void 0 : createExternalHandoffService({
5919
+ framework: input.framework,
5920
+ root,
5921
+ sessionId: input.session.id
5922
+ });
5923
+ let externalAgentControl;
5924
+ if (externalHandoffService !== void 0) {
5925
+ cleanups.push(() => externalHandoffService.close());
5926
+ try {
5927
+ await externalHandoffService.start();
5928
+ } catch {
5929
+ warn("External Agent handoff is unavailable; core tools remain active.");
5930
+ }
5931
+ if (externalHandoffService.capability().brokerReady) {
5932
+ try {
5933
+ const validation = await (input.resolveValidation?.() ?? resolveManagedExecutionValidation({ ai: options.ai, appRoot: root }));
5934
+ const control = await input.createExternalAgentControl(validation);
5935
+ externalAgentControl = control;
5936
+ cleanups.push(() => control.dispose());
5937
+ } catch {
5938
+ warn("Managed Agent control is unavailable; Inbox remains active.");
5939
+ }
5940
+ }
5941
+ }
5942
+ const middleware = createSpotPatchMiddleware({
5943
+ options,
5944
+ root,
5945
+ registry,
5946
+ session: input.session,
5947
+ ...input.projectDataFlowSource === void 0 ? {} : { projectDataFlowSource: input.projectDataFlowSource },
5948
+ ...input.bootstrap === void 0 ? {} : { bootstrap: input.bootstrap },
5949
+ ...input.logger === void 0 ? {} : { logger: input.logger },
5950
+ ...input.editorLauncher === void 0 ? {} : { editorLauncher: input.editorLauncher },
5951
+ ...agentManager === void 0 ? {} : { agentManager },
5952
+ ...contextualAskManager === void 0 ? {} : { contextualAskManager },
5953
+ ...externalHandoffService === void 0 ? {} : { externalHandoffService },
5954
+ ...externalAgentControl === void 0 ? {} : { externalAgentControl }
5955
+ });
5956
+ cleanups.push(() => {
5957
+ middleware.dispose();
5958
+ });
5959
+ return Object.freeze({ middleware, close });
5960
+ } catch (error) {
5961
+ try {
5962
+ await close();
5963
+ } catch (cleanupError) {
5964
+ throw new AggregateError(
5965
+ [error, cleanupError],
5966
+ "SpotPatch service startup and cleanup failed."
5967
+ );
5968
+ }
5969
+ throw error;
5970
+ }
5971
+ }
5852
5972
  // Annotate the CommonJS export names for ESM import in node:
5853
5973
  0 && (module.exports = {
5854
5974
  ContextualAskError,
@@ -5860,6 +5980,7 @@ function parseSerializedSpotPatchOptions(value) {
5860
5980
  createAgentJobManager,
5861
5981
  createConfiguredKeyAskExecutors,
5862
5982
  createContextualAskManager,
5983
+ createDevelopmentSession,
5863
5984
  createExternalHandoffService,
5864
5985
  createIntegrationFileChange,
5865
5986
  createRuntimeAiConfig,