@wundam/orchex 1.0.0-rc.6 → 1.0.0-rc.8

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
@@ -19,7 +19,7 @@ Your AI assistant does tasks one at a time. Orchex makes it do 10 at once — sa
19
19
  ## Prerequisites
20
20
 
21
21
  - [Node.js](https://nodejs.org/) >= 18
22
- - LLM API key (one of the following):
22
+ - LLM API key set via environment variable **or** store on the [dashboard](https://orchex.dev/dashboard/keys) and sync with `orchex login`:
23
23
  - `ANTHROPIC_API_KEY` for Anthropic Claude
24
24
  - `OPENAI_API_KEY` for OpenAI (GPT-4.1, o1, o3)
25
25
  - `GEMINI_API_KEY` for Google Gemini
@@ -47,11 +47,11 @@ Connect to orchex cloud for managed execution:
47
47
  orchex login
48
48
  ```
49
49
 
50
- Your browser opens — log in or create a free account, click **Allow**. Token saved automatically.
50
+ Your browser opens — log in or create a free account, click **Allow**. Token saved automatically. API keys stored on the dashboard are synced to your local machine so `orchex run` works without environment variables.
51
51
 
52
52
  ```bash
53
53
  orchex status # Check tier and trial runs
54
- orchex logout # Clear credentials
54
+ orchex logout # Clear credentials and cached keys
55
55
  orchex --help # All commands
56
56
  ```
57
57
 
@@ -0,0 +1,8 @@
1
+ import type { Config } from './config.js';
2
+ import type { ExecutionReport } from './intelligence/execution-report.js';
3
+ /**
4
+ * Fire-and-forget sync of an execution report to the cloud dashboard.
5
+ * Only runs when user is logged in (mode=cloud + apiKey present).
6
+ * Never blocks or throws — network failures are silently ignored.
7
+ */
8
+ export declare function syncReportToCloud(config: Config, report: ExecutionReport): Promise<void>;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Fire-and-forget sync of an execution report to the cloud dashboard.
3
+ * Only runs when user is logged in (mode=cloud + apiKey present).
4
+ * Never blocks or throws — network failures are silently ignored.
5
+ */
6
+ export async function syncReportToCloud(config, report) {
7
+ if (config.mode !== 'cloud' || !config.apiKey)
8
+ return;
9
+ const apiUrl = config.apiUrl;
10
+ try {
11
+ const resp = await fetch(`${apiUrl}/api/v1/history/sync`, {
12
+ method: 'POST',
13
+ headers: {
14
+ 'Authorization': `Bearer ${config.apiKey}`,
15
+ 'Content-Type': 'application/json',
16
+ },
17
+ body: JSON.stringify({ report }),
18
+ });
19
+ if (resp.status === 401) {
20
+ console.warn('Cloud sync: token expired. Run `orchex login` to re-authenticate.');
21
+ }
22
+ }
23
+ catch {
24
+ // Network error — silently skip
25
+ }
26
+ }
package/dist/config.d.ts CHANGED
@@ -7,6 +7,10 @@ export type LLMProvider = z.infer<typeof LLMProviderSchema>;
7
7
  * Priority: ORCHEX_PROVIDER env var > first available API key
8
8
  */
9
9
  export declare function detectProvider(): LLMProvider;
10
+ /**
11
+ * Log the detected provider and masked key at startup.
12
+ */
13
+ export declare function logProviderDetection(): void;
10
14
  /**
11
15
  * Get the API key for a specific provider from environment variables.
12
16
  */
package/dist/config.js CHANGED
@@ -3,6 +3,8 @@ import * as path from 'path';
3
3
  import * as os from 'os';
4
4
  import { z } from 'zod';
5
5
  import { TierIdSchema } from './tiers.js';
6
+ import { createLogger } from './logging.js';
7
+ const log = createLogger('config');
6
8
  // Well-known URLs
7
9
  export const PRODUCTION_URL = 'https://orchex.dev';
8
10
  // ============================================================================
@@ -35,6 +37,19 @@ export function detectProvider() {
35
37
  // Default to Anthropic (original behavior)
36
38
  return 'anthropic';
37
39
  }
40
+ function maskKey(key) {
41
+ if (key.length <= 8)
42
+ return '****';
43
+ return key.slice(0, 4) + '...' + key.slice(-4);
44
+ }
45
+ /**
46
+ * Log the detected provider and masked key at startup.
47
+ */
48
+ export function logProviderDetection() {
49
+ const provider = detectProvider();
50
+ const key = getProviderApiKey(provider);
51
+ log.info({ provider, key: key ? maskKey(key) : 'none' }, 'provider_detected');
52
+ }
38
53
  /**
39
54
  * Get the API key for a specific provider from environment variables.
40
55
  */
package/dist/index.js CHANGED
@@ -190,6 +190,7 @@ ORCHESTRATION:
190
190
  orchex run "..." --yes Skip approval prompt (auto-approve)
191
191
  orchex run "..." --dry-run Generate plan only, don't execute
192
192
  orchex run "..." --provider openai --model gpt-4.1
193
+ orchex complete --archive Archive active orchestration and unblock future runs
193
194
 
194
195
  CLOUD COMMANDS:
195
196
  orchex login Authenticate with orchex cloud (opens browser)
@@ -208,7 +209,7 @@ CONFIG:
208
209
  orchex config --staging Configure for staging server (reads ORCHEX_API_URL)
209
210
 
210
211
  ENVIRONMENT VARIABLES:
211
- ORCHEX_API_URL Override API server URL (e.g. https://stage.orchex.dev)
212
+ ORCHEX_API_URL Override API server URL (e.g. https://orchex.dev)
212
213
  Priority: --api-url flag > ORCHEX_API_URL > config file > default
213
214
 
214
215
  MCP SERVER (invoked by your AI assistant):
@@ -506,40 +507,64 @@ async function handleRunCommand(args) {
506
507
  }
507
508
  // Check for existing orchestration
508
509
  if (await manifestExists(projectDir)) {
509
- console.error('An active orchestration already exists. Run `orchex complete --archive` first.');
510
+ console.error('An active orchestration already exists. Run `orchex complete --archive` to clear it.');
510
511
  process.exit(1);
511
512
  }
512
513
  // Init and execute
513
514
  console.log(`\nInitializing orchestration: "${plan.title}"\n`);
514
515
  await initOrchestration(projectDir, plan.title, streamDefs);
515
516
  const allResponses = [];
516
- let done = false;
517
- let waveNum = 1;
518
- while (!done) {
519
- console.log(`Executing wave ${waveNum}...`);
520
- const response = await executeWave(projectDir, exec, { model: modelFlag });
521
- allResponses.push(response);
522
- const completed = response.streams.filter(s => s.status === 'complete').length;
523
- const failed = response.streams.filter(s => s.status === 'failed').length;
524
- console.log(` Wave ${waveNum}: ${completed} complete, ${failed} failed`);
525
- done = response.done;
526
- waveNum++;
527
- }
528
- // Generate report
529
- const manifest = await loadManifest(projectDir);
530
- const report = generateReport(manifest, allResponses);
531
- const reportPath = await saveReportLocally(projectDir, report);
532
- // Learning summary
533
- const summary = generateLearningSummary(report);
534
- console.log('\n--- Orchex learned ---');
535
- for (const insight of summary) {
536
- console.log(` ${insight}`);
537
- }
538
- console.log(`\nReport saved: ${reportPath}`);
539
- console.log(`Quality score: ${report.planQualityScore}/100\n`);
540
- // Mark first-run complete after successful orchestration
541
- if (await isFirstRun(projectDir)) {
542
- await markFirstRunComplete(projectDir);
517
+ let executionError;
518
+ try {
519
+ let done = false;
520
+ let waveNum = 1;
521
+ while (!done) {
522
+ console.log(`Executing wave ${waveNum}...`);
523
+ const response = await executeWave(projectDir, exec, { model: modelFlag });
524
+ allResponses.push(response);
525
+ const completed = response.streams.filter(s => s.status === 'complete').length;
526
+ const failed = response.streams.filter(s => s.status === 'failed').length;
527
+ console.log(` Wave ${waveNum}: ${completed} complete, ${failed} failed`);
528
+ done = response.done;
529
+ waveNum++;
530
+ }
531
+ // Generate report
532
+ const manifest = await loadManifest(projectDir);
533
+ const report = generateReport(manifest, allResponses);
534
+ const reportPath = await saveReportLocally(projectDir, report);
535
+ // Sync report to cloud dashboard (fire-and-forget, only if logged in)
536
+ const { syncReportToCloud } = await import('./cloud-sync.js');
537
+ await syncReportToCloud(config, report);
538
+ // Learning summary
539
+ const summary = generateLearningSummary(report);
540
+ console.log('\n--- Orchex learned ---');
541
+ for (const insight of summary) {
542
+ console.log(` ${insight}`);
543
+ }
544
+ console.log(`\nReport saved: ${reportPath}`);
545
+ console.log(`Quality score: ${report.planQualityScore}/100\n`);
546
+ // Mark first-run complete after successful orchestration
547
+ if (await isFirstRun(projectDir)) {
548
+ await markFirstRunComplete(projectDir);
549
+ }
550
+ }
551
+ catch (err) {
552
+ executionError = err;
553
+ console.error(`\nExecution error: ${executionError.message}`);
554
+ }
555
+ finally {
556
+ // Always archive to unblock future runs
557
+ try {
558
+ const { archiveOrchestration } = await import('./manifest.js');
559
+ const archiveName = await archiveOrchestration(projectDir);
560
+ console.log(`Orchestration archived: ${archiveName}`);
561
+ }
562
+ catch {
563
+ // Best-effort — report is already saved if execution succeeded
564
+ }
565
+ }
566
+ if (executionError) {
567
+ process.exit(1);
543
568
  }
544
569
  }
545
570
  async function main() {
@@ -584,6 +609,30 @@ async function main() {
584
609
  }
585
610
  return;
586
611
  }
612
+ if (args[0] === 'complete') {
613
+ const { manifestExists, archiveOrchestration } = await import('./manifest.js');
614
+ const subArgs = args.slice(1);
615
+ const pdFlag = subArgs.indexOf('--project-dir');
616
+ const projectDir = pdFlag !== -1 && subArgs[pdFlag + 1] ? subArgs[pdFlag + 1] : process.cwd();
617
+ if (!(await manifestExists(projectDir))) {
618
+ console.log('No active orchestration to archive.');
619
+ return;
620
+ }
621
+ if (subArgs.includes('--archive')) {
622
+ try {
623
+ const archiveName = await archiveOrchestration(projectDir);
624
+ console.log(`Orchestration archived: ${archiveName}`);
625
+ }
626
+ catch (err) {
627
+ console.error(`Archive failed: ${err.message}`);
628
+ process.exit(1);
629
+ }
630
+ }
631
+ else {
632
+ console.error('Usage: orchex complete --archive');
633
+ }
634
+ return;
635
+ }
587
636
  if (args[0] === '--version' || args[0] === '-v') {
588
637
  const { createRequire } = await import('module');
589
638
  const require = createRequire(import.meta.url);
@@ -2,7 +2,7 @@
2
2
  * Token cost tracking and estimation for Orchex Learn.
3
3
  * Provides cost estimation and aggregation for multi-provider token usage.
4
4
  */
5
- import type { TelemetryEvent } from '../telemetry/telemetry-types.js';
5
+ import type { TelemetryEvent } from './event-types.js';
6
6
  /**
7
7
  * Token costs per 1000 tokens (in USD).
8
8
  * Prices as of February 2026 - should be updated periodically.
@@ -0,0 +1,89 @@
1
+ import type { ErrorCategory } from './error-analyzer.js';
2
+ import type { BudgetViolationType } from '../types.js';
3
+ export interface TelemetryEvent {
4
+ id: string;
5
+ sessionHash: string;
6
+ eventType: 'orchestration_start' | 'orchestration_complete' | 'stream_complete' | 'stream_failed' | 'self_heal_triggered';
7
+ timestamp: string;
8
+ durationMs?: number;
9
+ streamCount?: number;
10
+ waveCount?: number;
11
+ parallelCount?: number;
12
+ success?: boolean;
13
+ errorCategory?: ErrorCategory;
14
+ retryCount?: number;
15
+ selfHealCount?: number;
16
+ complexityScore?: number;
17
+ tokensInput?: number;
18
+ tokensOutput?: number;
19
+ provider?: string;
20
+ model?: string;
21
+ tier?: string;
22
+ /** Estimated context tokens before execution */
23
+ contextTokensEstimated?: number;
24
+ /** Actual context tokens from API response */
25
+ contextTokensActual?: number;
26
+ /** Budget utilization ratio (0-1) */
27
+ contextBudgetUtilization?: number;
28
+ /** Whether budget limits were exceeded */
29
+ budgetViolationType?: BudgetViolationType;
30
+ /** Provider's context window limit for this model */
31
+ providerContextLimit?: number;
32
+ /** Whether cache was hit */
33
+ cacheHit?: boolean;
34
+ /** Tokens read from cache */
35
+ cacheReadTokens?: number;
36
+ /** Tokens written to cache */
37
+ cacheWriteTokens?: number;
38
+ /** Milliseconds saved by parallel execution */
39
+ timeSavedMs?: number;
40
+ /** Hypothetical sequential execution time in ms */
41
+ sequentialMs?: number;
42
+ /** Actual parallel execution time in ms */
43
+ parallelMs?: number;
44
+ /** Stream name for category-based analysis */
45
+ streamName?: string;
46
+ /** SHA-256 hash of the prompt sent to LLM */
47
+ promptHash?: string;
48
+ /** SHA-256 hash of the artifact response */
49
+ artifactHash?: string;
50
+ }
51
+ export interface TelemetryAggregate {
52
+ period: string;
53
+ orchestrationsTotal: number;
54
+ orchestrationsSuccess: number;
55
+ orchestrationsFailed: number;
56
+ streamsTotal: number;
57
+ streamsSuccess: number;
58
+ streamsFailed: number;
59
+ avgDurationMs?: number;
60
+ p50DurationMs?: number;
61
+ p95DurationMs?: number;
62
+ errorsByCategory: Record<ErrorCategory, number>;
63
+ selfHealTriggered: number;
64
+ selfHealSuccess: number;
65
+ tokensInputTotal: number;
66
+ tokensOutputTotal: number;
67
+ /** Average context budget utilization (0-1) */
68
+ avgContextUtilization?: number;
69
+ /** Count of soft limit violations */
70
+ softViolationCount: number;
71
+ /** Count of hard limit violations */
72
+ hardViolationCount: number;
73
+ /** Streams that failed due to context exceeded */
74
+ contextExceededFailures: number;
75
+ /** Cache hit rate (0-1) */
76
+ cacheHitRate?: number;
77
+ /** Total tokens read from cache */
78
+ cacheReadTokensTotal: number;
79
+ /** Total tokens written to cache */
80
+ cacheWriteTokensTotal: number;
81
+ /** Estimated cost savings from cache */
82
+ estimatedCacheSavings?: number;
83
+ /** Total milliseconds saved by parallel execution */
84
+ timeSavedMsTotal: number;
85
+ /** Total hypothetical sequential time in ms */
86
+ sequentialMsTotal: number;
87
+ /** Total actual parallel time in ms */
88
+ parallelMsTotal: number;
89
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -13,6 +13,9 @@ export interface ReportStreamResult {
13
13
  executionTimeMs?: number;
14
14
  selfHealCount: number;
15
15
  errorCategory?: string;
16
+ provider?: string;
17
+ model?: string;
18
+ filesChanged?: string[];
16
19
  }
17
20
  /**
18
21
  * Full execution report — deterministic, no LLM needed.
@@ -51,6 +51,9 @@ export function generateReport(manifest, responses, options) {
51
51
  executionTimeMs: sr.executionTimeMs,
52
52
  selfHealCount: fixStreamIds.has(sr.id) ? 1 : 0,
53
53
  errorCategory: sr.errorDetail?.category,
54
+ provider: sr.provider,
55
+ model: sr.model,
56
+ filesChanged: sr.filesChanged,
54
57
  }));
55
58
  // Token aggregation
56
59
  let totalInput = 0;
@@ -2,7 +2,7 @@
2
2
  * Learning engine for adaptive threshold adjustment in Orchex Learn.
3
3
  * Correlates context characteristics with execution success and adapts thresholds.
4
4
  */
5
- import type { TelemetryEvent } from '../telemetry/telemetry-types.js';
5
+ import type { TelemetryEvent } from './event-types.js';
6
6
  /**
7
7
  * Stream categories for per-category learning.
8
8
  * Canonical source of truth for stream categorization across all intelligence modules.
@@ -1,4 +1,4 @@
1
- import type { TelemetryAggregate } from '../telemetry/telemetry-types.js';
1
+ import type { TelemetryAggregate } from './event-types.js';
2
2
  import type { ErrorCategory } from './error-analyzer.js';
3
3
  export interface RetryPattern {
4
4
  category: ErrorCategory;
@@ -5,7 +5,7 @@
5
5
  * NOTE: StreamCategory and categorizeStream are imported from learning-engine.ts
6
6
  * to maintain a single source of truth for stream categorization.
7
7
  */
8
- import type { TelemetryEvent } from '../telemetry/telemetry-types.js';
8
+ import type { TelemetryEvent } from './event-types.js';
9
9
  import type { StreamResult } from '../types.js';
10
10
  import { type StreamCategory, categorizeStream as categorizeStreamFromLearningEngine } from './learning-engine.js';
11
11
  export type { StreamCategory };
@@ -12,7 +12,7 @@ import { runCommands } from './commands.js';
12
12
  import * as fs from 'fs/promises';
13
13
  import * as path from 'path';
14
14
  import { loadConfig, getConfiguredModel } from './config.js';
15
- import { getTier } from './tiers.js';
15
+ import { getTier, getEffectiveTier } from './tiers.js';
16
16
  import { broadcaster } from './execution-broadcaster.js';
17
17
  import { streamResultToTelemetryEvent, appendLocalEvents, runLearningCycle, } from './intelligence/learning-engine.js';
18
18
  import { routeStream, loadRoutingRules } from './intelligence/smart-router.js';
@@ -649,7 +649,14 @@ async function executeWaveInternal(projectDir, executor, options, logger) {
649
649
  // 7a. SELF-HEAL — generate fix streams for failed streams (tier-gated)
650
650
  const fixStreamsGenerated = [];
651
651
  const currentManifest = await loadManifest(projectDir);
652
- const tier = getTier(config.tier ?? 'free');
652
+ const effectiveTierId = config.mode === 'cloud' && config.apiKey
653
+ ? getEffectiveTier({
654
+ tier: config.tier ?? 'free',
655
+ createdAt: config.accountCreatedAt ?? new Date().toISOString(),
656
+ trialRunsRemaining: config.trialRunsRemaining ?? 0,
657
+ })
658
+ : (config.tier ?? 'free');
659
+ const tier = getTier(effectiveTierId);
653
660
  if (tier.selfHealing === 'full') {
654
661
  for (const sr of streamResults) {
655
662
  if (sr.status !== 'failed' || sr.id === 'unknown')
package/dist/tools.js CHANGED
@@ -865,6 +865,18 @@ export function registerTools(server, executor, context) {
865
865
  done: true,
866
866
  }]);
867
867
  await saveReportLocally(projectDir, report);
868
+ // Sync to cloud if locally authenticated (no ToolContext historyStore)
869
+ if (!context?.historyStore) {
870
+ try {
871
+ const { loadConfig } = await import('./config.js');
872
+ const { syncReportToCloud } = await import('./cloud-sync.js');
873
+ const syncConfig = await loadConfig();
874
+ await syncReportToCloud(syncConfig, report);
875
+ }
876
+ catch {
877
+ // Sync is best-effort
878
+ }
879
+ }
868
880
  const summary = generateLearningSummary(report);
869
881
  await logger.info('execution_report_saved', {
870
882
  runId: report.runId,
@@ -980,6 +992,18 @@ export function registerTools(server, executor, context) {
980
992
  const reportManifest = await loadManifest(projectDir);
981
993
  const report = generateReport(reportManifest, [result]);
982
994
  await saveReportLocally(projectDir, report);
995
+ // Sync to cloud if locally authenticated (no ToolContext historyStore)
996
+ if (!context?.historyStore) {
997
+ try {
998
+ const { loadConfig } = await import('./config.js');
999
+ const { syncReportToCloud } = await import('./cloud-sync.js');
1000
+ const syncConfig = await loadConfig();
1001
+ await syncReportToCloud(syncConfig, report);
1002
+ }
1003
+ catch {
1004
+ // Sync is best-effort
1005
+ }
1006
+ }
983
1007
  const summary = generateLearningSummary(report);
984
1008
  await logger.info('execution_report_saved', {
985
1009
  runId: report.runId,
@@ -1637,6 +1661,18 @@ export function registerTools(server, executor, context) {
1637
1661
  const manifest = await loadManifest(projectDir);
1638
1662
  const report = generateReport(manifest, allResponses);
1639
1663
  await saveReportLocally(projectDir, report);
1664
+ // Sync to cloud if locally authenticated (no ToolContext historyStore)
1665
+ if (!context?.historyStore) {
1666
+ try {
1667
+ const { loadConfig } = await import('./config.js');
1668
+ const { syncReportToCloud } = await import('./cloud-sync.js');
1669
+ const syncConfig = await loadConfig();
1670
+ await syncReportToCloud(syncConfig, report);
1671
+ }
1672
+ catch {
1673
+ // Sync is best-effort
1674
+ }
1675
+ }
1640
1676
  // 8. Generate learning summary
1641
1677
  const summary = generateLearningSummary(report);
1642
1678
  // 9. Mark first-run complete
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@wundam/orchex",
3
- "version": "1.0.0-rc.6",
3
+ "mcpName": "com.orchex/orchex",
4
+ "version": "1.0.0-rc.8",
4
5
  "description": "Autopilot AI orchestration — auto-plan, parallelize, and execute with ownership enforcement",
5
6
  "type": "module",
6
7
  "main": "dist/index.js",
@@ -75,6 +76,8 @@
75
76
  "dist/config.d.ts",
76
77
  "dist/key-cache.js",
77
78
  "dist/key-cache.d.ts",
79
+ "dist/cloud-sync.js",
80
+ "dist/cloud-sync.d.ts",
78
81
  "dist/context-builder.js",
79
82
  "dist/context-builder.d.ts",
80
83
  "dist/cost.js",