@cgh567/agent 2.4.3 → 2.4.4
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/agents/business/talisman-ceo.md +183 -0
- package/agents/business/talisman-comms.md +257 -0
- package/agents/business/talisman-cto.md +153 -0
- package/agents/business/talisman-finance.md +246 -0
- package/agents/business/talisman-marketing.md +240 -0
- package/agents/business/talisman-sales.md +242 -0
- package/agents/business/talisman-support.md +236 -0
- package/bin/helios-rpc.js +19 -0
- package/daemon/adapters/helios-rpc-adapter.js +5 -12
- package/daemon/context-enrichment.js +27 -0
- package/daemon/helios-api.js +290 -45
- package/daemon/helios-company-daemon.js +160 -50
- package/daemon/lib/blast-radius-analyzer.js +75 -0
- package/daemon/lib/domain-bootstrap-orchestrator.js +267 -0
- package/daemon/lib/forensic-log.js +113 -0
- package/daemon/lib/goal-research-pipeline.js +644 -0
- package/daemon/lib/harada/cascade-judge.js +84 -1
- package/daemon/lib/harada/cascade-research-dispatcher.js +282 -0
- package/daemon/lib/harada/pillar-dispatcher.js +23 -2
- package/daemon/lib/hbo-bridge.js +73 -5
- package/daemon/lib/headroom-middleware.js +129 -0
- package/daemon/lib/headroom-proxy-manager.js +309 -0
- package/daemon/lib/intelligence/department-page-generator.js +46 -1
- package/daemon/lib/interpretation-engine.js +92 -0
- package/daemon/lib/mental-model-cache.js +96 -0
- package/daemon/lib/project-factory.js +47 -0
- package/daemon/lib/session-log-reader.js +93 -0
- package/daemon/lib/standard-work-bootstrap.js +87 -1
- package/daemon/lib/task-completion-processor.js +12 -0
- package/daemon/package.json +2 -1
- package/daemon/routes/agents.js +51 -6
- package/daemon/routes/channels.js +116 -2
- package/daemon/routes/crm.js +85 -0
- package/daemon/routes/dashboard.js +62 -16
- package/daemon/routes/dept.js +10 -1
- package/daemon/routes/email-triage.js +19 -10
- package/daemon/routes/hbo.js +367 -13
- package/daemon/routes/hed.js +133 -0
- package/daemon/routes/inbox.js +397 -8
- package/daemon/routes/project.js +392 -9
- package/daemon/schema-definitions.js +10 -0
- package/daemon/schema-migrations-hbo.js +10 -0
- package/daemon/schema-migrations-proj.js +22 -0
- package/extensions/__tests__/codebase-index.test.ts +73 -0
- package/extensions/__tests__/extension-command-registration.test.ts +35 -0
- package/extensions/__tests__/git-push-guard.test.ts +68 -0
- package/extensions/context-compaction.ts +104 -76
- package/extensions/cortex/__tests__/cortex-core.test.ts +100 -0
- package/extensions/email/actions/draft-response.ts +21 -1
- package/extensions/email/auth/accounts.ts +5 -11
- package/extensions/email/auth/inbox-dog.ts +5 -2
- package/extensions/email/backfill.ts +20 -13
- package/extensions/email/providers/gmail.ts +164 -0
- package/extensions/email/providers/google-calendar.ts +34 -5
- package/extensions/helios-browser/__tests__/browser-routing.test.ts +57 -0
- package/extensions/helios-browser/backends/playwright.ts +3 -1
- package/extensions/helios-governance/__tests__/governance-gates.test.ts +40 -0
- package/extensions/helios-governance/__tests__/tournament-consumer.test.js +66 -0
- package/extensions/hema-dispatch-v3/headroom-compress.ts +103 -0
- package/extensions/hema-dispatch-v3/index.ts +33 -65
- package/extensions/interview/__tests__/server.test.ts +117 -0
- package/extensions/lib/helios-root.cjs +46 -0
- package/extensions/subagent-mesh/__tests__/handlers.test.ts +98 -0
- package/extensions/warm-tick/warm-tick-maintenance.ts +156 -0
- package/lib/__tests__/bulk-ingest.live.test.ts +66 -0
- package/lib/__tests__/crash-fixes.test.ts +49 -0
- package/lib/__tests__/maintenance-mission-wiring.test.ts +35 -0
- package/lib/broker/__tests__/jit-subscription.test.js +44 -1
- package/lib/broker/__tests__/lifecycle-channels.test.js +25 -1
- package/lib/compression/__tests__/ccr-store.test.js +138 -0
- package/lib/compression/__tests__/pipeline.test.js +280 -0
- package/lib/compression/__tests__/smart-crusher.test.js +242 -0
- package/lib/compression/dist/server.js +34 -1
- package/lib/compression/dist/start-server.js +77 -0
- package/lib/graph/learning/headroom-learn-bridge.js +175 -0
- package/lib/hbo-core-store.ts +71 -0
- package/lib/mission-loop/__tests__/research-handler.test.ts +143 -0
- package/lib/skill-sync.js +6 -1
- package/lib/startup-integrity.js +9 -2
- package/lib/triage-core/__tests__/classifier-fixture.test.ts +254 -0
- package/lib/triage-core/__tests__/classifier-post-norm.test.ts +1 -1
- package/lib/triage-core/__tests__/classifier.test.ts +45 -7
- package/lib/triage-core/__tests__/correction-detector.test.ts +36 -0
- package/lib/triage-core/__tests__/d6-dunbar-boost.test.ts +5 -5
- package/lib/triage-core/__tests__/orchestrator-pipeline.test.ts +107 -0
- package/lib/triage-core/__tests__/orchestrator.test.ts +113 -1
- package/lib/triage-core/__tests__/signals.test.ts +357 -0
- package/lib/triage-core/__tests__/sql-parity.test.ts +216 -0
- package/lib/triage-core/backfill-cost-estimator.ts +91 -0
- package/lib/triage-core/backfill-orchestrator.ts +119 -0
- package/lib/triage-core/classifier.ts +38 -6
- package/lib/triage-core/cos/cross-channel-escalation.ts +2 -3
- package/lib/triage-core/cos/response-debt.ts +2 -2
- package/lib/triage-core/graph/__tests__/batch-persistence.test.ts +283 -0
- package/lib/triage-core/graph/batch-persistence.ts +66 -2
- package/lib/triage-core/graph/betweenness-worker.js +75 -0
- package/lib/triage-core/graph/graph-rank-sql.ts +67 -0
- package/lib/triage-core/graph/persistence.ts +1 -1
- package/lib/triage-core/graph/schema-v2.ts +2 -0
- package/lib/triage-core/graph/schema.cypher +1 -0
- package/lib/triage-core/graph/triage-query.ts +1 -1
- package/lib/triage-core/learning.ts +15 -20
- package/lib/triage-core/mental-model/bedrock-config.ts +78 -0
- package/lib/triage-core/mental-model/cos-integration.ts +1 -1
- package/lib/triage-core/mental-model/entity-extractor.ts +51 -4
- package/lib/triage-core/mental-model/identity-resolver.ts +5 -5
- package/lib/triage-core/mental-model/model-assembler-sql.ts +200 -0
- package/lib/triage-core/mental-model/model-assembler.ts +16 -3
- package/lib/triage-core/orchestrator.ts +4 -4
- package/lib/triage-core/scheduled-sends.ts +39 -2
- package/lib/triage-core/signals/comms-style.ts +1 -1
- package/lib/triage-core/signals/cross-channel-escalation.ts +2 -2
- package/lib/triage-core/signals/favee-type.ts +6 -1
- package/lib/triage-core/signals/goal-relevance.ts +31 -2
- package/lib/triage-core/signals/personal-importance.ts +1 -1
- package/lib/triage-core/signals/referral-chain.ts +0 -1
- package/lib/triage-core/signals/relationship-decay.ts +4 -0
- package/lib/triage-core/signals/relationship-health.ts +6 -1
- package/lib/triage-core/signals/trajectory-signal.ts +38 -3
- package/lib/triage-core/tournament-runner.js +11 -1
- package/lib/triage-core/triage-llm-factory.ts +110 -0
- package/lib/triage-core/triage-local-llm.ts +145 -0
- package/lib/triage-core/triage-sql-store.ts +337 -0
- package/lib/triage-core/types.ts +2 -2
- package/lib/unified-graph.atomic.test.ts +52 -0
- package/lib/unified-graph.failure-categories.test.ts +55 -0
- package/package.json +10 -3
- package/prebuilds/darwin-arm64/better_sqlite3.node +0 -0
- package/prebuilds/linux-x64/better_sqlite3.node +0 -0
- package/prebuilds/win32-x64/better_sqlite3.node +0 -0
- package/skills/helios-bookkeeping/SKILL.md +321 -0
- package/skills/helios-briefer/SKILL.md +44 -0
- package/skills/helios-client-relations/SKILL.md +322 -0
- package/skills/helios-personal-triager/SKILL.md +45 -0
- package/skills/helios-recruitment/SKILL.md +317 -0
- package/skills/helios-relationship-nudger/SKILL.md +77 -0
- package/skills/helios-researcher/SKILL.md +44 -0
- package/skills/helios-scheduler/SKILL.md +58 -0
- package/skills/helios-tax-analyst/SKILL.md +280 -0
- package/lib/triage-core/orchestrator.ts.bak-r005-r006-r008 +0 -1823
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* DomainBootstrapOrchestrator — drives DomainBootstrapJob through all stages.
|
|
4
|
+
*
|
|
5
|
+
* Stage machine:
|
|
6
|
+
* domain_search → domain_candidates_generated → domain_selected [HITL]
|
|
7
|
+
* → domain_registered [HITL] → resend_provisioned → dns_configured
|
|
8
|
+
* → dns_verified → forwarding_configured → email_channel_created → complete
|
|
9
|
+
*
|
|
10
|
+
* HITL gates at domain_selected (human picks domain) and domain_registered
|
|
11
|
+
* (human confirms purchase). Both create Approval nodes that surface in
|
|
12
|
+
* the Inbox → Approvals tab.
|
|
13
|
+
*
|
|
14
|
+
* Inbound: Porkbun email forwarding (company@domain → founder's personal inbox)
|
|
15
|
+
* Outbound: Resend (sends FROM company@domain)
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const STAGES = [
|
|
19
|
+
'domain_search',
|
|
20
|
+
'domain_candidates_generated',
|
|
21
|
+
'domain_selected', // HITL gate
|
|
22
|
+
'domain_registered', // HITL gate (money)
|
|
23
|
+
'resend_provisioned',
|
|
24
|
+
'dns_configured',
|
|
25
|
+
'dns_verified',
|
|
26
|
+
'forwarding_configured',
|
|
27
|
+
'email_channel_created',
|
|
28
|
+
'complete',
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
class DomainBootstrapOrchestrator {
|
|
32
|
+
constructor(mgQuery, companyId, log) {
|
|
33
|
+
this._mg = mgQuery;
|
|
34
|
+
this._companyId = companyId;
|
|
35
|
+
this._log = log || ((level, msg, meta) => console.log(`[domain-bootstrap] ${msg}`, meta || ''));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Called every N ticks. Finds pending jobs for this company and advances them.
|
|
40
|
+
*/
|
|
41
|
+
async checkPendingJobs() {
|
|
42
|
+
let EmailInfraStore, PorkbunApiClient, ResendApiClient, generateDomainCandidates, pollDnsRecord;
|
|
43
|
+
try {
|
|
44
|
+
EmailInfraStore = require('../lib/email-infrastructure-store').EmailInfraStore;
|
|
45
|
+
PorkbunApiClient = require('../lib/porkbun-api').PorkbunApiClient;
|
|
46
|
+
ResendApiClient = require('../lib/resend-api').ResendApiClient;
|
|
47
|
+
generateDomainCandidates = require('../lib/domain-candidate-generator').generateDomainCandidates;
|
|
48
|
+
pollDnsRecord = require('../lib/dns-propagation').pollDnsRecord;
|
|
49
|
+
} catch (e) {
|
|
50
|
+
this._log('warn', `domain-bootstrap: missing dependency — ${e.message}`);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const store = new EmailInfraStore(this._mg);
|
|
55
|
+
const jobs = await store.getBootstrapJobs(this._companyId, 'pending').catch(() => []);
|
|
56
|
+
|
|
57
|
+
for (const job of jobs) {
|
|
58
|
+
try {
|
|
59
|
+
await this._advanceJob(job, store, { PorkbunApiClient, ResendApiClient, generateDomainCandidates, pollDnsRecord });
|
|
60
|
+
} catch (err) {
|
|
61
|
+
this._log('error', `domain-bootstrap: job ${job.id} failed at stage ${job.currentStage}: ${err.message}`);
|
|
62
|
+
await store.updateBootstrapJob(job.id, { lastError: err.message, errorAt: new Date().toISOString() }).catch(() => {});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async _advanceJob(job, store, deps) {
|
|
68
|
+
const { PorkbunApiClient, ResendApiClient, generateDomainCandidates, pollDnsRecord } = deps;
|
|
69
|
+
|
|
70
|
+
switch (job.currentStage) {
|
|
71
|
+
case 'domain_search': {
|
|
72
|
+
// Generate domain candidates from company name
|
|
73
|
+
const companyRow = await this._mg(
|
|
74
|
+
`MATCH (c:Company {id: $cid}) RETURN c.name AS name`, { cid: this._companyId }
|
|
75
|
+
);
|
|
76
|
+
const companyName = companyRow?.rows?.[0]?.[0] || this._companyId;
|
|
77
|
+
const candidates = generateDomainCandidates(companyName, { tlds: ['.com', '.io', '.co'], maxCandidates: 15 });
|
|
78
|
+
|
|
79
|
+
// Check availability via Porkbun (if key available, else skip availability check)
|
|
80
|
+
let enrichedCandidates = candidates;
|
|
81
|
+
if (process.env.PORKBUN_API_KEY) {
|
|
82
|
+
const porkbun = new PorkbunApiClient();
|
|
83
|
+
enrichedCandidates = await Promise.allSettled(
|
|
84
|
+
candidates.map(async c => ({
|
|
85
|
+
domain: c,
|
|
86
|
+
available: await porkbun.checkDomain(c).then(r => r.status === 'AVAILABLE').catch(() => null),
|
|
87
|
+
price: await porkbun.getPricing([c.split('.').pop()]).then(r => r[0]?.registration || null).catch(() => null),
|
|
88
|
+
}))
|
|
89
|
+
).then(results => results.filter(r => r.status === 'fulfilled').map(r => r.value));
|
|
90
|
+
} else {
|
|
91
|
+
enrichedCandidates = candidates.map(c => ({ domain: c, available: null, price: null }));
|
|
92
|
+
this._log('warn', 'domain-bootstrap: PORKBUN_API_KEY not set — skipping availability check');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
await store.updateBootstrapJob(job.id, {
|
|
96
|
+
currentStage: 'domain_candidates_generated',
|
|
97
|
+
domainCandidates: JSON.stringify(enrichedCandidates),
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Create HITL Approval for domain selection
|
|
101
|
+
const approvalId = `approval:domain-select:${job.id}`;
|
|
102
|
+
await this._mg(
|
|
103
|
+
`MERGE (a:Approval {id: $aid})
|
|
104
|
+
ON CREATE SET
|
|
105
|
+
a.companyId = $cid, a.type = 'domain_selection', a.status = 'pending',
|
|
106
|
+
a.jobId = $jobId, a.candidates = $candidates,
|
|
107
|
+
a.message = 'Choose a domain for your company email',
|
|
108
|
+
a.createdAt = localdatetime()`,
|
|
109
|
+
{ aid: approvalId, cid: this._companyId, jobId: job.id, candidates: JSON.stringify(enrichedCandidates.slice(0, 8)) }
|
|
110
|
+
);
|
|
111
|
+
this._log('info', `domain-bootstrap: ${enrichedCandidates.length} candidates generated, HITL approval created`);
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
case 'domain_candidates_generated': {
|
|
116
|
+
// Wait for HITL approval (domain selection)
|
|
117
|
+
const approval = await this._mg(
|
|
118
|
+
`MATCH (a:Approval {type: 'domain_selection', jobId: $jobId}) RETURN a.status, a.selectedDomain LIMIT 1`,
|
|
119
|
+
{ jobId: job.id }
|
|
120
|
+
);
|
|
121
|
+
const status = approval?.rows?.[0]?.[0];
|
|
122
|
+
const selectedDomain = approval?.rows?.[0]?.[1];
|
|
123
|
+
if (status === 'approved' && selectedDomain) {
|
|
124
|
+
await store.updateBootstrapJob(job.id, { currentStage: 'domain_selected', selectedDomain });
|
|
125
|
+
|
|
126
|
+
// Create HITL Approval for purchase confirmation
|
|
127
|
+
const purchaseApprovalId = `approval:domain-purchase:${job.id}`;
|
|
128
|
+
await this._mg(
|
|
129
|
+
`MERGE (a:Approval {id: $aid})
|
|
130
|
+
ON CREATE SET
|
|
131
|
+
a.companyId = $cid, a.type = 'domain_purchase', a.status = 'pending',
|
|
132
|
+
a.jobId = $jobId, a.domain = $domain,
|
|
133
|
+
a.message = $msg, a.createdAt = localdatetime()`,
|
|
134
|
+
{ aid: purchaseApprovalId, cid: this._companyId, jobId: job.id, domain: selectedDomain,
|
|
135
|
+
msg: `Confirm purchase of ${selectedDomain} for your company email. This will charge your Porkbun account.` }
|
|
136
|
+
);
|
|
137
|
+
this._log('info', `domain-bootstrap: domain ${selectedDomain} selected, purchase confirmation requested`);
|
|
138
|
+
}
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
case 'domain_selected': {
|
|
143
|
+
// Wait for purchase HITL approval
|
|
144
|
+
const approval = await this._mg(
|
|
145
|
+
`MATCH (a:Approval {type: 'domain_purchase', jobId: $jobId}) RETURN a.status LIMIT 1`,
|
|
146
|
+
{ jobId: job.id }
|
|
147
|
+
);
|
|
148
|
+
const status = approval?.rows?.[0]?.[0];
|
|
149
|
+
if (status === 'approved') {
|
|
150
|
+
if (!process.env.PORKBUN_API_KEY) {
|
|
151
|
+
this._log('warn', 'domain-bootstrap: PORKBUN_API_KEY not set — cannot register domain');
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
const porkbun = new PorkbunApiClient();
|
|
155
|
+
await porkbun.registerDomain(job.selectedDomain, 1, false);
|
|
156
|
+
await store.updateBootstrapJob(job.id, { currentStage: 'domain_registered' });
|
|
157
|
+
this._log('info', `domain-bootstrap: domain ${job.selectedDomain} registered`);
|
|
158
|
+
}
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
case 'domain_registered': {
|
|
163
|
+
if (!process.env.RESEND_API_KEY) {
|
|
164
|
+
this._log('warn', 'domain-bootstrap: RESEND_API_KEY not set — cannot provision outbound email');
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
const resend = new ResendApiClient();
|
|
168
|
+
const resendDomain = await resend.addDomain(job.selectedDomain);
|
|
169
|
+
const dnsRecords = await resend.getDomainDnsRecords(resendDomain.id);
|
|
170
|
+
await store.updateBootstrapJob(job.id, {
|
|
171
|
+
currentStage: 'resend_provisioned',
|
|
172
|
+
resendDomainId: resendDomain.id,
|
|
173
|
+
resendDnsRecords: JSON.stringify(dnsRecords),
|
|
174
|
+
});
|
|
175
|
+
this._log('info', `domain-bootstrap: Resend provisioned, ${dnsRecords.length} DNS records needed`);
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
case 'resend_provisioned': {
|
|
180
|
+
if (!process.env.PORKBUN_API_KEY) break;
|
|
181
|
+
const porkbun = new PorkbunApiClient();
|
|
182
|
+
const dnsRecords = JSON.parse(job.resendDnsRecords || '[]');
|
|
183
|
+
for (const record of dnsRecords) {
|
|
184
|
+
await porkbun.createDnsRecord(job.selectedDomain, record).catch(e =>
|
|
185
|
+
this._log('warn', `domain-bootstrap: DNS record creation failed: ${e.message}`)
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
await store.updateBootstrapJob(job.id, { currentStage: 'dns_configured' });
|
|
189
|
+
this._log('info', `domain-bootstrap: DNS records configured, waiting for propagation`);
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
case 'dns_configured': {
|
|
194
|
+
// Poll for DNS propagation (non-blocking — will be re-checked next tick)
|
|
195
|
+
const verified = await pollDnsRecord(job.selectedDomain, 'MX', { maxAttempts: 3, intervalMs: 5000 }).catch(() => false);
|
|
196
|
+
if (verified) {
|
|
197
|
+
if (process.env.RESEND_API_KEY) {
|
|
198
|
+
const resend = new ResendApiClient();
|
|
199
|
+
await resend.verifyDomain(job.resendDomainId).catch(() => {});
|
|
200
|
+
}
|
|
201
|
+
await store.updateBootstrapJob(job.id, { currentStage: 'dns_verified' });
|
|
202
|
+
this._log('info', `domain-bootstrap: DNS verified for ${job.selectedDomain}`);
|
|
203
|
+
}
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
case 'dns_verified': {
|
|
208
|
+
// Set up email forwarding: company@domain → founder's personal inbox
|
|
209
|
+
// Get the company's EmailAccount to find the forwarding target
|
|
210
|
+
const accounts = await this._mg(
|
|
211
|
+
`MATCH (c:Company {id: $cid})-[:HAS_EMAIL_ACCOUNT]->(a:EmailAccount) RETURN a.email LIMIT 1`,
|
|
212
|
+
{ cid: this._companyId }
|
|
213
|
+
);
|
|
214
|
+
const forwardTo = accounts?.rows?.[0]?.[0];
|
|
215
|
+
if (forwardTo && process.env.PORKBUN_API_KEY) {
|
|
216
|
+
const porkbun = new PorkbunApiClient();
|
|
217
|
+
const infoAlias = `info@${job.selectedDomain}`;
|
|
218
|
+
await porkbun.createEmailForward(job.selectedDomain, 'info', forwardTo).catch(e =>
|
|
219
|
+
this._log('warn', `domain-bootstrap: email forward creation failed: ${e.message}`)
|
|
220
|
+
);
|
|
221
|
+
await store.updateBootstrapJob(job.id, { currentStage: 'forwarding_configured', companyEmail: infoAlias });
|
|
222
|
+
this._log('info', `domain-bootstrap: ${infoAlias} → ${forwardTo} forwarding configured`);
|
|
223
|
+
} else {
|
|
224
|
+
// No forwarding target — skip to channel creation
|
|
225
|
+
const companyEmail = `info@${job.selectedDomain}`;
|
|
226
|
+
await store.updateBootstrapJob(job.id, { currentStage: 'forwarding_configured', companyEmail });
|
|
227
|
+
}
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
case 'forwarding_configured': {
|
|
232
|
+
// Create EmailAccount node + EmailChannel
|
|
233
|
+
const accountSlug = job.companyEmail.replace('@', '-at-').replace(/\./g, '-');
|
|
234
|
+
const accountId = `acct:${this._companyId}:${accountSlug}`;
|
|
235
|
+
await this._mg(
|
|
236
|
+
`MERGE (a:EmailAccount {id: $aid})
|
|
237
|
+
ON CREATE SET
|
|
238
|
+
a.companyId = $cid, a.email = $email, a.provider = 'resend',
|
|
239
|
+
a.authMethod = 'resend-api', a.enabled = true,
|
|
240
|
+
a.domain = $domain, a.resendDomainId = $resendId,
|
|
241
|
+
a.createdAt = localdatetime()
|
|
242
|
+
WITH a
|
|
243
|
+
MATCH (c:Company {id: $cid})
|
|
244
|
+
MERGE (c)-[:HAS_EMAIL_ACCOUNT]->(a)`,
|
|
245
|
+
{ aid: accountId, cid: this._companyId, email: job.companyEmail,
|
|
246
|
+
domain: job.selectedDomain, resendId: job.resendDomainId || null }
|
|
247
|
+
);
|
|
248
|
+
await store.updateBootstrapJob(job.id, { currentStage: 'email_channel_created', emailAccountId: accountId });
|
|
249
|
+
// Broadcast SSE so wizard/frontend knows setup is complete
|
|
250
|
+
this._log('info', `domain-bootstrap: EmailAccount ${accountId} created`);
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
case 'email_channel_created': {
|
|
255
|
+
await store.updateBootstrapJob(job.id, { currentStage: 'complete', completedAt: new Date().toISOString() });
|
|
256
|
+
this._log('info', `domain-bootstrap: job ${job.id} complete — ${job.companyEmail} ready`);
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
default:
|
|
261
|
+
// Job is at complete or unknown stage — nothing to do
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
module.exports = { DomainBootstrapOrchestrator, STAGES };
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* forensic-log.js — Pino-based request/response logger for helios-api.js
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* const { forensicLog, createRequestLogger } = require('./lib/forensic-log');
|
|
6
|
+
*
|
|
7
|
+
* // In the route() function, at the top:
|
|
8
|
+
* const reqLog = createRequestLogger(req);
|
|
9
|
+
* reqLog.info('request received');
|
|
10
|
+
*
|
|
11
|
+
* // At the end:
|
|
12
|
+
* reqLog.info({ statusCode: 200, durationMs: Date.now() - startMs }, 'response sent');
|
|
13
|
+
*
|
|
14
|
+
* Environment variables:
|
|
15
|
+
* HELIOS_TRACE_REQUESTS=1 — log every HTTP request + response duration
|
|
16
|
+
* HELIOS_TRACE_SSE=1 — log every SSE event broadcast
|
|
17
|
+
* HELIOS_LOG_LEVEL — pino log level (default: 'info')
|
|
18
|
+
* LOG_LEVEL — fallback log level
|
|
19
|
+
*/
|
|
20
|
+
'use strict';
|
|
21
|
+
|
|
22
|
+
let pino;
|
|
23
|
+
try {
|
|
24
|
+
pino = require('pino');
|
|
25
|
+
} catch {
|
|
26
|
+
// Fallback if pino not installed — use the daemon's existing log format
|
|
27
|
+
pino = null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const LOG_LEVEL = process.env.HELIOS_LOG_LEVEL || process.env.LOG_LEVEL || 'info';
|
|
31
|
+
|
|
32
|
+
// Base forensic logger — writes to stderr so it doesn't pollute daemon stdout JSONL
|
|
33
|
+
const forensicLog = pino
|
|
34
|
+
? pino(
|
|
35
|
+
{
|
|
36
|
+
level: LOG_LEVEL,
|
|
37
|
+
name: 'helios-api',
|
|
38
|
+
timestamp: pino.stdTimeFunctions.isoTime,
|
|
39
|
+
// Redact auth tokens from logs
|
|
40
|
+
redact: {
|
|
41
|
+
paths: [
|
|
42
|
+
'req.headers.authorization',
|
|
43
|
+
'headers.authorization',
|
|
44
|
+
'token',
|
|
45
|
+
'apiToken',
|
|
46
|
+
],
|
|
47
|
+
censor: '[REDACTED]',
|
|
48
|
+
},
|
|
49
|
+
// Remove pid/hostname to keep logs lean
|
|
50
|
+
base: null,
|
|
51
|
+
},
|
|
52
|
+
// fd 2 = stderr — daemon stdout is reserved for JSONL agent events
|
|
53
|
+
pino.destination({ dest: 2, sync: false })
|
|
54
|
+
)
|
|
55
|
+
: {
|
|
56
|
+
// Minimal fallback when pino is unavailable
|
|
57
|
+
info: (obj, msg) =>
|
|
58
|
+
process.stderr.write(
|
|
59
|
+
JSON.stringify({ ts: new Date().toISOString(), level: 'info', ...(typeof obj === 'string' ? { msg: obj } : obj), ...(msg ? { msg } : {}) }) + '\n'
|
|
60
|
+
),
|
|
61
|
+
warn: (obj, msg) =>
|
|
62
|
+
process.stderr.write(
|
|
63
|
+
JSON.stringify({ ts: new Date().toISOString(), level: 'warn', ...(typeof obj === 'string' ? { msg: obj } : obj), ...(msg ? { msg } : {}) }) + '\n'
|
|
64
|
+
),
|
|
65
|
+
error: (obj, msg) =>
|
|
66
|
+
process.stderr.write(
|
|
67
|
+
JSON.stringify({ ts: new Date().toISOString(), level: 'error', ...(typeof obj === 'string' ? { msg: obj } : obj), ...(msg ? { msg } : {}) }) + '\n'
|
|
68
|
+
),
|
|
69
|
+
debug: () => {},
|
|
70
|
+
child(ctx) {
|
|
71
|
+
const parent = this;
|
|
72
|
+
return {
|
|
73
|
+
...parent,
|
|
74
|
+
info: (obj, msg) => parent.info({ ...ctx, ...(typeof obj === 'string' ? { msg: obj } : obj) }, msg),
|
|
75
|
+
warn: (obj, msg) => parent.warn({ ...ctx, ...(typeof obj === 'string' ? { msg: obj } : obj) }, msg),
|
|
76
|
+
error: (obj, msg) => parent.error({ ...ctx, ...(typeof obj === 'string' ? { msg: obj } : obj) }, msg),
|
|
77
|
+
debug: () => {},
|
|
78
|
+
};
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
let _reqCounter = 0;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Create a child logger bound to a specific HTTP request.
|
|
86
|
+
* Every log line from this child includes method, url, requestId.
|
|
87
|
+
*/
|
|
88
|
+
function createRequestLogger(req) {
|
|
89
|
+
const requestId =
|
|
90
|
+
req.headers['x-request-id'] || `req-${(++_reqCounter).toString().padStart(6, '0')}`;
|
|
91
|
+
return forensicLog.child({
|
|
92
|
+
requestId,
|
|
93
|
+
method: req.method,
|
|
94
|
+
url: req.url,
|
|
95
|
+
remoteAddr: req.socket && req.socket.remoteAddress,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Log an SSE connection lifecycle event.
|
|
101
|
+
*/
|
|
102
|
+
function logSseEvent(event, data) {
|
|
103
|
+
forensicLog.info({ sseEvent: event, ...data }, `[sse] ${event}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Log a task dispatch event (agent → daemon).
|
|
108
|
+
*/
|
|
109
|
+
function logTaskDispatch(event, data) {
|
|
110
|
+
forensicLog.info({ dispatchEvent: event, ...data }, `[dispatch] ${event}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = { forensicLog, createRequestLogger, logSseEvent, logTaskDispatch };
|