@gmickel/gno 2.10.1 → 2.12.0

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 (112) hide show
  1. package/README.md +11 -11
  2. package/assets/skill/SKILL.md +27 -15
  3. package/assets/skill/cli-reference.md +6 -5
  4. package/assets/skill/examples.md +16 -12
  5. package/assets/skill/mcp-reference.md +2 -1
  6. package/assets/spa-production.json.gz +0 -0
  7. package/browser-extension/artifacts/{gno-browser-clipper-v2.10.1.zip → gno-browser-clipper-v2.12.0.zip} +0 -0
  8. package/browser-extension/artifacts/gno-browser-clipper-v2.12.0.zip.sha256 +1 -0
  9. package/browser-extension/dist/manifest.json +1 -1
  10. package/package.json +10 -5
  11. package/spec/cli.md +127 -35
  12. package/spec/evals-agentic.md +3 -1
  13. package/spec/mcp.md +36 -6
  14. package/spec/output-schemas/ask.schema.json +8 -0
  15. package/spec/output-schemas/claim-verification.schema.json +8 -2
  16. package/spec/output-schemas/context-capsule-v1.schema.json +6 -1
  17. package/spec/output-schemas/context-capsule-verification.schema.json +4 -2
  18. package/spec/output-schemas/get.schema.json +4 -0
  19. package/spec/output-schemas/metadata-summary.schema.json +60 -0
  20. package/spec/output-schemas/multi-get.schema.json +4 -0
  21. package/spec/output-schemas/search-result.schema.json +4 -0
  22. package/spec/output-schemas/search-results.schema.json +4 -0
  23. package/spec/output-schemas/status.schema.json +28 -0
  24. package/src/app/constants.ts +16 -0
  25. package/src/app/context-agent-projection.ts +10 -1
  26. package/src/app/context-format.ts +1 -0
  27. package/src/app/verified-ask.ts +2 -0
  28. package/src/cli/commands/agents/block.ts +10 -9
  29. package/src/cli/commands/agents/commands.ts +6 -5
  30. package/src/cli/commands/agents/engine.ts +15 -2
  31. package/src/cli/commands/agents/harnesses.ts +21 -14
  32. package/src/cli/commands/ask-format.ts +18 -1
  33. package/src/cli/commands/ask.ts +4 -5
  34. package/src/cli/commands/cleanup.ts +46 -6
  35. package/src/cli/commands/collection/remove.ts +1 -0
  36. package/src/cli/commands/collection/rename.ts +1 -0
  37. package/src/cli/commands/daemon.ts +5 -1
  38. package/src/cli/commands/doctor.ts +37 -0
  39. package/src/cli/commands/get.ts +11 -1
  40. package/src/cli/commands/mcp/paths.ts +2 -1
  41. package/src/cli/commands/metadata.ts +79 -0
  42. package/src/cli/commands/multi-get.ts +13 -0
  43. package/src/cli/commands/setup-semantic.ts +6 -1
  44. package/src/cli/commands/shared.ts +35 -8
  45. package/src/cli/commands/skill/paths.ts +20 -2
  46. package/src/cli/commands/status.ts +89 -15
  47. package/src/cli/format/search-results.ts +11 -0
  48. package/src/cli/options.ts +2 -0
  49. package/src/cli/program.ts +64 -3
  50. package/src/core/activation-probe-plan.ts +0 -1
  51. package/src/core/config-mutation.ts +72 -55
  52. package/src/core/context-capsule-schema.ts +2 -0
  53. package/src/core/context-capsule-verification.ts +2 -0
  54. package/src/core/context-compiler.ts +2 -5
  55. package/src/core/context-evidence.ts +1 -0
  56. package/src/core/context-verifier.ts +14 -1
  57. package/src/core/memory-remember.ts +0 -1
  58. package/src/core/metadata-discovery.ts +169 -0
  59. package/src/core/module-path.ts +33 -0
  60. package/src/core/runtime-entrypoint.ts +3 -1
  61. package/src/ingestion/file-processor.ts +6 -3
  62. package/src/ingestion/prepare-file.ts +14 -4
  63. package/src/ingestion/source-availability/root.ts +138 -0
  64. package/src/ingestion/sync.ts +35 -1
  65. package/src/ingestion/walker.ts +11 -3
  66. package/src/llm/native-worker/client.ts +8 -4
  67. package/src/llm/native-worker/errors.ts +26 -3
  68. package/src/mcp/http-egress.ts +1 -0
  69. package/src/mcp/http-transport.ts +7 -2
  70. package/src/mcp/server.ts +3 -0
  71. package/src/mcp/tool-descriptions-core.ts +3 -3
  72. package/src/mcp/tool-profile.ts +16 -1
  73. package/src/mcp/tools/get.ts +10 -0
  74. package/src/mcp/tools/index.ts +68 -4
  75. package/src/mcp/tools/multi-get.ts +15 -0
  76. package/src/mcp/tools/query.ts +5 -3
  77. package/src/mcp/tools/search.ts +5 -3
  78. package/src/mcp/tools/status.ts +17 -2
  79. package/src/mcp/tools/vsearch.ts +5 -3
  80. package/src/mcp/tools/workspace-write.ts +42 -34
  81. package/src/pipeline/answer-prompt.ts +15 -2
  82. package/src/pipeline/answer.ts +3 -0
  83. package/src/pipeline/claim-verification-schema.ts +6 -1
  84. package/src/pipeline/claim-verification.ts +54 -1
  85. package/src/pipeline/explain.ts +1 -1
  86. package/src/pipeline/fusion.ts +24 -4
  87. package/src/pipeline/hybrid.ts +5 -1
  88. package/src/pipeline/query-snippet.ts +266 -0
  89. package/src/pipeline/result-context.ts +29 -0
  90. package/src/pipeline/search.ts +154 -20
  91. package/src/pipeline/types.ts +4 -0
  92. package/src/pipeline/vsearch.ts +5 -1
  93. package/src/sdk/client.ts +46 -6
  94. package/src/sdk/documents.ts +5 -0
  95. package/src/sdk/types.ts +11 -1
  96. package/src/serve/public/components/ThoroughnessSelector.tsx +1 -1
  97. package/src/serve/public/globals.built.css +1 -1
  98. package/src/serve/public/pages/Collections.tsx +8 -0
  99. package/src/serve/public/pages/Search.tsx +9 -1
  100. package/src/serve/resident-request.ts +8 -0
  101. package/src/serve/resident-runtime.ts +25 -3
  102. package/src/serve/routes/api.ts +29 -1
  103. package/src/serve/server.ts +12 -0
  104. package/src/serve/spa-production-build.ts +7 -3
  105. package/src/serve/status-model.ts +2 -0
  106. package/src/serve/status.ts +29 -0
  107. package/src/sessions/config-refresh.ts +6 -0
  108. package/src/sessions/import-child.ts +4 -1
  109. package/src/store/sqlite/adapter.ts +241 -20
  110. package/src/store/types.ts +61 -7
  111. package/src/store/vector/variant-search.ts +112 -74
  112. package/browser-extension/artifacts/gno-browser-clipper-v2.10.1.zip.sha256 +0 -1
@@ -81,6 +81,8 @@ interface CollectionStats {
81
81
  documentCount: number;
82
82
  chunkCount: number;
83
83
  embeddedCount: number;
84
+ /** Set while the folder is missing or unreadable; documents stay indexed. */
85
+ rootError?: { code: string; message: string };
84
86
  include?: string[];
85
87
  models?: Partial<Record<"embed" | "rerank" | "expand" | "gen", string>>;
86
88
  effectiveModels?: Record<"embed" | "rerank" | "expand" | "gen", string>;
@@ -277,6 +279,12 @@ function CollectionCard({
277
279
  </Tooltip>
278
280
  </TooltipProvider>
279
281
  )}
282
+ {collection.rootError && (
283
+ <p className="flex items-start gap-1.5 text-amber-500 text-xs">
284
+ <AlertCircleIcon className="mt-0.5 size-3.5 shrink-0" />
285
+ <span>{collection.rootError.message}</span>
286
+ </p>
287
+ )}
280
288
  </CardHeader>
281
289
 
282
290
  <CardContent className="pt-2">
@@ -174,8 +174,12 @@ export default function Search({ navigate }: PageProps) {
174
174
  query.trim().length > 0 && !structuredQueryState.ok
175
175
  ? structuredQueryState.error.message
176
176
  : null;
177
+ // Without vectors, Fast is keyword search unless modes or intent need the
178
+ // hybrid endpoint. With vectors, Fast is always hybrid (no expansion,
179
+ // rerank or graph), the same path as CLI `--fast`.
177
180
  const forceHybridForModes =
178
181
  thoroughness === "fast" &&
182
+ !hybridAvailable &&
179
183
  (queryModes.length > 0 ||
180
184
  intent.trim().length > 0 ||
181
185
  (structuredQueryState.ok &&
@@ -325,6 +329,7 @@ export default function Search({ navigate }: PageProps) {
325
329
 
326
330
  const useBm25 =
327
331
  thoroughness === "fast" &&
332
+ !hybridAvailable &&
328
333
  queryModes.length === 0 &&
329
334
  intent.trim().length === 0 &&
330
335
  !structuredQueryState.value.usedStructuredQuerySyntax;
@@ -403,6 +408,7 @@ export default function Search({ navigate }: PageProps) {
403
408
  activePreset,
404
409
  category,
405
410
  exclude,
411
+ hybridAvailable,
406
412
  intent,
407
413
  query,
408
414
  queryModes,
@@ -448,7 +454,9 @@ export default function Search({ navigate }: PageProps) {
448
454
 
449
455
  const thoroughnessDesc =
450
456
  thoroughness === "fast"
451
- ? "Keyword search (BM25)"
457
+ ? hybridAvailable
458
+ ? "Hybrid, no expansion or reranking"
459
+ : "Keyword search (no vectors)"
452
460
  : thoroughness === "balanced"
453
461
  ? resolveDepthPolicy({ presetId: activePreset })
454
462
  .balancedExpansionEnabled
@@ -2,6 +2,7 @@ import type { ResidentRuntime } from "./resident-runtime";
2
2
  /** Admission and bounded-reader boundary for resident REST reads. */
3
3
 
4
4
  import { withInferenceScope } from "../llm/inference-scope";
5
+ import { StaleCollectionsError } from "../store/sqlite/adapter";
5
6
  import { sessionsErrorResponse } from "./routes/sessions";
6
7
 
7
8
  /**
@@ -16,6 +17,13 @@ export async function refreshResidentConfig(
16
17
  await runtime.refreshConfig?.();
17
18
  return null;
18
19
  } catch (error) {
20
+ // Names collections only (no paths), and says how to resolve it.
21
+ if (error instanceof StaleCollectionsError) {
22
+ return Response.json(
23
+ { error: { code: "STALE_COLLECTIONS", message: error.message } },
24
+ { status: 409 }
25
+ );
26
+ }
19
27
  return sessionsErrorResponse(error);
20
28
  }
21
29
  }
@@ -61,7 +61,11 @@ import {
61
61
  readInstanceConfig,
62
62
  } from "../sessions/config-refresh";
63
63
  import { remoteSafeSessionsError } from "../sessions/types";
64
- import { SqliteAdapter } from "../store/sqlite/adapter";
64
+ import {
65
+ SqliteAdapter,
66
+ StaleCollectionsError,
67
+ staleCollectionsMessage,
68
+ } from "../store/sqlite/adapter";
65
69
  import {
66
70
  createServerContext,
67
71
  type CreateServerContextOptions,
@@ -519,10 +523,23 @@ export async function startResidentRuntime(
519
523
  return undefined;
520
524
  }
521
525
  };
526
+ // Another client may index a collection the served config omits after this
527
+ // runtime started; requests fail instead of reading it.
528
+ const assertServedCollections = (): void => {
529
+ const stale = store.staleIndexedCollections(
530
+ ctxHolder.config.collections.map(({ name }) => name)
531
+ );
532
+ if (stale.length > 0) {
533
+ throw new StaleCollectionsError(staleCollectionsMessage(stale));
534
+ }
535
+ };
522
536
  const refreshConfig = (): Promise<void> => {
523
537
  configRefresh ??= (async () => {
524
538
  const stamp = await configStamp();
525
- if (stamp !== undefined && stamp === loadedConfigStamp) return;
539
+ if (stamp !== undefined && stamp === loadedConfigStamp) {
540
+ assertServedCollections();
541
+ return;
542
+ }
526
543
  const file = await readInstanceConfig(
527
544
  instance,
528
545
  deps.loadConfig ?? loadConfig
@@ -535,6 +552,7 @@ export async function startResidentRuntime(
535
552
  };
536
553
  if (Bun.deepEquals(next, served)) {
537
554
  loadedConfigStamp = stamp;
555
+ assertServedCollections();
538
556
  return;
539
557
  }
540
558
  await adoptServedConfig(
@@ -816,7 +834,11 @@ export async function startResidentRuntime(
816
834
  await refreshConfig();
817
835
  lastRefreshError = undefined;
818
836
  } catch (error) {
819
- const message = remoteSafeSessionsError(error).message;
837
+ // The stale-collections message names collections, never paths.
838
+ const message =
839
+ error instanceof StaleCollectionsError
840
+ ? error.message
841
+ : remoteSafeSessionsError(error).message;
820
842
  if (message !== lastRefreshError)
821
843
  console.warn(`gno ${mode}: config not reloaded: ${message}`);
822
844
  lastRefreshError = message;
@@ -5,7 +5,6 @@
5
5
  * @module src/serve/routes/api
6
6
  */
7
7
 
8
- // node:fs/promises structure ops + realpath have no Bun equivalent
9
8
  import { readdir, realpath } from "node:fs/promises";
10
9
  // node:path has no Bun equivalent
11
10
  import { posix as pathPosix } from "node:path";
@@ -90,6 +89,7 @@ import {
90
89
  type RecallInput,
91
90
  type RememberInput,
92
91
  } from "../../core/memory";
92
+ import { readMetadataSummary } from "../../core/metadata-discovery";
93
93
  import {
94
94
  hasContentMutation,
95
95
  recordContentMutation,
@@ -167,6 +167,8 @@ import {
167
167
  import { diagnoseQueryTarget } from "../../pipeline/diagnose";
168
168
  import { searchHybrid } from "../../pipeline/hybrid";
169
169
  import { validateQueryModes } from "../../pipeline/query-modes";
170
+ // node:fs/promises structure ops + realpath have no Bun equivalent
171
+ import { supersedingUri } from "../../pipeline/result-context";
170
172
  import { searchBm25 } from "../../pipeline/search";
171
173
  import {
172
174
  derivePublishArtifactFilename,
@@ -2146,7 +2148,9 @@ export async function handleDoc(
2146
2148
  const record = projectRecordEvidenceMetadata(doc);
2147
2149
  const relPath = doc.recordSourcePath ?? doc.relPath;
2148
2150
 
2151
+ const successor = await supersedingUri(store, doc.uri);
2149
2152
  const responseData = {
2153
+ ...(successor ? { supersededBy: successor } : {}),
2150
2154
  docid: doc.docid,
2151
2155
  uri: doc.uri,
2152
2156
  title: doc.title,
@@ -2463,6 +2467,30 @@ export async function handleDocAsset(
2463
2467
  * Query params: collection, prefix
2464
2468
  * Returns tag list with document counts.
2465
2469
  */
2470
+ export async function handleMetadata(
2471
+ store: Pick<StorePort, "getTypedMetadataDocuments">,
2472
+ url: URL
2473
+ ): Promise<Response> {
2474
+ const collection = url.searchParams.get("collection") ?? undefined;
2475
+ const valuesParam = url.searchParams.get("values");
2476
+ try {
2477
+ return jsonResponse(
2478
+ await readMetadataSummary(store, {
2479
+ collection,
2480
+ values: valuesParam === null ? undefined : Number(valuesParam),
2481
+ })
2482
+ );
2483
+ } catch (cause) {
2484
+ return cause instanceof RangeError
2485
+ ? errorResponse("VALIDATION", cause.message)
2486
+ : errorResponse(
2487
+ "RUNTIME",
2488
+ cause instanceof Error ? cause.message : String(cause),
2489
+ 500
2490
+ );
2491
+ }
2492
+ }
2493
+
2466
2494
  export async function handleTags(
2467
2495
  store: SqliteAdapter,
2468
2496
  url: URL
@@ -83,6 +83,7 @@ import {
83
83
  handleSetPreset,
84
84
  handleStatus,
85
85
  handleSync,
86
+ handleMetadata,
86
87
  handleTags,
87
88
  handleTrashDoc,
88
89
  handleUpdateCollection,
@@ -1331,6 +1332,17 @@ export async function startServer(
1331
1332
  }
1332
1333
  },
1333
1334
  },
1335
+ "/api/metadata": {
1336
+ GET: async (req: Request) => {
1337
+ const url = new URL(req.url);
1338
+ return withSecurityHeaders(
1339
+ await handleResidentRead(runtime as ResidentRuntime, req, () =>
1340
+ handleMetadata(store, url)
1341
+ ),
1342
+ isDev
1343
+ );
1344
+ },
1345
+ },
1334
1346
  "/api/tags": {
1335
1347
  GET: async (req: Request) => {
1336
1348
  const url = new URL(req.url);
@@ -6,6 +6,8 @@ import { tmpdir } from "node:os";
6
6
  // node:path — no Bun path utils.
7
7
  import { basename, join, relative } from "node:path";
8
8
 
9
+ import { moduleDir, modulePath } from "../core/module-path";
10
+
9
11
  export const ROOT_MOUNT_MARKER = 'getElementById("root")';
10
12
 
11
13
  export type ProductionSpaFile = {
@@ -19,7 +21,8 @@ export type ProductionSpaAssets = {
19
21
  sourceHash: string;
20
22
  };
21
23
 
22
- const productionSpaPublicDir = (): string => join(import.meta.dir, "public");
24
+ const productionSpaPublicDir = (): string =>
25
+ join(moduleDir(import.meta), "public");
23
26
 
24
27
  export const productionSpaEntryPath = (): string =>
25
28
  join(productionSpaPublicDir(), "index.html");
@@ -79,13 +82,14 @@ const contentTypeFor = (path: string): string => {
79
82
  return "application/octet-stream";
80
83
  };
81
84
 
82
- export const isBunfsPath = (path: string): boolean => {
85
+ export const isBunfsPath = (path: string | undefined): boolean => {
86
+ if (path === undefined) return false;
83
87
  const normalized = path.replaceAll("\\", "/");
84
88
  return normalized.includes("/$bunfs/") || normalized.startsWith("B:/~BUN/");
85
89
  };
86
90
 
87
91
  export const isStandaloneExecutable = (): boolean =>
88
- isBunfsPath(import.meta.path);
92
+ isBunfsPath(modulePath(import.meta));
89
93
 
90
94
  const rewriteProductionHtml = (html: string, jsEntryPath: string): string => {
91
95
  const script = `<script type="module" src="/${basename(jsEntryPath)}"></script>`;
@@ -28,6 +28,8 @@ export interface StatusCollection {
28
28
  documentCount: number;
29
29
  chunkCount: number;
30
30
  embeddedCount: number;
31
+ /** Present while the collection root is missing or unreadable (path-free). */
32
+ rootError?: { code: string; message: string };
31
33
  }
32
34
 
33
35
  export interface SuggestedCollection {
@@ -20,6 +20,10 @@ import {
20
20
  type ActivationStatus,
21
21
  buildActivationStatus,
22
22
  } from "../core/activation-status";
23
+ import {
24
+ checkCollectionRoots,
25
+ type CollectionRootError,
26
+ } from "../ingestion/source-availability/root";
23
27
  import { ModelCache } from "../llm/cache";
24
28
  import { envIsSet, resolveDownloadPolicy } from "../llm/policy";
25
29
  import { getActivePreset, resolveModelUri } from "../llm/registry";
@@ -263,6 +267,25 @@ function buildCollectionCheck(status: IndexStatus): HealthCheck {
263
267
  };
264
268
  }
265
269
 
270
+ /** Folders whose root is missing or unreadable right now; null when none. */
271
+ function buildFolderAccessCheck(
272
+ rootErrors: Record<string, CollectionRootError>
273
+ ): HealthCheck | null {
274
+ const names = Object.keys(rootErrors);
275
+ if (names.length === 0) {
276
+ return null;
277
+ }
278
+ return {
279
+ id: "folder-access",
280
+ title: "Folder access",
281
+ status: "warn",
282
+ summary: `${names.join(", ")}: folder missing or unreadable`,
283
+ detail: `${names.map((name) => `${name}: ${rootErrors[name]?.message}.`).join(" ")} Reconnect the drive or restore the folder; this clears as soon as it is back. If the folder moved, change the collection's path.`,
284
+ actionLabel: "Manage folders",
285
+ actionKind: "open-collections",
286
+ };
287
+ }
288
+
266
289
  function buildIndexingCheck(status: IndexStatus): HealthCheck {
267
290
  if (status.recentErrors > 0) {
268
291
  return {
@@ -688,6 +711,7 @@ export async function buildAppStatus(
688
711
  bootstrap,
689
712
  embedCached,
690
713
  connectorTargets,
714
+ rootErrors,
691
715
  ] = await Promise.all([
692
716
  buildModelCheck(ctx, deps),
693
717
  buildDiskCheck(status, deps),
@@ -695,6 +719,7 @@ export async function buildAppStatus(
695
719
  buildBootstrapState(ctx),
696
720
  isModelCached(preset.embed),
697
721
  (deps.listConnectorTargets ?? getConnectorVerificationTargets)(),
722
+ checkCollectionRoots(ctx.config.collections),
698
723
  ]);
699
724
  const activation = await (deps.buildActivation ?? buildActivationStatus)(
700
725
  ctx.store,
@@ -712,6 +737,7 @@ export async function buildAppStatus(
712
737
  const backgroundCheck = buildBackgroundCheck(ctx, status);
713
738
  const checks = [
714
739
  buildCollectionCheck(status),
740
+ buildFolderAccessCheck(rootErrors),
715
741
  buildIndexingCheck(status),
716
742
  buildActivationCheck(activation),
717
743
  buildConnectorActivationCheck(activation),
@@ -746,6 +772,9 @@ export async function buildAppStatus(
746
772
  documentCount: collection.activeDocuments,
747
773
  chunkCount: collection.totalChunks,
748
774
  embeddedCount: collection.embeddedChunks,
775
+ ...(rootErrors[collection.name]
776
+ ? { rootError: rootErrors[collection.name] }
777
+ : {}),
749
778
  })),
750
779
  totalDocuments: status.activeDocuments,
751
780
  totalChunks: status.totalChunks,
@@ -13,6 +13,7 @@ import type { SqliteAdapter } from "../store/sqlite/adapter";
13
13
  import { getIndexDbPath } from "../app/constants";
14
14
  import { loadConfig } from "../config";
15
15
  import { collectionEgressPolicyEpoch } from "../core/collection-egress-policy-service";
16
+ import { StaleCollectionsError } from "../store/sqlite/adapter";
16
17
  import { assertSessionBinding } from "./binding";
17
18
  import { SessionsError } from "./types";
18
19
 
@@ -76,7 +77,12 @@ export async function adoptServedConfig(
76
77
  served: ServedSessionsConfig,
77
78
  config: Config
78
79
  ): Promise<void> {
80
+ // A config edit never deletes indexed collections; one it dropped fails
81
+ // the refresh until it is added back or `gno cleanup` deletes it.
79
82
  const collections = await served.store.syncCollections(config.collections);
83
+ if (!collections.ok && collections.error.code === "STALE_COLLECTIONS") {
84
+ throw new StaleCollectionsError(collections.error.message);
85
+ }
80
86
  if (!collections.ok) {
81
87
  throw new Error(
82
88
  `Config saved but collection sync failed: ${collections.error.message}`
@@ -26,6 +26,7 @@ import type { Config } from "../config/types";
26
26
  import type { SessionImportReceipt } from "./types";
27
27
 
28
28
  import { getIndexDbPath } from "../app/constants";
29
+ import { modulePath } from "../core/module-path";
29
30
  import { isStandaloneExecutable } from "../serve/spa-production-build";
30
31
  import { SqliteAdapter } from "../store/sqlite/adapter";
31
32
  import { assertSessionBinding } from "./binding";
@@ -113,7 +114,9 @@ export async function importInChildProcess(
113
114
  // Shared check: covers the POSIX `/$bunfs/` and Windows `B:/~BUN/` roots.
114
115
  const compiled = isStandaloneExecutable();
115
116
  const child = Bun.spawn({
116
- cmd: compiled ? [process.execPath] : [process.execPath, import.meta.path],
117
+ cmd: compiled
118
+ ? [process.execPath]
119
+ : [process.execPath, modulePath(import.meta)],
117
120
  env: compiled ? { ...process.env, [IMPORT_CHILD_ENV]: "1" } : process.env,
118
121
  // The request line, then held open as the child's lifeline.
119
122
  stdin: "pipe",