@ours.network/install 0.17.0-nightly.13 → 0.17.0-nightly.14

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.
@@ -423,6 +423,47 @@ function readChanged(stdout) {
423
423
  }
424
424
  }
425
425
 
426
+ /**
427
+ * THE QUESTION NOBODY WAS ASKING.
428
+ *
429
+ * `planComponentSelection` has always read an `answers` map — and NOTHING EVER
430
+ * POPULATED IT. `args.answers` is not produced by parseInstallArgs and is not
431
+ * written by any phase, so it was permanently `{}`, every optional component fell
432
+ * to its own default, and tg and cowork (both default false) were skipped in
433
+ * silence. The screen then printed "cowork — not installed" as though that had
434
+ * been a decision.
435
+ *
436
+ * This was the ONE phase with no question. Harness plugins ask, ours-fleet asks,
437
+ * voice asks, the broker asks. Components did not.
438
+ *
439
+ * IT IS NOT A COWORK BUG, and fixing only the component that got reported would
440
+ * have left the identical defect live under a different label: the Telegram
441
+ * connector is unreachable by exactly the same route, and nobody noticed only
442
+ * because tg is usually configured somewhere else.
443
+ *
444
+ * A REQUIRED component is never asked — since #74 the MCP package IS the daemon,
445
+ * the daemon phase has already installed it, and a question whose only honest
446
+ * answer is yes is not a question. Non-interactive runs are unchanged: assumeYes
447
+ * takes each component's own default, which is what NON_INTERACTIVE_ANSWERS
448
+ * documents (mcp yes, tg no, cowork no), so nothing about the unattended contract
449
+ * moves.
450
+ */
451
+ export async function askComponents(args, effects) {
452
+ const answers = { ...(args.answers ?? {}) };
453
+ if (args.assumeYes) return answers;
454
+ const optional = COMPONENTS.filter((c) => !c.required && answers[c.key] === undefined);
455
+ if (optional.length === 0) return answers;
456
+ effects.out(heading('What else should this daemon carry?'));
457
+ for (const component of optional) {
458
+ const already = (args.installed ?? {})[component.key] === true;
459
+ answers[component.key] = await effects.ask(
460
+ already ? `Keep ${component.label}?` : `Install ${component.label}?`,
461
+ already || component.default,
462
+ );
463
+ }
464
+ return answers;
465
+ }
466
+
426
467
  /**
427
468
  * Components: §5. A component that fails is reported with its retry command and
428
469
  * the run CONTINUES — a failed component is never a reason to undo a successful
@@ -432,8 +473,9 @@ export async function runComponentPhase(args, effects, target) {
432
473
  const dir = target.stateDir;
433
474
  const endpoint = `http://127.0.0.1:${target.port}`;
434
475
  const isDefaultStateDir = dir === join(effects.home, '.ours');
476
+ const answers = await askComponents(args, effects);
435
477
  const chosen = planComponentSelection({
436
- answers: args.answers ?? {},
478
+ answers,
437
479
  installed: args.installed ?? {},
438
480
  assumeYes: args.assumeYes,
439
481
  });
@@ -1029,7 +1071,19 @@ export async function runInstall(argv, effects) {
1029
1071
  if (args.version) { effects.out(`ours-install v${effects.version ?? '?'}`); return EXIT_OK; }
1030
1072
 
1031
1073
  args.brokerUrl = args.brokerUrl ?? effects.brokerUrl;
1032
- args.channel = resolveChannel(effects.env.OURS_CHANNEL ?? effects.env.OURS_INSTALL_CHANNEL);
1074
+ // THE INSTALLER'S OWN VERSION IS THE CHANNEL SIGNAL WHEN NOTHING SAYS OTHERWISE.
1075
+ //
1076
+ // resolveChannel falls back to `selfVersion` only when the environment is
1077
+ // silent, and this call passed no selfVersion — so a NIGHTLY installer with no
1078
+ // OURS_CHANNEL set resolved to `latest` and installed the whole stack at latest.
1079
+ // That is what put fleet@latest and a stable ours-mcp on a nightly machine.
1080
+ //
1081
+ // The v2 bin has always done this correctly (install.mjs:53 passes pkgVersion());
1082
+ // the v3 orchestrator dropped the argument. @ours.network/install is published on
1083
+ // BOTH dist-tags from one lockstep bump, so its own version is the only thing
1084
+ // that distinguishes a nightly installer from a stable one when the operator has
1085
+ // said nothing.
1086
+ args.channel = resolveChannel(effects.env.OURS_CHANNEL ?? effects.env.OURS_INSTALL_CHANNEL, effects.version);
1033
1087
 
1034
1088
  effects.out(banner());
1035
1089
  effects.out(heading(`ours: target ${args.stateDir}${args.portExplicit ? `, port ${args.port}` : ''}`));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ours.network/install",
3
- "version": "0.17.0-nightly.13",
3
+ "version": "0.17.0-nightly.14",
4
4
  "private": false,
5
5
  "description": "The unified ours.network stack installer (ours-install): one guided ~3-minute flow for ours core (the daemon) + the harness plugins (Claude Code / Codex) + ours-fleet + the Telegram connector, then a single copy-paste hand-off prompt. Self-contained (Node built-ins only); run as `ours-install` or via curl|bash (install.sh).",
6
6
  "type": "module",