@aria_asi/cli 0.2.29 → 0.2.31

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 (98) hide show
  1. package/dist/aria-connector/src/connectors/claude-code.d.ts.map +1 -1
  2. package/dist/aria-connector/src/connectors/claude-code.js +88 -20
  3. package/dist/aria-connector/src/connectors/claude-code.js.map +1 -1
  4. package/dist/aria-connector/src/connectors/codex.d.ts.map +1 -1
  5. package/dist/aria-connector/src/connectors/codex.js +526 -2
  6. package/dist/aria-connector/src/connectors/codex.js.map +1 -1
  7. package/dist/aria-connector/src/connectors/doctrine-trigger-map.d.ts +7 -0
  8. package/dist/aria-connector/src/connectors/doctrine-trigger-map.d.ts.map +1 -0
  9. package/dist/aria-connector/src/connectors/doctrine-trigger-map.js +87 -0
  10. package/dist/aria-connector/src/connectors/doctrine-trigger-map.js.map +1 -0
  11. package/dist/aria-connector/src/connectors/must-read.d.ts +4 -0
  12. package/dist/aria-connector/src/connectors/must-read.d.ts.map +1 -0
  13. package/dist/aria-connector/src/connectors/must-read.js +111 -0
  14. package/dist/aria-connector/src/connectors/must-read.js.map +1 -0
  15. package/dist/aria-connector/src/connectors/opencode.d.ts.map +1 -1
  16. package/dist/aria-connector/src/connectors/opencode.js +2 -0
  17. package/dist/aria-connector/src/connectors/opencode.js.map +1 -1
  18. package/dist/aria-connector/src/connectors/runtime.d.ts.map +1 -1
  19. package/dist/aria-connector/src/connectors/runtime.js +231 -19
  20. package/dist/aria-connector/src/connectors/runtime.js.map +1 -1
  21. package/dist/aria-connector/src/connectors/shell.d.ts.map +1 -1
  22. package/dist/aria-connector/src/connectors/shell.js +76 -3
  23. package/dist/aria-connector/src/connectors/shell.js.map +1 -1
  24. package/dist/aria-connector/src/self-update.d.ts +2 -1
  25. package/dist/aria-connector/src/self-update.d.ts.map +1 -1
  26. package/dist/aria-connector/src/self-update.js +84 -8
  27. package/dist/aria-connector/src/self-update.js.map +1 -1
  28. package/dist/assets/hooks/aria-cognition-substrate-binding.mjs +53 -34
  29. package/dist/assets/hooks/aria-harness-via-sdk.mjs +126 -12
  30. package/dist/assets/hooks/aria-pre-tool-gate.mjs +185 -76
  31. package/dist/assets/hooks/aria-preturn-memory-gate.mjs +63 -14
  32. package/dist/assets/hooks/aria-repo-doctrine-gate.mjs +2 -0
  33. package/dist/assets/hooks/aria-stop-gate.mjs +225 -52
  34. package/dist/assets/hooks/lib/canonical-lenses.mjs +6 -5
  35. package/dist/assets/hooks/lib/gate-loop-state.mjs +50 -0
  36. package/dist/assets/hooks/lib/hook-message-window.mjs +121 -0
  37. package/dist/assets/hooks/test-tier-lens-labeling.mjs +26 -58
  38. package/dist/assets/opencode-plugins/harness-gate/index.js +24 -2
  39. package/dist/assets/opencode-plugins/harness-stop/index.js +94 -5
  40. package/dist/runtime/auth-middleware.mjs +251 -0
  41. package/dist/runtime/codex-bridge.mjs +644 -0
  42. package/dist/runtime/discipline/CLAUDE.md +12 -0
  43. package/dist/runtime/discipline/doctrine_trigger_map.json +479 -0
  44. package/dist/runtime/doctrine_trigger_map.json +479 -0
  45. package/dist/runtime/fleet-engine.mjs +231 -0
  46. package/dist/runtime/harness-daemon.mjs +433 -0
  47. package/dist/runtime/local-phase.mjs +18 -0
  48. package/dist/runtime/manifest.json +1 -1
  49. package/dist/runtime/metering.mjs +100 -0
  50. package/dist/runtime/onboarding-engine.mjs +89 -0
  51. package/dist/runtime/plugin-engine.mjs +196 -0
  52. package/dist/runtime/sdk/BUNDLED.json +1 -1
  53. package/dist/runtime/sdk/index.d.ts +7 -0
  54. package/dist/runtime/sdk/index.js +120 -14
  55. package/dist/runtime/sdk/index.js.map +1 -1
  56. package/dist/runtime/service.mjs +1464 -67
  57. package/dist/runtime/vendor/aria-gate-runtime/index.d.ts +1 -1
  58. package/dist/runtime/vendor/aria-gate-runtime/index.d.ts.map +1 -1
  59. package/dist/runtime/vendor/aria-gate-runtime/index.js +16 -1
  60. package/dist/runtime/vendor/aria-gate-runtime/index.js.map +1 -1
  61. package/dist/runtime/workflow-engine.mjs +322 -0
  62. package/dist/sdk/BUNDLED.json +1 -1
  63. package/dist/sdk/index.d.ts +7 -0
  64. package/dist/sdk/index.js +120 -14
  65. package/dist/sdk/index.js.map +1 -1
  66. package/hooks/aria-cognition-substrate-binding.mjs +53 -34
  67. package/hooks/aria-harness-via-sdk.mjs +126 -12
  68. package/hooks/aria-pre-tool-gate.mjs +185 -76
  69. package/hooks/aria-preturn-memory-gate.mjs +63 -14
  70. package/hooks/aria-repo-doctrine-gate.mjs +2 -0
  71. package/hooks/aria-stop-gate.mjs +225 -52
  72. package/hooks/lib/canonical-lenses.mjs +6 -5
  73. package/hooks/lib/gate-loop-state.mjs +50 -0
  74. package/hooks/lib/hook-message-window.mjs +121 -0
  75. package/hooks/test-tier-lens-labeling.mjs +26 -58
  76. package/opencode-plugins/harness-gate/index.js +24 -2
  77. package/opencode-plugins/harness-stop/index.js +94 -5
  78. package/package.json +2 -2
  79. package/runtime-src/auth-middleware.mjs +251 -0
  80. package/runtime-src/codex-bridge.mjs +644 -0
  81. package/runtime-src/fleet-engine.mjs +231 -0
  82. package/runtime-src/harness-daemon.mjs +433 -0
  83. package/runtime-src/local-phase.mjs +18 -0
  84. package/runtime-src/metering.mjs +100 -0
  85. package/runtime-src/onboarding-engine.mjs +89 -0
  86. package/runtime-src/plugin-engine.mjs +196 -0
  87. package/runtime-src/service.mjs +1464 -67
  88. package/runtime-src/workflow-engine.mjs +322 -0
  89. package/scripts/bundle-sdk.mjs +5 -0
  90. package/src/connectors/claude-code.ts +98 -20
  91. package/src/connectors/codex.ts +534 -1
  92. package/src/connectors/doctrine-trigger-map.ts +112 -0
  93. package/src/connectors/must-read.ts +113 -0
  94. package/src/connectors/opencode.ts +3 -0
  95. package/src/connectors/runtime.ts +241 -21
  96. package/src/connectors/shell.ts +78 -3
  97. package/src/self-update.ts +89 -8
  98. package/dist/cli-0.2.0.tgz +0 -0
@@ -0,0 +1,100 @@
1
+ import { existsSync, mkdirSync, appendFileSync, readFileSync } from 'node:fs';
2
+ import { join, dirname } from 'node:path';
3
+ import { randomUUID } from 'node:crypto';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+ const STATE_DIR = join(__dirname, '..', 'state', 'metering');
8
+
9
+ const COST_RATES = {
10
+ 'deepseek-v4-pro': { inputPer1k: 0.0006, outputPer1k: 0.0022 },
11
+ 'deepseek-v4-flash': { inputPer1k: 0.00015, outputPer1k: 0.0006 },
12
+ };
13
+
14
+ function ensureDir(p) {
15
+ if (!existsSync(p)) mkdirSync(p, { recursive: true });
16
+ }
17
+
18
+ export function brandModel(provider, model) {
19
+ if (provider === 'deepseek' && model?.includes('v4-pro')) return 'Aria Intelligence Pro';
20
+ if (provider === 'deepseek' && model?.includes('v4-flash')) return 'Aria Intelligence Flash';
21
+ return 'Aria Intelligence';
22
+ }
23
+
24
+ export function recordTokenUsage(record) {
25
+ const { tenantId, agentId, agentName, department, sessionId, provider, model, usage, requestType } = record;
26
+ if (!usage || !tenantId) return null;
27
+ const inputTokens = usage.prompt_tokens || usage.input_tokens || 0;
28
+ const outputTokens = usage.completion_tokens || usage.output_tokens || 0;
29
+ const totalTokens = usage.total_tokens || (inputTokens + outputTokens);
30
+ if (totalTokens === 0) return null;
31
+ const modelKey = model?.includes('v4-pro') ? 'deepseek-v4-pro' : model?.includes('v4-flash') ? 'deepseek-v4-flash' : null;
32
+ const rates = COST_RATES[modelKey] || COST_RATES['deepseek-v4-flash'];
33
+ const estimatedCostUsd = (inputTokens / 1000) * rates.inputPer1k + (outputTokens / 1000) * rates.outputPer1k;
34
+ const entry = {
35
+ id: randomUUID(), tenantId, agentId: agentId || null, agentName: agentName || null,
36
+ department: department || null, sessionId: sessionId || '', timestamp: new Date().toISOString(),
37
+ provider: provider || 'unknown', modelLabel: brandModel(provider, model),
38
+ inputTokens, outputTokens, totalTokens,
39
+ estimatedCostUsd: Math.round(estimatedCostUsd * 1e8) / 1e8,
40
+ requestType: requestType || 'chat',
41
+ };
42
+ ensureDir(STATE_DIR);
43
+ appendFileSync(join(STATE_DIR, `${tenantId}.jsonl`), JSON.stringify(entry) + '\n');
44
+ return entry;
45
+ }
46
+
47
+ export function getUsageSummary(tenantId, opts = {}) {
48
+ const filePath = join(STATE_DIR, `${tenantId}.jsonl`);
49
+ if (!existsSync(filePath)) return { tenantId, records: 0, totalTokens: 0, totalCostUsd: 0, byDepartment: {}, byAgent: {}, byModel: {}, byRequestType: {} };
50
+ const lines = readFileSync(filePath, 'utf-8').split('\n').filter(Boolean);
51
+ const from = opts.from ? new Date(opts.from) : null;
52
+ const to = opts.to ? new Date(opts.to) : null;
53
+ const summary = { tenantId, records: 0, totalTokens: 0, totalCostUsd: 0, byDepartment: {}, byAgent: {}, byModel: {}, byRequestType: {} };
54
+ for (const line of lines) {
55
+ try {
56
+ const r = JSON.parse(line);
57
+ if (from && new Date(r.timestamp) < from) continue;
58
+ if (to && new Date(r.timestamp) > to) continue;
59
+ summary.records++; summary.totalTokens += r.totalTokens; summary.totalCostUsd += r.estimatedCostUsd;
60
+ const dept = r.department || 'unassigned';
61
+ summary.byDepartment[dept] = summary.byDepartment[dept] || { tokens: 0, cost: 0, calls: 0 };
62
+ summary.byDepartment[dept].tokens += r.totalTokens; summary.byDepartment[dept].cost += r.estimatedCostUsd; summary.byDepartment[dept].calls++;
63
+ const agent = r.agentId || 'unassigned';
64
+ summary.byAgent[agent] = summary.byAgent[agent] || { tokens: 0, cost: 0, calls: 0, name: r.agentName };
65
+ summary.byAgent[agent].tokens += r.totalTokens; summary.byAgent[agent].cost += r.estimatedCostUsd; summary.byAgent[agent].calls++;
66
+ const mdl = r.modelLabel || 'unknown';
67
+ summary.byModel[mdl] = summary.byModel[mdl] || { tokens: 0, cost: 0, calls: 0 };
68
+ summary.byModel[mdl].tokens += r.totalTokens; summary.byModel[mdl].cost += r.estimatedCostUsd; summary.byModel[mdl].calls++;
69
+ const rt = r.requestType || 'chat';
70
+ summary.byRequestType[rt] = summary.byRequestType[rt] || { tokens: 0, cost: 0, calls: 0 };
71
+ summary.byRequestType[rt].tokens += r.totalTokens; summary.byRequestType[rt].cost += r.estimatedCostUsd; summary.byRequestType[rt].calls++;
72
+ } catch {}
73
+ }
74
+ summary.totalCostUsd = Math.round(summary.totalCostUsd * 1e6) / 1e6;
75
+ return summary;
76
+ }
77
+
78
+ export function getSubscriptionTier(tierId) {
79
+ const TIERS = {
80
+ starter: { id: 'starter', label: 'Starter', includedTokens: 500_000, maxOpenClaws: 3, priceUsd: 49, overagePerToken: 0.0001 },
81
+ pro: { id: 'pro', label: 'Pro', includedTokens: 5_000_000, maxOpenClaws: 10, priceUsd: 199, overagePerToken: 0.00008 },
82
+ enterprise: { id: 'enterprise', label: 'Enterprise', includedTokens: 50_000_000, maxOpenClaws: 100, priceUsd: 999, overagePerToken: 0.00005 },
83
+ };
84
+ return TIERS[tierId] || TIERS.starter;
85
+ }
86
+
87
+ export function getBillingSummary(tenantId, tierId = 'starter') {
88
+ const usage = getUsageSummary(tenantId);
89
+ const tier = getSubscriptionTier(tierId);
90
+ const overageTokens = Math.max(0, usage.totalTokens - tier.includedTokens);
91
+ const overageCost = overageTokens * tier.overagePerToken;
92
+ const utilizationPct = tier.includedTokens > 0 ? Math.min(100, Math.round((usage.totalTokens / tier.includedTokens) * 100)) : 0;
93
+ return {
94
+ tenantId, tier, period: { from: null, to: new Date().toISOString() },
95
+ usage: { totalTokens: usage.totalTokens, totalCostUsd: usage.totalCostUsd, records: usage.records },
96
+ billing: { basePriceUsd: tier.priceUsd, overageTokens, overageCostUsd: Math.round(overageCost * 100) / 100, totalDueUsd: tier.priceUsd + Math.round(overageCost * 100) / 100 },
97
+ utilizationPct, upsellTriggers: { at50: utilizationPct >= 50, at80: utilizationPct >= 80, at100: utilizationPct >= 100 },
98
+ breakdown: { byDepartment: usage.byDepartment, byAgent: usage.byAgent, byModel: usage.byModel },
99
+ };
100
+ }
@@ -0,0 +1,89 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import { join, dirname } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+ const STATE_DIR = join(__dirname, '..', 'state');
8
+
9
+ export const STEPS = [
10
+ 'greeting', 'industry-discovery', 'team-size', 'role-selection',
11
+ 'openclaw-allocation', 'plugin-preference', 'llm-preference',
12
+ 'workflow-customization', 'credentials', 'confirmation', 'complete',
13
+ ];
14
+
15
+ const STEP_PROMPTS = {
16
+ greeting: `You are Aria, the AI workforce architect. A new client just started onboarding. Introduce yourself warmly and ask: "What does your company do? What industry are you in?" Keep it to 2-3 sentences.`,
17
+ 'industry-discovery': `Based on what the client said, identify their industry (real-estate, saas, recruiting, ecommerce, generic) and confirm. Then ask about their team size. Extract: {{FIELD:industry:value}} {{FIELD:companyName:value}}.`,
18
+ 'team-size': `Acknowledge team size. Recommend departments for their industry. Ask which ones interest them. Extract: {{FIELD:teamSize:value}}.`,
19
+ 'role-selection': `List agent roles for their selected departments. Confirm which agents they want. Extract: {{FIELD:selectedAgents:list}}.`,
20
+ 'openclaw-allocation': `Explain OpenClaws and tiers: Starter $49/mo 3 OpenClaws 500K tokens, Pro $199/mo 10 OpenClaws 5M tokens, Enterprise $999/mo 100 OpenClaws 50M tokens. Recommend based on agent count. Extract: {{FIELD:openClaws:value}} {{FIELD:tier:value}}.`,
21
+ 'plugin-preference': `Offer plugins: CRM Hub, Communication Hub, KPI Dashboard, Compliance Guard. Can skip. Extract: {{FIELD:plugins:list}}.`,
22
+ 'llm-preference': `Explain managed AI (Aria Intelligence) vs BYOK option. Extract: {{FIELD:llmPreference:value}}.`,
23
+ 'workflow-customization': `For selected agents, ask about workflow customization (auto-score vs manual review, approval thresholds, escalation). Extract: {{FIELD:workflowConfig:object}}.`,
24
+ credentials: `Say: "Almost done! To set up your dashboard login, I need your email address. What email should we use for your account?" Extract: {{FIELD:email:value}}.`,
25
+ confirmation: `Present full HQ config summary including their email. Ask to confirm deployment. Extract: {{FIELD:confirmed:true}}.`,
26
+ };
27
+
28
+ export function advanceStep(current) {
29
+ const idx = STEPS.indexOf(current);
30
+ return idx >= 0 && idx < STEPS.length - 1 ? STEPS[idx + 1] : 'complete';
31
+ }
32
+
33
+ function extractFields(text) {
34
+ const fields = {};
35
+ const rx = /\{\{FIELD:(\w+):(\w+)\}\}/g;
36
+ let m;
37
+ while ((m = rx.exec(text)) !== null) {
38
+ if (m[2] !== 'list' && m[2] !== 'object') fields[m[1]] = m[2];
39
+ }
40
+ return fields;
41
+ }
42
+
43
+ export function createOnboardingSession(tenantId) {
44
+ return {
45
+ id: randomUUID(), tenantId, step: 'greeting',
46
+ data: { industry: null, companyName: null, teamSize: null, selectedAgents: [], openClaws: 3, tier: 'starter', plugins: [], llmPreference: 'managed', workflowConfig: {}, email: null, password: null, confirmed: false },
47
+ history: [], createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
48
+ };
49
+ }
50
+
51
+ export function getSessionPath(tenantId) {
52
+ return join(STATE_DIR, `onboarding-${tenantId}.json`);
53
+ }
54
+
55
+ export function loadSession(tenantId) {
56
+ const p = getSessionPath(tenantId);
57
+ return existsSync(p) ? JSON.parse(readFileSync(p, 'utf-8')) : null;
58
+ }
59
+
60
+ export function saveSession(session) {
61
+ session.updatedAt = new Date().toISOString();
62
+ writeFileSync(getSessionPath(session.tenantId), JSON.stringify(session, null, 2));
63
+ return session;
64
+ }
65
+
66
+ export function getStepPrompt(step) {
67
+ return STEP_PROMPTS[step] || STEP_PROMPTS.greeting;
68
+ }
69
+
70
+ export function processResponse(session, llmResponse) {
71
+ const extracted = extractFields(llmResponse);
72
+ for (const [k, v] of Object.entries(extracted)) { if (k in session.data) session.data[k] = v; }
73
+ session.history.push({ role: 'assistant', content: llmResponse, step: session.step, timestamp: new Date().toISOString() });
74
+ if ((Object.keys(extracted).length > 0 || session.step === 'greeting') && session.data.confirmed !== true) {
75
+ // Don't auto-advance from credentials — service handler manages the two-phase flow
76
+ if (session.step !== 'credentials') {
77
+ session.step = advanceStep(session.step);
78
+ }
79
+ }
80
+ if (session.data.confirmed === true) session.step = 'complete';
81
+ return session;
82
+ }
83
+
84
+ export function buildFleetConfig(session) {
85
+ const agents = (session.data.selectedAgents || []).map((id, i) => ({
86
+ id: `${session.tenantId}-${id}-${i}`, templateId: id, name: id, department: 'unassigned', tier: 2,
87
+ }));
88
+ return { agents, maxOpenClaws: session.data.openClaws || 3, industry: session.data.industry || 'generic', tier: session.data.tier || 'starter', plugins: session.data.plugins || [], llmPreference: session.data.llmPreference || 'managed', workflowConfig: session.data.workflowConfig || {} };
89
+ }
@@ -0,0 +1,196 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { join, dirname } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ const __dirname = dirname(fileURLToPath(import.meta.url));
6
+ const STATE_DIR = join(__dirname, '..', 'state');
7
+
8
+ function ensureDir(p) {
9
+ if (!existsSync(p)) mkdirSync(p, { recursive: true });
10
+ }
11
+
12
+ const PLUGIN_REGISTRY = {
13
+ 'crm-hub': {
14
+ id: 'crm-hub',
15
+ name: 'CRM Hub',
16
+ description: 'Contacts, deals, pipeline management, and customer data',
17
+ version: '1.0.0',
18
+ hooks: ['onMessage', 'onAction', 'onWebhook'],
19
+ defaultConfig: {
20
+ pipelineStages: ['lead', 'qualified', 'proposal', 'negotiation', 'closed-won', 'closed-lost'],
21
+ autoScore: true,
22
+ scoreThreshold: 70,
23
+ fields: ['name', 'email', 'company', 'value', 'stage', 'source'],
24
+ },
25
+ },
26
+ 'communication-hub': {
27
+ id: 'communication-hub',
28
+ name: 'Communication Hub',
29
+ description: 'Agent DMs, channels, notifications, and team messaging',
30
+ version: '1.0.0',
31
+ hooks: ['onMessage', 'onAction', 'onSchedule'],
32
+ defaultConfig: {
33
+ channels: ['general', 'sales', 'marketing', 'ops', 'support'],
34
+ notifyOnMention: true,
35
+ digestFrequency: 'daily',
36
+ maxRecipients: 50,
37
+ },
38
+ },
39
+ 'kpi-dashboard': {
40
+ id: 'kpi-dashboard',
41
+ name: 'KPI Dashboard',
42
+ description: 'Business metrics, performance tracking, and goal management',
43
+ version: '1.0.0',
44
+ hooks: ['onMessage', 'onSchedule'],
45
+ defaultConfig: {
46
+ metrics: ['revenue', 'conversion_rate', 'customer_acquisition_cost', 'lifetime_value', 'churn_rate'],
47
+ refreshInterval: 'hourly',
48
+ alertThresholds: { revenue_drop_pct: 20, conversion_below: 0.02 },
49
+ targets: {},
50
+ },
51
+ },
52
+ 'compliance-guard': {
53
+ id: 'compliance-guard',
54
+ name: 'Compliance Guard',
55
+ description: 'Output verification, audit trail, and policy enforcement',
56
+ version: '1.0.0',
57
+ hooks: ['onMessage', 'onAction'],
58
+ defaultConfig: {
59
+ enabledRules: ['no_pii_leak', 'no_toxic_content', 'no_false_claims', 'mandatory_disclaimer'],
60
+ auditLevel: 'standard',
61
+ blockOnViolation: false,
62
+ logAllOutput: true,
63
+ },
64
+ },
65
+ };
66
+
67
+ function pluginsPath(tenantId) {
68
+ return join(STATE_DIR, `plugins-${tenantId}.json`);
69
+ }
70
+
71
+ export function listPlugins(tenantId) {
72
+ const registry = Object.values(PLUGIN_REGISTRY);
73
+ let installed = [];
74
+ const pp = pluginsPath(tenantId);
75
+ if (existsSync(pp)) {
76
+ try {
77
+ const data = JSON.parse(readFileSync(pp, 'utf-8'));
78
+ installed = data.installed || [];
79
+ } catch {}
80
+ }
81
+ const installedIds = new Set(installed.map(p => p.id));
82
+ return registry.map(r => ({
83
+ ...r,
84
+ installed: installedIds.has(r.id),
85
+ config: installed.find(p => p.id === r.id)?.config || null,
86
+ installedAt: installed.find(p => p.id === r.id)?.installedAt || null,
87
+ }));
88
+ }
89
+
90
+ export function installPlugin(tenantId, pluginId, config = {}) {
91
+ const manifest = PLUGIN_REGISTRY[pluginId];
92
+ if (!manifest) return { ok: false, error: `Unknown plugin: ${pluginId}. Available: ${Object.keys(PLUGIN_REGISTRY).join(', ')}` };
93
+ const pp = pluginsPath(tenantId);
94
+ let plugins = existsSync(pp) ? JSON.parse(readFileSync(pp, 'utf-8')) : { installed: [] };
95
+ if (plugins.installed.find(p => p.id === pluginId)) {
96
+ return { ok: true, message: 'already installed', plugin: plugins.installed.find(p => p.id === pluginId) };
97
+ }
98
+ const entry = {
99
+ id: pluginId,
100
+ name: manifest.name,
101
+ config: { ...manifest.defaultConfig, ...config },
102
+ installedAt: new Date().toISOString(),
103
+ status: 'active',
104
+ hooks: manifest.hooks,
105
+ };
106
+ plugins.installed.push(entry);
107
+ ensureDir(dirname(pp));
108
+ writeFileSync(pp, JSON.stringify(plugins, null, 2));
109
+ return { ok: true, plugin: entry };
110
+ }
111
+
112
+ export function uninstallPlugin(tenantId, pluginId) {
113
+ const pp = pluginsPath(tenantId);
114
+ if (!existsSync(pp)) return { ok: false, error: 'No plugins installed' };
115
+ let plugins = JSON.parse(readFileSync(pp, 'utf-8'));
116
+ const idx = plugins.installed.findIndex(p => p.id === pluginId);
117
+ if (idx < 0) return { ok: false, error: `Plugin ${pluginId} not installed` };
118
+ const removed = plugins.installed.splice(idx, 1)[0];
119
+ writeFileSync(pp, JSON.stringify(plugins, null, 2));
120
+ return { ok: true, removed };
121
+ }
122
+
123
+ export function configurePlugin(tenantId, pluginId, config) {
124
+ const pp = pluginsPath(tenantId);
125
+ if (!existsSync(pp)) return { ok: false, error: 'No plugins installed' };
126
+ let plugins = JSON.parse(readFileSync(pp, 'utf-8'));
127
+ const plugin = plugins.installed.find(p => p.id === pluginId);
128
+ if (!plugin) return { ok: false, error: `Plugin ${pluginId} not installed` };
129
+ plugin.config = { ...plugin.config, ...config };
130
+ plugin.updatedAt = new Date().toISOString();
131
+ writeFileSync(pp, JSON.stringify(plugins, null, 2));
132
+ return { ok: true, plugin };
133
+ }
134
+
135
+ export function getInstalledPlugins(tenantId) {
136
+ const pp = pluginsPath(tenantId);
137
+ if (!existsSync(pp)) return [];
138
+ try {
139
+ const data = JSON.parse(readFileSync(pp, 'utf-8'));
140
+ return (data.installed || []).filter(p => p.status === 'active');
141
+ } catch {
142
+ return [];
143
+ }
144
+ }
145
+
146
+ export function dispatchHook(tenantId, hookName, context) {
147
+ const installed = getInstalledPlugins(tenantId);
148
+ const results = [];
149
+ for (const plugin of installed) {
150
+ const manifest = PLUGIN_REGISTRY[plugin.id];
151
+ if (!manifest || !manifest.hooks.includes(hookName)) continue;
152
+ try {
153
+ const handler = HOOK_HANDLERS[`${plugin.id}:${hookName}`];
154
+ if (typeof handler === 'function') {
155
+ const result = handler(context, plugin.config);
156
+ results.push({ pluginId: plugin.id, hookName, result: result || null, error: null });
157
+ }
158
+ } catch (err) {
159
+ results.push({ pluginId: plugin.id, hookName, result: null, error: err.message });
160
+ }
161
+ }
162
+ return results;
163
+ }
164
+
165
+ const HOOK_HANDLERS = {
166
+ 'crm-hub:onMessage': (ctx, config) => {
167
+ if (!ctx.message || !ctx.message.toLowerCase()) return null;
168
+ const fields = config.fields || [];
169
+ const detected = fields.filter(f => ctx.message.toLowerCase().includes(f.toLowerCase()));
170
+ if (detected.length === 0) return null;
171
+ return { type: 'crm-extract', fields: detected, suggestion: `CRM data detected: ${detected.join(', ')}. Consider creating/updating a contact record.` };
172
+ },
173
+ 'compliance-guard:onMessage': (ctx, config) => {
174
+ if (!ctx.message) return null;
175
+ const rules = config.enabledRules || [];
176
+ const violations = [];
177
+ if (rules.includes('no_pii_leak')) {
178
+ const piiPatterns = [/\b\d{3}-\d{2}-\d{4}\b/, /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/i, /\b\d{16}\b/];
179
+ for (const rx of piiPatterns) {
180
+ if (rx.test(ctx.message)) violations.push('potential_pii_detected');
181
+ }
182
+ }
183
+ if (violations.length > 0) {
184
+ return { type: 'compliance-violation', violations, block: config.blockOnViolation === true, message: `Compliance issues: ${violations.join(', ')}` };
185
+ }
186
+ return null;
187
+ },
188
+ 'kpi-dashboard:onMessage': (ctx, config) => {
189
+ if (!ctx.agentId) return null;
190
+ return { type: 'kpi-event', metric: 'agent_message', agentId: ctx.agentId, timestamp: new Date().toISOString() };
191
+ },
192
+ 'communication-hub:onMessage': (ctx, config) => {
193
+ if (!ctx.agentId || !ctx.tenantId) return null;
194
+ return { type: 'comm-log', agentId: ctx.agentId, tenantId: ctx.tenantId, timestamp: new Date().toISOString() };
195
+ },
196
+ };
@@ -1,5 +1,5 @@
1
1
  {
2
- "bundledAt": "2026-04-29T23:22:23.542Z",
2
+ "bundledAt": "2026-04-30T13:56:07.664Z",
3
3
  "sdkSource": "/home/hamzaibrahim1/rei-ai-brain/harness/packages/harness-http-client/dist",
4
4
  "files": 6
5
5
  }
@@ -260,7 +260,14 @@ export declare class HTTPHarnessClient {
260
260
  private cachedPacket;
261
261
  private packetLastFetched;
262
262
  private readonly packetTtlMs;
263
+ private preferredBaseUrl;
264
+ private readonly baseUrlCandidates;
263
265
  constructor(config: HarnessClientConfig);
266
+ private buildBaseUrlCandidates;
267
+ private isRouteEligibleForBaseFailover;
268
+ private candidateUrlsFor;
269
+ private shouldFailoverResponse;
270
+ private promoteSuccessfulBase;
264
271
  private extractHarnessText;
265
272
  private normalizePlanDocs;
266
273
  private collectPlanFilePaths;
@@ -3,6 +3,32 @@ import { existsSync, readFileSync, statSync } from 'node:fs';
3
3
  import { homedir as _homedir } from 'node:os';
4
4
  import { resolve, isAbsolute } from 'node:path';
5
5
  import { randomUUID } from 'node:crypto';
6
+ const CLOUD_RUN_SOUL_URL = 'https://arias-soul-6zp3gtk2ca-uc.a.run.app';
7
+ const PUBLIC_HARNESS_URL = 'https://harness.ariasos.com';
8
+ const LOCAL_HARNESS_CANDIDATES = [
9
+ 'http://127.0.0.1:30080',
10
+ 'http://192.168.4.25:30080',
11
+ ];
12
+ const FAILOVER_STATUS_CODES = new Set([404, 405, 501, 502, 503, 504]);
13
+ function isMountedRuntimeBaseUrl(baseUrl) {
14
+ return /:\/\/(?:127\.0\.0\.1|localhost):4319$/i.test(String(baseUrl || '').replace(/\/+$/, ''));
15
+ }
16
+ function normalizeHarnessPacketPayload(payload) {
17
+ let current = payload;
18
+ for (let depth = 0; depth < 3; depth++) {
19
+ if (!current || typeof current !== 'object' || Array.isArray(current))
20
+ break;
21
+ const nested = current.packet;
22
+ if (!nested || typeof nested !== 'object' || Array.isArray(nested))
23
+ break;
24
+ if (typeof current.timestamp !== 'string' && typeof current.version !== 'string')
25
+ break;
26
+ current = nested;
27
+ }
28
+ return (current && typeof current === 'object' && !Array.isArray(current))
29
+ ? current
30
+ : {};
31
+ }
6
32
  // ── 8-Lens Cognition Block ─────────────────────────────────────────────────
7
33
  const EIGHT_LENS_BLOCK = `[ARIA SHELL PROTOCOL — You are a controlled surface]
8
34
 
@@ -43,11 +69,17 @@ export class HTTPHarnessClient {
43
69
  cachedPacket = null;
44
70
  packetLastFetched = 0;
45
71
  packetTtlMs = 60_000;
72
+ preferredBaseUrl;
73
+ baseUrlCandidates;
46
74
  constructor(config) {
47
75
  this.baseUrl = config.baseUrl.replace(/\/+$/, '');
48
76
  this.apiKey = config.apiKey;
49
- this.harnessPacketUrl = config.harnessPacketUrl ?? `${this.baseUrl}/api/harness/codex`;
77
+ this.harnessPacketUrl = config.harnessPacketUrl ?? (isMountedRuntimeBaseUrl(this.baseUrl)
78
+ ? `${this.baseUrl}/packet`
79
+ : `${this.baseUrl}/api/harness/codex`);
50
80
  this.workspaceRoot = config.workspaceRoot ?? process.cwd();
81
+ this.preferredBaseUrl = this.baseUrl;
82
+ this.baseUrlCandidates = this.buildBaseUrlCandidates(config.baseUrl);
51
83
  // Layer 3 — sub-agent disk-cache bootstrap (#84).
52
84
  // If ARIA_HARNESS_PACKET_PATH is set (injected by aria-agent-handoff.mjs
53
85
  // or spawnSubAgent), OR the owner-tier handoff JSON at
@@ -57,6 +89,65 @@ export class HTTPHarnessClient {
57
89
  // TTL is 5 min (matches handoff TTL). Stale packets trigger normal fetch.
58
90
  this._tryLoadDiskPacket();
59
91
  }
92
+ buildBaseUrlCandidates(primaryBaseUrl) {
93
+ const envFallbacks = String(process.env.ARIA_HARNESS_FALLBACK_URLS || '')
94
+ .split(',')
95
+ .map((value) => value.trim())
96
+ .filter(Boolean);
97
+ const candidates = [
98
+ primaryBaseUrl,
99
+ process.env.ARIA_HIVE_RUNTIME_URL || '',
100
+ process.env.ARIA_HARNESS_BASE_URL || '',
101
+ process.env.ARIA_HARNESS_URL || '',
102
+ process.env.ARIA_SOUL_URL || '',
103
+ process.env.ARIAS_SOUL_URL || '',
104
+ process.env.ARIA_SOUL_BASE_URL || '',
105
+ ...envFallbacks,
106
+ ...LOCAL_HARNESS_CANDIDATES,
107
+ CLOUD_RUN_SOUL_URL,
108
+ PUBLIC_HARNESS_URL,
109
+ ]
110
+ .map((value) => String(value || '').trim().replace(/\/+$/, ''))
111
+ .filter(Boolean);
112
+ return Array.from(new Set(candidates));
113
+ }
114
+ isRouteEligibleForBaseFailover(url) {
115
+ return (url.pathname.startsWith('/api/harness/') ||
116
+ url.pathname === '/api/garden/fire' ||
117
+ url.pathname.startsWith('/api/cognition/'));
118
+ }
119
+ candidateUrlsFor(targetUrl) {
120
+ let parsed;
121
+ try {
122
+ parsed = new URL(targetUrl);
123
+ }
124
+ catch {
125
+ return [targetUrl];
126
+ }
127
+ if (!this.isRouteEligibleForBaseFailover(parsed)) {
128
+ return [targetUrl];
129
+ }
130
+ const pathAndQuery = `${parsed.pathname}${parsed.search}`;
131
+ const orderedBases = [
132
+ this.preferredBaseUrl,
133
+ ...this.baseUrlCandidates,
134
+ ]
135
+ .map((value) => value.replace(/\/+$/, ''))
136
+ .filter(Boolean);
137
+ return Array.from(new Set(orderedBases)).map((baseUrl) => `${baseUrl}${pathAndQuery}`);
138
+ }
139
+ shouldFailoverResponse(response, url) {
140
+ if (!this.isRouteEligibleForBaseFailover(url))
141
+ return false;
142
+ return FAILOVER_STATUS_CODES.has(response.status);
143
+ }
144
+ promoteSuccessfulBase(targetUrl) {
145
+ try {
146
+ const parsed = new URL(targetUrl);
147
+ this.preferredBaseUrl = parsed.origin;
148
+ }
149
+ catch { }
150
+ }
60
151
  extractHarnessText(packet) {
61
152
  const raw = packet?.harness;
62
153
  return typeof raw === 'string' ? raw.trim() : '';
@@ -230,7 +321,7 @@ export class HTTPHarnessClient {
230
321
  const raw = JSON.parse(readFileSync(packetPath, 'utf8'));
231
322
  // Packet on disk may be the raw API response (has .packet wrapper) or
232
323
  // a bare packet object. Normalise to HarnessPacket shape.
233
- const packetData = (raw.packet ?? raw);
324
+ const packetData = normalizeHarnessPacketPayload(raw.packet ?? raw);
234
325
  this.cachedPacket = {
235
326
  packet: packetData,
236
327
  timestamp: new Date().toISOString(),
@@ -297,7 +388,7 @@ export class HTTPHarnessClient {
297
388
  }
298
389
  const body = (await res.json());
299
390
  this.cachedPacket = {
300
- packet: (body.packet ?? body),
391
+ packet: normalizeHarnessPacketPayload(body.packet ?? body),
301
392
  timestamp: new Date().toISOString(),
302
393
  version: '1.0.0',
303
394
  };
@@ -312,7 +403,7 @@ export class HTTPHarnessClient {
312
403
  if (envPacket) {
313
404
  try {
314
405
  this.cachedPacket = {
315
- packet: JSON.parse(envPacket),
406
+ packet: normalizeHarnessPacketPayload(JSON.parse(envPacket)),
316
407
  timestamp: new Date().toISOString(),
317
408
  version: '1.0.0',
318
409
  };
@@ -768,7 +859,7 @@ export class HTTPHarnessClient {
768
859
  resolvedPacketPath = packetPath;
769
860
  // Also pre-load into this SDK instance's cache so subsequent
770
861
  // getHarnessPacket() calls are instant within this process.
771
- const packetPayload = (packetData.packet ?? packetData);
862
+ const packetPayload = normalizeHarnessPacketPayload(packetData.packet ?? packetData);
772
863
  this.cachedPacket = {
773
864
  packet: packetPayload,
774
865
  timestamp: new Date().toISOString(),
@@ -1428,19 +1519,34 @@ export class HTTPHarnessClient {
1428
1519
  // codes — caller decides what to do with non-2xx). No circuit breaker:
1429
1520
  // we always try. Backoff: 250ms, 500ms, 1000ms (3 attempts total).
1430
1521
  async fetchWithRetry(url, init, attempts = 3) {
1522
+ const candidateUrls = this.candidateUrlsFor(url);
1431
1523
  let lastErr;
1432
- for (let i = 0; i < attempts; i++) {
1433
- try {
1434
- return await fetch(url, init);
1435
- }
1436
- catch (err) {
1437
- lastErr = err;
1438
- if (i < attempts - 1) {
1439
- const delay = 250 * Math.pow(2, i);
1440
- await new Promise((r) => setTimeout(r, delay));
1524
+ let lastResponse = null;
1525
+ for (const candidateUrl of candidateUrls) {
1526
+ for (let i = 0; i < attempts; i++) {
1527
+ try {
1528
+ const response = await fetch(candidateUrl, init);
1529
+ lastResponse = response;
1530
+ const parsed = new URL(candidateUrl);
1531
+ if (this.shouldFailoverResponse(response, parsed) && candidateUrl !== candidateUrls[candidateUrls.length - 1]) {
1532
+ break;
1533
+ }
1534
+ if (response.ok) {
1535
+ this.promoteSuccessfulBase(candidateUrl);
1536
+ }
1537
+ return response;
1538
+ }
1539
+ catch (err) {
1540
+ lastErr = err;
1541
+ if (i < attempts - 1) {
1542
+ const delay = 250 * Math.pow(2, i);
1543
+ await new Promise((r) => setTimeout(r, delay));
1544
+ }
1441
1545
  }
1442
1546
  }
1443
1547
  }
1548
+ if (lastResponse)
1549
+ return lastResponse;
1444
1550
  throw lastErr instanceof Error
1445
1551
  ? lastErr
1446
1552
  : new Error(`fetch failed after ${attempts} attempts: ${String(lastErr)}`);