@chllming/wave-orchestration 0.6.1 → 0.6.3

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 (34) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +79 -30
  3. package/docs/README.md +15 -3
  4. package/docs/concepts/context7-vs-skills.md +24 -0
  5. package/docs/concepts/runtime-agnostic-orchestration.md +17 -2
  6. package/docs/concepts/what-is-a-wave.md +28 -0
  7. package/docs/evals/README.md +2 -0
  8. package/docs/guides/terminal-surfaces.md +2 -0
  9. package/docs/plans/current-state.md +2 -1
  10. package/docs/plans/wave-orchestrator.md +22 -3
  11. package/docs/reference/runtime-config/README.md +4 -4
  12. package/docs/reference/runtime-config/claude.md +6 -1
  13. package/docs/reference/runtime-config/codex.md +2 -2
  14. package/docs/reference/runtime-config/opencode.md +1 -1
  15. package/docs/research/agent-context-sources.md +2 -0
  16. package/docs/research/coordination-failure-review.md +37 -13
  17. package/package.json +1 -1
  18. package/releases/manifest.json +33 -0
  19. package/scripts/wave-autonomous.mjs +2 -4
  20. package/scripts/wave-orchestrator/adhoc.mjs +32 -11
  21. package/scripts/wave-orchestrator/agent-state.mjs +10 -3
  22. package/scripts/wave-orchestrator/autonomous.mjs +20 -6
  23. package/scripts/wave-orchestrator/config.mjs +19 -0
  24. package/scripts/wave-orchestrator/dashboard-renderer.mjs +150 -20
  25. package/scripts/wave-orchestrator/dashboard-state.mjs +8 -0
  26. package/scripts/wave-orchestrator/executors.mjs +67 -4
  27. package/scripts/wave-orchestrator/install.mjs +198 -25
  28. package/scripts/wave-orchestrator/launcher-runtime.mjs +1 -0
  29. package/scripts/wave-orchestrator/launcher.mjs +249 -10
  30. package/scripts/wave-orchestrator/package-update-notice.mjs +230 -0
  31. package/scripts/wave-orchestrator/package-version.mjs +32 -0
  32. package/scripts/wave-orchestrator/terminals.mjs +25 -0
  33. package/scripts/wave-orchestrator/wave-files.mjs +31 -0
  34. package/scripts/wave.mjs +12 -2
@@ -217,6 +217,7 @@ function buildClaudeLaunchSpec({ agent, promptPath, logPath, overlayDir }) {
217
217
  appendSingleValueFlag(tokens, "--agent", executor.claude.agent);
218
218
  appendSingleValueFlag(tokens, "--permission-mode", executor.claude.permissionMode);
219
219
  appendSingleValueFlag(tokens, "--permission-prompt-tool", executor.claude.permissionPromptTool);
220
+ appendSingleValueFlag(tokens, "--effort", executor.claude.effort);
220
221
  appendSingleValueFlag(tokens, "--max-turns", executor.claude.maxTurns);
221
222
  appendRepeatedFlag(tokens, "--mcp-config", executor.claude.mcpConfig);
222
223
  appendSingleValueFlag(tokens, "--settings", settingsPath);
@@ -299,6 +300,55 @@ function buildLocalLaunchSpec({ promptPath, logPath }) {
299
300
  };
300
301
  }
301
302
 
303
+ function buildLaunchLimitsMetadata(agent) {
304
+ const executor = agent?.executorResolved || {};
305
+ const executorId = normalizeExecutorMode(executor.id || DEFAULT_EXECUTOR_MODE);
306
+ const attemptTimeoutMinutes = executor?.budget?.minutes ?? null;
307
+ if (executorId === "claude") {
308
+ const source = executor?.claude?.maxTurnsSource || null;
309
+ return {
310
+ attemptTimeoutMinutes,
311
+ knownTurnLimit: executor?.claude?.maxTurns ?? null,
312
+ turnLimitSource: source,
313
+ notes:
314
+ source === "budget.turns"
315
+ ? ["Known turn limit was derived from generic budget.turns."]
316
+ : [],
317
+ };
318
+ }
319
+ if (executorId === "opencode") {
320
+ const source = executor?.opencode?.stepsSource || null;
321
+ return {
322
+ attemptTimeoutMinutes,
323
+ knownTurnLimit: executor?.opencode?.steps ?? null,
324
+ turnLimitSource: source,
325
+ notes:
326
+ source === "budget.turns"
327
+ ? ["Known turn limit was derived from generic budget.turns."]
328
+ : [],
329
+ };
330
+ }
331
+ if (executorId === "codex") {
332
+ const profileNote = executor?.codex?.profileName
333
+ ? ` via Codex profile ${executor.codex.profileName}`
334
+ : "";
335
+ return {
336
+ attemptTimeoutMinutes,
337
+ knownTurnLimit: null,
338
+ turnLimitSource: "not-set-by-wave",
339
+ notes: [
340
+ `Wave emits no Codex turn-limit flag; any effective ceiling may come from the selected Codex profile${profileNote} or the upstream Codex runtime.`,
341
+ ],
342
+ };
343
+ }
344
+ return {
345
+ attemptTimeoutMinutes,
346
+ knownTurnLimit: null,
347
+ turnLimitSource: "not-applicable",
348
+ notes: ["Local executor does not use model turn limits."],
349
+ };
350
+ }
351
+
302
352
  function buildCodexLaunchSpec({ agent, promptPath, logPath, skillProjection }) {
303
353
  const executor = agent.executorResolved;
304
354
  return {
@@ -329,16 +379,29 @@ function buildCodexLaunchSpec({ agent, promptPath, logPath, skillProjection }) {
329
379
  export function buildExecutorLaunchSpec({ agent, promptPath, logPath, overlayDir, skillProjection }) {
330
380
  const executorId = normalizeExecutorMode(agent?.executorResolved?.id || DEFAULT_EXECUTOR_MODE);
331
381
  ensureDirectory(overlayDir);
382
+ const limits = buildLaunchLimitsMetadata(agent);
332
383
  if (executorId === "local") {
333
- return buildLocalLaunchSpec({ promptPath, logPath });
384
+ return {
385
+ ...buildLocalLaunchSpec({ promptPath, logPath }),
386
+ limits,
387
+ };
334
388
  }
335
389
  if (executorId === "claude") {
336
- return buildClaudeLaunchSpec({ agent, promptPath, logPath, overlayDir, skillProjection });
390
+ return {
391
+ ...buildClaudeLaunchSpec({ agent, promptPath, logPath, overlayDir, skillProjection }),
392
+ limits,
393
+ };
337
394
  }
338
395
  if (executorId === "opencode") {
339
- return buildOpenCodeLaunchSpec({ agent, promptPath, logPath, overlayDir, skillProjection });
396
+ return {
397
+ ...buildOpenCodeLaunchSpec({ agent, promptPath, logPath, overlayDir, skillProjection }),
398
+ limits,
399
+ };
340
400
  }
341
- return buildCodexLaunchSpec({ agent, promptPath, logPath, skillProjection });
401
+ return {
402
+ ...buildCodexLaunchSpec({ agent, promptPath, logPath, skillProjection }),
403
+ limits,
404
+ };
342
405
  }
343
406
 
344
407
  export function commandForExecutor(executor, executorId = executor?.id) {
@@ -1,10 +1,17 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
+ import { spawnSync } from "node:child_process";
3
4
  import {
4
5
  applyContext7SelectionsToWave,
5
6
  loadContext7BundleIndex,
6
7
  } from "./context7.mjs";
7
8
  import { buildLanePaths, ensureDirectory, PACKAGE_ROOT, readJsonOrNull, REPO_ROOT, writeJsonAtomic } from "./shared.mjs";
9
+ import { fetchLatestPackageVersion } from "./package-update-notice.mjs";
10
+ import {
11
+ compareVersions,
12
+ readInstalledPackageMetadata,
13
+ WAVE_PACKAGE_NAME,
14
+ } from "./package-version.mjs";
8
15
  import { loadWaveConfig } from "./config.mjs";
9
16
  import { applyExecutorSelectionsToWave, parseWaveFiles, validateWaveDefinition } from "./wave-files.mjs";
10
17
  import { validateLaneSkillConfiguration } from "./skills.mjs";
@@ -14,7 +21,7 @@ export const INSTALL_STATE_DIR = ".wave";
14
21
  export const INSTALL_STATE_PATH = path.join(REPO_ROOT, INSTALL_STATE_DIR, "install-state.json");
15
22
  export const UPGRADE_HISTORY_DIR = path.join(REPO_ROOT, INSTALL_STATE_DIR, "upgrade-history");
16
23
  export const CHANGELOG_MANIFEST_PATH = path.join(PACKAGE_ROOT, "releases", "manifest.json");
17
- export const PACKAGE_METADATA_PATH = path.join(PACKAGE_ROOT, "package.json");
24
+ export const WORKSPACE_PACKAGE_JSON_PATH = path.join(REPO_ROOT, "package.json");
18
25
  export const STARTER_TEMPLATE_PATHS = [
19
26
  "wave.config.json",
20
27
  "docs/README.md",
@@ -69,11 +76,7 @@ function collectDeclaredDeployKinds(waves = []) {
69
76
  }
70
77
 
71
78
  function packageMetadata() {
72
- const payload = readJsonOrNull(PACKAGE_METADATA_PATH);
73
- if (!payload?.name || !payload?.version) {
74
- throw new Error(`Invalid package metadata: ${PACKAGE_METADATA_PATH}`);
75
- }
76
- return payload;
79
+ return readInstalledPackageMetadata();
77
80
  }
78
81
 
79
82
  function readInstallState() {
@@ -149,25 +152,6 @@ function nextHistoryRecord(existingState, entry) {
149
152
  return history;
150
153
  }
151
154
 
152
- function normalizeVersionParts(version) {
153
- return String(version || "")
154
- .split(".")
155
- .map((part) => Number.parseInt(part.replace(/[^0-9].*$/, ""), 10) || 0);
156
- }
157
-
158
- function compareVersions(a, b) {
159
- const left = normalizeVersionParts(a);
160
- const right = normalizeVersionParts(b);
161
- const length = Math.max(left.length, right.length);
162
- for (let index = 0; index < length; index += 1) {
163
- const diff = (left[index] || 0) - (right[index] || 0);
164
- if (diff !== 0) {
165
- return diff;
166
- }
167
- }
168
- return 0;
169
- }
170
-
171
155
  function readChangelogManifest() {
172
156
  const payload = readJsonOrNull(CHANGELOG_MANIFEST_PATH);
173
157
  if (!payload?.releases || !Array.isArray(payload.releases)) {
@@ -478,6 +462,186 @@ export function upgradeWorkspace() {
478
462
  };
479
463
  }
480
464
 
465
+ function readWorkspacePackageManifest(workspaceRoot = REPO_ROOT) {
466
+ const payload = readJsonOrNull(path.join(workspaceRoot, "package.json"));
467
+ if (!payload || typeof payload !== "object") {
468
+ throw new Error(`Missing package.json at ${path.join(workspaceRoot, "package.json")}`);
469
+ }
470
+ return payload;
471
+ }
472
+
473
+ function readInstallStateForWorkspace(workspaceRoot = REPO_ROOT) {
474
+ const payload = readJsonOrNull(path.join(workspaceRoot, INSTALL_STATE_DIR, "install-state.json"));
475
+ return payload && typeof payload === "object" ? payload : null;
476
+ }
477
+
478
+ function parsePackageManagerId(value) {
479
+ const normalized = String(value || "")
480
+ .trim()
481
+ .toLowerCase();
482
+ if (!normalized) {
483
+ return null;
484
+ }
485
+ if (normalized.startsWith("pnpm@")) {
486
+ return "pnpm";
487
+ }
488
+ if (normalized.startsWith("npm@")) {
489
+ return "npm";
490
+ }
491
+ if (normalized.startsWith("yarn@")) {
492
+ return "yarn";
493
+ }
494
+ if (normalized.startsWith("bun@")) {
495
+ return "bun";
496
+ }
497
+ return null;
498
+ }
499
+
500
+ export function detectWorkspacePackageManager(workspaceRoot = REPO_ROOT) {
501
+ const manifest = readWorkspacePackageManifest(workspaceRoot);
502
+ const packageManagerFromManifest = parsePackageManagerId(manifest.packageManager);
503
+ if (packageManagerFromManifest) {
504
+ return {
505
+ id: packageManagerFromManifest,
506
+ source: "packageManager",
507
+ raw: manifest.packageManager,
508
+ };
509
+ }
510
+ for (const [fileName, id] of [
511
+ ["pnpm-lock.yaml", "pnpm"],
512
+ ["package-lock.json", "npm"],
513
+ ["npm-shrinkwrap.json", "npm"],
514
+ ["yarn.lock", "yarn"],
515
+ ["bun.lockb", "bun"],
516
+ ["bun.lock", "bun"],
517
+ ]) {
518
+ if (fs.existsSync(path.join(workspaceRoot, fileName))) {
519
+ return {
520
+ id,
521
+ source: "lockfile",
522
+ raw: fileName,
523
+ };
524
+ }
525
+ }
526
+ return {
527
+ id: "npm",
528
+ source: "default",
529
+ raw: null,
530
+ };
531
+ }
532
+
533
+ function packageManagerCommands(managerId, packageName = WAVE_PACKAGE_NAME) {
534
+ if (managerId === "pnpm") {
535
+ return {
536
+ install: ["pnpm", ["add", "-D", `${packageName}@latest`]],
537
+ execWave: (args) => ["pnpm", ["exec", "wave", ...args]],
538
+ };
539
+ }
540
+ if (managerId === "npm") {
541
+ return {
542
+ install: ["npm", ["install", "--save-dev", `${packageName}@latest`]],
543
+ execWave: (args) => ["npm", ["exec", "--", "wave", ...args]],
544
+ };
545
+ }
546
+ if (managerId === "yarn") {
547
+ return {
548
+ install: ["yarn", ["add", "-D", `${packageName}@latest`]],
549
+ execWave: (args) => ["yarn", ["exec", "wave", ...args]],
550
+ };
551
+ }
552
+ if (managerId === "bun") {
553
+ return {
554
+ install: ["bun", ["add", "-d", `${packageName}@latest`]],
555
+ execWave: (args) => ["bun", ["x", "wave", ...args]],
556
+ };
557
+ }
558
+ throw new Error(`Unsupported package manager: ${managerId}`);
559
+ }
560
+
561
+ function runCommandOrThrow(command, args, options = {}) {
562
+ const spawnImpl = options.spawnImpl || spawnSync;
563
+ const result = spawnImpl(command, args, {
564
+ cwd: options.workspaceRoot || REPO_ROOT,
565
+ stdio: options.stdio || "inherit",
566
+ env: options.env || process.env,
567
+ encoding: "utf8",
568
+ });
569
+ const status = Number.isInteger(result?.status) ? result.status : 1;
570
+ if (status !== 0) {
571
+ throw new Error(`${command} ${args.join(" ")} failed with status ${status}`);
572
+ }
573
+ return result;
574
+ }
575
+
576
+ export async function selfUpdateWorkspace(options = {}) {
577
+ const workspaceRoot = options.workspaceRoot || REPO_ROOT;
578
+ const metadata = options.packageMetadata || packageMetadata();
579
+ const installState = readInstallStateForWorkspace(workspaceRoot);
580
+ const packageManager = detectWorkspacePackageManager(workspaceRoot);
581
+ const commands = packageManagerCommands(packageManager.id, metadata.name || WAVE_PACKAGE_NAME);
582
+ const emit = options.emit || console.log;
583
+ let latestVersion = null;
584
+
585
+ try {
586
+ latestVersion = await fetchLatestPackageVersion(metadata.name || WAVE_PACKAGE_NAME, {
587
+ fetchImpl: options.fetchImpl,
588
+ timeoutMs: options.timeoutMs,
589
+ });
590
+ } catch {
591
+ latestVersion = null;
592
+ }
593
+
594
+ const currentVersion = String(metadata.version || "").trim();
595
+ const recordedVersion = String(installState?.installedVersion || "").trim() || null;
596
+ const needsUpgradeOnly = recordedVersion && compareVersions(currentVersion, recordedVersion) !== 0;
597
+
598
+ emit(`[wave:self-update] package_manager=${packageManager.id}`);
599
+
600
+ if (latestVersion && compareVersions(latestVersion, currentVersion) <= 0) {
601
+ if (!needsUpgradeOnly) {
602
+ emit(`[wave:self-update] ${metadata.name} is already current at ${currentVersion}.`);
603
+ return {
604
+ mode: "already-current",
605
+ packageManager: packageManager.id,
606
+ currentVersion,
607
+ latestVersion,
608
+ };
609
+ }
610
+ emit(
611
+ `[wave:self-update] dependency is already at ${currentVersion}; recording workspace upgrade state.`,
612
+ );
613
+ const [upgradeCommand, upgradeArgs] = commands.execWave(["upgrade"]);
614
+ runCommandOrThrow(upgradeCommand, upgradeArgs, options);
615
+ return {
616
+ mode: "upgrade-only",
617
+ packageManager: packageManager.id,
618
+ currentVersion,
619
+ latestVersion,
620
+ };
621
+ }
622
+
623
+ emit(
624
+ `[wave:self-update] updating ${metadata.name} from ${currentVersion}${latestVersion ? ` to ${latestVersion}` : " to the latest published version"}.`,
625
+ );
626
+ const [installCommand, installArgs] = commands.install;
627
+ runCommandOrThrow(installCommand, installArgs, options);
628
+
629
+ emit("[wave:self-update] release notes since the recorded install:");
630
+ const [changelogCommand, changelogArgs] = commands.execWave(["changelog", "--since-installed"]);
631
+ runCommandOrThrow(changelogCommand, changelogArgs, options);
632
+
633
+ emit("[wave:self-update] recording install-state and upgrade report:");
634
+ const [upgradeCommand, upgradeArgs] = commands.execWave(["upgrade"]);
635
+ runCommandOrThrow(upgradeCommand, upgradeArgs, options);
636
+
637
+ return {
638
+ mode: "updated",
639
+ packageManager: packageManager.id,
640
+ currentVersion,
641
+ latestVersion,
642
+ };
643
+ }
644
+
481
645
  function printJson(payload) {
482
646
  console.log(JSON.stringify(payload, null, 2));
483
647
  }
@@ -486,6 +650,7 @@ function printHelp() {
486
650
  console.log(`Usage:
487
651
  wave init [--adopt-existing] [--json]
488
652
  wave upgrade [--json]
653
+ wave self-update
489
654
  wave changelog [--since-installed] [--json]
490
655
  wave doctor [--json]
491
656
  `);
@@ -562,6 +727,14 @@ export async function runInstallCli(argv) {
562
727
  return;
563
728
  }
564
729
 
730
+ if (subcommand === "self-update") {
731
+ if (options.json) {
732
+ throw new Error("`wave self-update` does not support --json.");
733
+ }
734
+ await selfUpdateWorkspace();
735
+ return;
736
+ }
737
+
565
738
  if (subcommand === "changelog") {
566
739
  const result = readChangelog({ sinceInstalled: options.sinceInstalled });
567
740
  if (options.json) {
@@ -139,6 +139,7 @@ export async function launchAgentSession(lanePaths, params, { runTmuxFn }) {
139
139
  env: launchSpec.env || {},
140
140
  useRateLimitRetries: launchSpec.useRateLimitRetries === true,
141
141
  invocationLines: launchSpec.invocationLines,
142
+ limits: launchSpec.limits || null,
142
143
  skills: summarizeResolvedSkills(agent.skillsResolved),
143
144
  });
144
145
  return {