@wundam/orchex 1.0.0-rc.27 → 1.0.0-rc.28

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/dist/artifacts.js CHANGED
@@ -420,6 +420,10 @@ export async function writeArtifact(projectDir, streamId, artifact) {
420
420
  await fs.writeFile(artifactPath, JSON.stringify(artifact, null, 2), 'utf-8');
421
421
  return artifactPath;
422
422
  }
423
+ // Artifact-application trust boundary. Plan-level ownership is enforced
424
+ // upstream by validatePlan (src/intelligence/plan-contract.ts); this
425
+ // function guards the different trust boundary where LLM-generated
426
+ // artifacts try to apply their output.
423
427
  /**
424
428
  * Check whether all file operations fall within the stream's owns patterns.
425
429
  *
@@ -1,5 +1,17 @@
1
1
  import { extractArtifact } from './artifacts.js';
2
2
  import { validateOwnership } from './utils/ownership-validator.js';
3
+ function parseProviderErrorBody(body) {
4
+ try {
5
+ const parsed = JSON.parse(body);
6
+ const message = parsed?.error?.message ?? parsed?.message;
7
+ if (typeof message === 'string')
8
+ return message;
9
+ }
10
+ catch {
11
+ // Not JSON — return null
12
+ }
13
+ return null;
14
+ }
3
15
  /**
4
16
  * Returns actionable error messages for cloud API failures.
5
17
  * Status-code-specific guidance tells users exactly how to fix each error.
@@ -18,6 +30,16 @@ function formatCloudError(status, body) {
18
30
  const modelHint = modelMatch ? ` "${modelMatch[1]}"` : '';
19
31
  return `Model${modelHint} not found (404)${detail}.\n\nRun \`orchex config --model <model>\` to use a different model.`;
20
32
  }
33
+ case 400: {
34
+ const providerMsg = parseProviderErrorBody(body);
35
+ if (providerMsg && /credit balance|insufficient|billing/i.test(providerMsg)) {
36
+ return `LLM provider billing error (400): ${providerMsg}\n\nTop up credits at your provider's billing page, or switch providers with \`orchex config --provider <provider>\`.`;
37
+ }
38
+ if (providerMsg) {
39
+ return `LLM provider error (400): ${providerMsg}`;
40
+ }
41
+ return `Cloud API error: 400${detail}`;
42
+ }
21
43
  case 429:
22
44
  return `Quota exceeded (429)${detail}.\n\nYou've used all cloud runs for this period. Upgrade at https://orchex.dev/pricing`;
23
45
  default:
@@ -180,11 +202,18 @@ export class CloudExecutor {
180
202
  };
181
203
  }
182
204
  if (job.status === 'failed') {
205
+ const rawError = job.error ?? 'Cloud execution failed';
206
+ const providerMsg = parseProviderErrorBody(rawError);
207
+ const formattedError = providerMsg
208
+ ? (/credit balance|insufficient|billing/i.test(providerMsg)
209
+ ? `LLM provider billing error: ${providerMsg}\n\nTop up credits at your provider's billing page, or switch providers with \`orchex config --provider <provider>\`.`
210
+ : `LLM provider error: ${providerMsg}`)
211
+ : rawError;
183
212
  return {
184
213
  success: false,
185
214
  rawResponse: job.output ?? '',
186
215
  tokensUsed: job.tokensUsed ?? { input: 0, output: 0 },
187
- error: job.error ?? 'Cloud execution failed',
216
+ error: formattedError,
188
217
  };
189
218
  }
190
219
  // Job still pending/running - wait with adaptive interval
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@ import { ORCHEX_INSTRUCTIONS } from './mcp-instructions.js';
6
6
  import { registerResources } from './mcp-resources.js';
7
7
  import { broadcaster } from './execution-broadcaster.js';
8
8
  import { loadConfig, saveConfig, maskConfigForDisplay, resolveApiUrl, PRODUCTION_URL, LLMProviderSchema } from './config.js';
9
+ import { analyzeError } from './intelligence/index.js';
9
10
  import { buildVerificationMessage, buildStatusMessage, parseLoginApiResponse, } from './login-helpers.js';
10
11
  /** Opens browser cross-platform. Errors are silently ignored — URL already printed to terminal. */
11
12
  function openBrowser(url) {
@@ -619,6 +620,17 @@ async function handleRunCommand(args) {
619
620
  }
620
621
  // Init and execute
621
622
  console.log(`\nInitializing orchestration: "${plan.title}"\n`);
623
+ // Plan-contract boundary (Session 13): fail fast on invalid streams before
624
+ // they reach the legacy 5-layer validation. Additive — existing layers stay
625
+ // in place as defense-in-depth until Session 14 consolidates them.
626
+ {
627
+ const { validatePlan, formatPlanValidationErrors } = await import('./intelligence/index.js');
628
+ const validation = validatePlan(streamDefs, projectDir);
629
+ if (!validation.ok) {
630
+ console.error('Plan validation failed:\n' + formatPlanValidationErrors(validation.errors));
631
+ process.exit(1);
632
+ }
633
+ }
622
634
  await initOrchestration(projectDir, plan.title, streamDefs);
623
635
  const allResponses = [];
624
636
  let executionError;
@@ -638,9 +650,13 @@ async function handleRunCommand(args) {
638
650
  process.stdout.write(` ✓ ${event.data.streamId} — complete${dur}\n`);
639
651
  break;
640
652
  }
641
- case 'stream_failed':
642
- process.stdout.write(` ✗ ${event.data.streamId} failed: ${String(event.data.error).slice(0, 100)}\n`);
653
+ case 'stream_failed': {
654
+ const errStr = String(event.data.error);
655
+ const analysis = analyzeError(errStr);
656
+ const categoryTag = analysis.category !== 'unknown' ? ` [${analysis.category}]` : '';
657
+ process.stdout.write(` ✗ ${event.data.streamId} — failed${categoryTag}: ${errStr.slice(0, 200)}\n`);
643
658
  break;
659
+ }
644
660
  case 'stream_rate_limited':
645
661
  process.stdout.write(` ⏳ ${event.data.streamId} — rate limited (retry in ${Math.round((event.data.retryAfterMs ?? 0) / 1000)}s)\n`);
646
662
  break;
@@ -1,10 +1,12 @@
1
- export { gatherProjectContext, generatePlan } from './auto-planner.js';
1
+ export { gatherProjectContext, generatePlan, extractSpecPaths } from './auto-planner.js';
2
2
  export { parsePlanDocument, getSectionsAtLevel, isUnpopulatedTemplate } from './plan-parser.js';
3
3
  export { extractDeliverables, processDeliverables, formatDeliverablesReport } from './deliverable-extractor.js';
4
4
  export type { Deliverable } from './deliverable-extractor.js';
5
5
  export { buildDependencyGraph, formatDependencyReport } from './dependency-inferrer.js';
6
6
  export { generateStreams, formatStreamsForReview, toInitFormat, extractPrerequisites } from './stream-generator.js';
7
- export { formatPlanPreview, formatPlanPreviewText, type ModelDecision } from './plan-preview.js';
7
+ export { validatePlan, formatPlanValidationErrors, StreamDefinitionSchema as PlanContractStreamSchema } from './plan-contract.js';
8
+ export type { PlanValidationError, ValidatePlanResult, StreamDefinition as PlanContractStream } from './plan-contract.js';
9
+ export { formatPlanPreview, formatPlanPreviewText, generatePlanPreview, type ModelDecision, type PlanPreview, type GeneratePlanPreviewInput, type GeneratePlanPreviewResult, } from './plan-preview.js';
8
10
  export { detectSequentialEdits, autoFixSequentialEdits } from './sequential-diagnostics.js';
9
11
  export type { SequentialEditDiagnostic } from './sequential-diagnostics.js';
10
12
  export { createDiagnostics, detectOwnershipConflicts } from './diagnostics.js';