@alfe.ai/claude-code-host 0.1.0

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.
@@ -0,0 +1,2055 @@
1
+ import { ChangelogEntry, EncryptedEnvelopeV1, FieldEnvelope, FieldFormat, FieldSensitivity, GeneratedDataKey, IntegrationConfigResult, IntegrationInstall, RegistryEntry, ScopeInfo, SecretAggregate, SecretCategory, SecretMetadata, SecretScope } from "@alfe/types";
2
+ import { spawn } from "node:child_process";
3
+
4
+ //#region ../agent-api-client/dist/index.d.ts
5
+
6
+ //# sourceMappingURL=tool-error-capture.d.ts.map
7
+ //#endregion
8
+ //#region src/index.d.ts
9
+ interface AgentApiClientConfig {
10
+ apiKey: string;
11
+ apiUrl: string;
12
+ }
13
+ /**
14
+ * The broad-news providers behind the metered `services/news` Lambda. The
15
+ * server validates this with a zod enum; a value outside the union is an
16
+ * unpriceable product, so keep the literal union in lockstep with the service.
17
+ */
18
+ type NewsProvider = "apitube" | "newsdata";
19
+ /** One normalized article. `sentiment` is provider-shaped (APITube supplies it). */
20
+ interface NewsArticle {
21
+ title: string;
22
+ url: string;
23
+ source: string;
24
+ publishedAt: string;
25
+ snippet: string;
26
+ sentiment?: unknown;
27
+ }
28
+ /** Provider-agnostic result — the server normalizes every adapter to this. */
29
+ interface NewsResult {
30
+ articles: NewsArticle[];
31
+ provider: string;
32
+ }
33
+ interface RemoteSessionInfo {
34
+ sessionId: string;
35
+ agentId: string;
36
+ surface: "browser" | "terminal";
37
+ status: "agent_driving" | "awaiting_human" | "human_in_control" | "resuming" | "completed" | "expired" | "failed";
38
+ url?: string;
39
+ instructions?: string;
40
+ requestedAt?: string;
41
+ }
42
+ interface SyncAgentInfo {
43
+ agentId: string;
44
+ tenantId: string;
45
+ displayName: string;
46
+ s3Prefix: string;
47
+ status: "stale" | "syncing" | "synced";
48
+ fileCount?: number;
49
+ totalSize?: number;
50
+ lastSync?: string;
51
+ }
52
+ interface SyncManifestEntry {
53
+ hash: string;
54
+ size: number;
55
+ modified: string;
56
+ etag?: string;
57
+ storageClass?: string;
58
+ compressed?: boolean;
59
+ }
60
+ interface SyncManifest {
61
+ version: 1;
62
+ agentId: string;
63
+ lastSync: string;
64
+ files: Record<string, SyncManifestEntry>;
65
+ }
66
+ interface SyncPresignedUrl {
67
+ path: string;
68
+ url: string;
69
+ expiresAt: string;
70
+ }
71
+ interface SyncConfirmedUpload {
72
+ filePath: string;
73
+ hash: string;
74
+ size: number;
75
+ storageClass: "STANDARD" | "GLACIER_IR";
76
+ syncedAt: string;
77
+ }
78
+ interface SyncReconstructFile {
79
+ path: string;
80
+ size: number;
81
+ url: string;
82
+ storageClass?: string;
83
+ compressed?: boolean;
84
+ }
85
+ interface SyncReconstructBundle {
86
+ agentId: string;
87
+ mode: "full" | "active" | "memory";
88
+ fileCount: number;
89
+ totalSize: number;
90
+ files: SyncReconstructFile[];
91
+ expiresAt: string;
92
+ }
93
+ interface SyncAgentStats {
94
+ agentId: string;
95
+ standardBytes: number;
96
+ glacierBytes: number;
97
+ fileCount: number;
98
+ lastSyncAt: string | null;
99
+ }
100
+ interface SyncFileEntry {
101
+ filePath: string;
102
+ size: number;
103
+ modified: string;
104
+ contentHash: string;
105
+ storageClass?: string;
106
+ compressed?: boolean;
107
+ }
108
+ interface SyncSessionEntry {
109
+ sessionId: string;
110
+ size: number;
111
+ lastModified: string;
112
+ storageClass?: string;
113
+ isArchived: boolean;
114
+ }
115
+ interface SyncSessionContent {
116
+ sessionId: string;
117
+ content: string;
118
+ compressed: boolean;
119
+ }
120
+ interface SharedFileEntry {
121
+ filePath: string;
122
+ fileName: string;
123
+ size: number;
124
+ contentType?: string;
125
+ }
126
+ type KnowledgeScopeType = "org" | "team" | "project";
127
+ interface KnowledgeScope {
128
+ scopeType: KnowledgeScopeType;
129
+ scopeId: string;
130
+ name: string;
131
+ }
132
+ interface KnowledgeSearchHit {
133
+ id: string;
134
+ text: string;
135
+ /** Normalized relevance in (0,1]; higher = closer. */
136
+ score: number;
137
+ scopeType: KnowledgeScopeType;
138
+ scopeId: string;
139
+ /**
140
+ * Provenance of the hit. All live results are `"doc"`; `"fact"` only ever
141
+ * appears for legacy vectors indexed before the facts primitive was removed
142
+ * (the search index stays tolerant of them). Treat every hit as a doc.
143
+ */
144
+ source: "doc" | "fact";
145
+ /** The canonical file under shared/<scope>/ (present on doc hits). */
146
+ filePath?: string;
147
+ /** Legacy-only: the id of a pre-removal fact vector. */
148
+ factId?: string;
149
+ }
150
+ interface KnowledgeSearchResult {
151
+ results: KnowledgeSearchHit[];
152
+ /** True when fan-out breadth was capped (more member scopes than the cap). */
153
+ truncatedScopes: boolean;
154
+ }
155
+ interface KnowledgeProfileLink {
156
+ label: string;
157
+ url: string;
158
+ }
159
+ interface KnowledgeProfile {
160
+ scopeType: KnowledgeScopeType;
161
+ scopeId: string;
162
+ about: string | null;
163
+ description: string | null;
164
+ links: KnowledgeProfileLink[];
165
+ updatedAt: string | null;
166
+ updatedBy: string | null;
167
+ }
168
+ interface KnowledgeDoc {
169
+ filePath: string;
170
+ fileName: string;
171
+ contentType?: string;
172
+ size: number;
173
+ uploadedBy?: string;
174
+ createdAt: string;
175
+ updatedAt: string;
176
+ }
177
+ type ChangeRequestResourceType = "doc" | "profile";
178
+ type ChangeRequestOperation = "create" | "update" | "delete";
179
+ type ChangeRequestStatus = "open" | "approved" | "rejected" | "withdrawn" | "superseded";
180
+ type ChangeRequestActorKind = "human" | "agent";
181
+ /** Public projection of a change request (mirrors `PublicChangeRequest` in services/org). */
182
+ interface KnowledgeChangeRequest {
183
+ changeRequestId: string;
184
+ scopeType: KnowledgeScopeType;
185
+ scopeId: string;
186
+ resourceType: ChangeRequestResourceType;
187
+ operation: ChangeRequestOperation;
188
+ targetPath: string | null;
189
+ baseVersionId: string | null;
190
+ proposedContentType: string | null;
191
+ status: ChangeRequestStatus;
192
+ proposerId: string;
193
+ proposerKind: ChangeRequestActorKind;
194
+ rationale: string;
195
+ reviewerId: string | null;
196
+ reviewerKind: ChangeRequestActorKind | null;
197
+ reviewedAt: string | null;
198
+ reviewNote: string | null;
199
+ appliedRef: string | null;
200
+ createdAt: string;
201
+ updatedAt: string;
202
+ }
203
+ /** Per-type proposal payload for `proposeScopeChange`. */
204
+ interface ProposeScopeChangeInput {
205
+ resourceType: ChangeRequestResourceType;
206
+ operation: ChangeRequestOperation;
207
+ /** Why the change is proposed — shown to the reviewer. */
208
+ rationale: string;
209
+ /** doc: the path the proposal applies to (e.g. designs/data-center.md). */
210
+ targetPath?: string;
211
+ /** doc create/update: the staged body to upload (markdown or other text). */
212
+ content?: string;
213
+ /** doc create/update: content type of the staged body (default text/markdown). */
214
+ contentType?: string;
215
+ /** profile: the proposed value ({ about, description, links }). */
216
+ proposedValue?: unknown;
217
+ }
218
+ /** Voice settings — core agent config. Mirrors `VoiceConfig` in `@alfe/types`. */
219
+ interface AgentVoiceConfig {
220
+ /** ElevenLabs voice ID; platform default when unset. */
221
+ voiceId?: string;
222
+ ttsModel?: string;
223
+ enabled?: boolean;
224
+ }
225
+ /**
226
+ * The agent's own public identity, as returned by `updateSelf`, `generateAvatar`,
227
+ * `presignAvatar`'s finalize (`finalizeAvatar`). This is the public agent
228
+ * projection; only the identity-relevant fields are typed here — the response
229
+ * carries the full public agent record.
230
+ */
231
+ interface AgentSelf {
232
+ agentId: string;
233
+ tenantId: string;
234
+ name: string;
235
+ avatarUrl?: string;
236
+ voiceConfig?: AgentVoiceConfig;
237
+ status: string;
238
+ }
239
+ /** Result of `presignAvatar` — the agent PUTs bytes to `uploadUrl`, then finalizes with `s3Key`. */
240
+ interface AgentAvatarPresign {
241
+ /** Presigned PUT URL to upload the image bytes to. */
242
+ uploadUrl: string;
243
+ /** Object key — echoed back to `finalizeAvatar`. */
244
+ s3Key: string;
245
+ /** Stable public URL the avatar will be served from once finalized. */
246
+ publicUrl: string;
247
+ /** ISO expiry of the presigned PUT URL. */
248
+ expiresAt: string;
249
+ }
250
+ /** A voice in the platform catalogue (ElevenLabs), from `listVoices`. */
251
+ interface AgentVoice {
252
+ id: string;
253
+ name: string;
254
+ previewUrl: string;
255
+ description: string;
256
+ labels: Record<string, string>;
257
+ category: string;
258
+ }
259
+ /** The ElevenLabs models with a pricing row — the TTS endpoint rejects any other value. */
260
+ type VoiceTtsModel = "eleven_turbo_v2_5" | "eleven_multilingual_v2";
261
+ interface VoiceTtsArgs {
262
+ /** Text to synthesize (1–5000 chars — the endpoint enforces this). */
263
+ text: string;
264
+ /** ElevenLabs voice id; platform default when unset. */
265
+ voiceId?: string;
266
+ /** TTS model; `eleven_turbo_v2_5` (lower latency) when unset. */
267
+ model?: VoiceTtsModel;
268
+ }
269
+ /** Raw synthesized audio plus its PCM framing (from the response headers). */
270
+ interface VoiceTtsResult {
271
+ /** Raw little-endian PCM samples — no container. Wrap in WAV to make a playable file. */
272
+ audio: Buffer;
273
+ /** Samples per second (e.g. 24000). */
274
+ sampleRate: number;
275
+ /** Channel count (mono = 1). */
276
+ channels: number;
277
+ /** Bits per sample (e.g. 16). */
278
+ bitDepth: number;
279
+ }
280
+ interface VoiceSttArgs {
281
+ /** Raw linear16 (16-bit little-endian) mono PCM samples — no WAV/container header. */
282
+ audio: Uint8Array;
283
+ /** Sample rate of `audio` in Hz (8000–48000). */
284
+ sampleRate: number;
285
+ }
286
+ interface VoiceSttResult {
287
+ text: string;
288
+ /** Deepgram confidence in (0,1]. */
289
+ confidence: number;
290
+ }
291
+ declare class AgentApiClient {
292
+ private readonly apiKey;
293
+ private readonly apiUrl;
294
+ constructor(config: AgentApiClientConfig);
295
+ private request;
296
+ syncRegister(args?: {
297
+ displayName?: string;
298
+ }): Promise<{
299
+ agent: SyncAgentInfo;
300
+ }>;
301
+ syncGetManifest(): Promise<SyncManifest>;
302
+ syncPresign(args: {
303
+ files: {
304
+ path: string;
305
+ operation: "put" | "get";
306
+ contentType?: string;
307
+ }[];
308
+ }): Promise<{
309
+ urls: SyncPresignedUrl[];
310
+ }>;
311
+ syncConfirmUpload(args: {
312
+ filePath: string;
313
+ hash: string;
314
+ size: number;
315
+ storageClass?: "STANDARD" | "GLACIER_IR";
316
+ }): Promise<SyncConfirmedUpload>;
317
+ syncReconstruct(args: {
318
+ mode: "full" | "active" | "memory";
319
+ }): Promise<SyncReconstructBundle>;
320
+ syncGetStats(): Promise<SyncAgentStats>;
321
+ syncListFiles(args?: {
322
+ prefix?: string;
323
+ }): Promise<{
324
+ files: SyncFileEntry[];
325
+ }>;
326
+ syncListSessions(): Promise<{
327
+ sessions: SyncSessionEntry[];
328
+ }>;
329
+ syncGetSession(sessionId: string): Promise<SyncSessionContent>;
330
+ syncDeleteFile(filePath: string): Promise<{
331
+ removed: boolean;
332
+ }>;
333
+ sharedListFiles(args: {
334
+ scope: "org" | "team" | "project";
335
+ scopeId: string;
336
+ }): Promise<{
337
+ files: SharedFileEntry[];
338
+ nextCursor: string | null;
339
+ }>;
340
+ sharedDownloadUrl(args: {
341
+ scope: "org" | "team" | "project";
342
+ scopeId: string;
343
+ filePath: string;
344
+ }): Promise<{
345
+ downloadUrl: string;
346
+ expiresIn: number;
347
+ }>;
348
+ listIntegrations(): Promise<IntegrationInstall[]>;
349
+ getIntegrationConfig(integrationId: string): Promise<IntegrationConfigResult>;
350
+ updateIntegrationConfig(integrationId: string, config: Record<string, unknown>): Promise<void>;
351
+ installIntegration(integrationId: string, options?: {
352
+ version?: string;
353
+ config?: Record<string, unknown>;
354
+ }): Promise<IntegrationInstall>;
355
+ removeIntegration(integrationId: string): Promise<IntegrationInstall>;
356
+ getOAuthUrl(provider: string, scopes?: string[]): Promise<{
357
+ url: string;
358
+ provider: string;
359
+ expiresIn: number;
360
+ }>;
361
+ getOAuthStatus(provider: string): Promise<{
362
+ provider: string;
363
+ connected: boolean;
364
+ config?: Record<string, string>;
365
+ }>;
366
+ getRegistry(): Promise<{
367
+ integrations: RegistryEntry[];
368
+ }>;
369
+ /**
370
+ * Returns every connected Google account for the agent. Multi-account by
371
+ * design — the openclaw-google plugin requires the LLM to pass `email`
372
+ * explicitly to `google_run_command` so an account is always selected
373
+ * deliberately.
374
+ *
375
+ * 2026-05-14 (connections-redesign PR 1): the legacy flat shape (`email`,
376
+ * `refreshToken`, `accessToken`, etc., populated from the default account)
377
+ * is gone. Iterate over `accounts`.
378
+ */
379
+ getGoogleCredentials(): Promise<{
380
+ accounts: {
381
+ email: string;
382
+ refreshToken: string;
383
+ clientId: string;
384
+ clientSecret: string;
385
+ displayName?: string;
386
+ connectedAt?: string;
387
+ }[];
388
+ }>;
389
+ disconnectGoogleAccount(email: string): Promise<{
390
+ accounts: {
391
+ email: string;
392
+ displayName?: string;
393
+ connectedAt?: string;
394
+ }[];
395
+ }>;
396
+ getGoogleChatCredentials(): Promise<{
397
+ email: string;
398
+ refreshToken: string;
399
+ clientId: string;
400
+ clientSecret: string;
401
+ displayName?: string;
402
+ }>;
403
+ /**
404
+ * Fetch decrypted credentials for ONE specific connection by its
405
+ * stable connectionId (connection-scoped, vs the provider-scoped
406
+ * `get<Provider>Credentials` helpers). Used by the daemon to resolve
407
+ * a Custom Connection-driven integration's credentials from the
408
+ * exact connection it was installed from — every custom connection
409
+ * shares the `custom` provider id, so provider-scoping is ambiguous.
410
+ *
411
+ * For custom connections `accessToken` is the JSON-encoded secret
412
+ * bundle (the daemon un-bundles it); non-secret fields are on
413
+ * `providerMetadata`. The endpoint enforces that the connection is in
414
+ * the calling agent's effective scope (403 otherwise).
415
+ */
416
+ getConnectionCredentials(connectionId: string): Promise<{
417
+ provider: string;
418
+ connectionId: string;
419
+ accountIdentifier?: string;
420
+ accessToken?: string;
421
+ providerMetadata?: Record<string, unknown>;
422
+ [key: string]: unknown;
423
+ }>;
424
+ /**
425
+ * Resolve the primary cTrader Connection's credentials for the calling
426
+ * agent. Unlike most providers, the cTrader Open API needs app-level auth
427
+ * (`clientId` + `clientSecret`) AND account auth (`accessToken` +
428
+ * `accountId`) on the socket, so `@alfe.ai/ctrader-mcp` self-fetches the
429
+ * full set here at startup (the atlassian/google pattern). `clientId` /
430
+ * `clientSecret` are the SST-sourced global app credentials the connect
431
+ * endpoint injects — they are never persisted on the connection. `host` is
432
+ * the resolved TLS endpoint (`live.ctraderapi.com` / `demo.ctraderapi.com`)
433
+ * derived from the selected account's live/demo flag.
434
+ */
435
+ getCTraderCredentials(): Promise<{
436
+ accessToken: string;
437
+ refreshToken: string;
438
+ accountId: string;
439
+ host: string;
440
+ clientId: string;
441
+ clientSecret: string;
442
+ }>;
443
+ /**
444
+ * Pattern A: multi-account credential fetch for cTrader.
445
+ *
446
+ * Unlike atlassian/salesforce (one Connection row per account/site), a
447
+ * cTrader is MULTI-grant per agent: an agent may connect several distinct
448
+ * cTrader logins, each its own Connection row keyed on `accountIdentifier =
449
+ * ctid:<userId>` (Phase 1). This aggregates the *trading accounts* across
450
+ * ALL of those Connection rows — each row contributes its `availableAccounts`
451
+ * flattened, and every account carries ITS OWN grant's `accessToken` (the
452
+ * token that authenticates that account against the cTrader Open API). One
453
+ * OAuth grant still covers all accounts under that single login on one shared
454
+ * token; only the `ctidTraderAccountId` and the protobuf socket `host` (live
455
+ * vs demo) differ within a grant. Across grants the tokens differ, so the
456
+ * token is now PER-ACCOUNT rather than hoisted to the top level.
457
+ *
458
+ * `host` per account is derived from the account's `isLive` flag
459
+ * (`live.ctraderapi.com` / `demo.ctraderapi.com`) — the same mapping the
460
+ * connect provider applies server-side when an account is auto-selected.
461
+ *
462
+ * `clientId` / `clientSecret` are the SST-sourced GLOBAL app credentials the
463
+ * connect endpoint injects — identical across every Connection row (one
464
+ * cTrader app), never persisted on a connection. We take them from the first
465
+ * row that carries them.
466
+ *
467
+ * Accounts are deduped on `ctidTraderAccountId` first-wins: Spotware ids are
468
+ * globally unique across logins, so a duplicate can only appear if the same
469
+ * account somehow surfaced under two grants — first-wins keeps it
470
+ * deterministic.
471
+ *
472
+ * `accounts` may be empty (no cTrader Connection at all), in which case we
473
+ * return empty creds rather than throwing.
474
+ */
475
+ getCTraderAccounts(): Promise<{
476
+ accounts: {
477
+ ctidTraderAccountId: string;
478
+ host: string;
479
+ isLive: boolean;
480
+ brokerName?: string;
481
+ accountNumber?: string;
482
+ accessToken: string;
483
+ }[];
484
+ clientId: string;
485
+ clientSecret: string;
486
+ }>;
487
+ /**
488
+ * @deprecated Returns a single primary credential blob (legacy "pick-the-
489
+ * default-connection" shape). Use `getGithubAccounts()` for the multi-
490
+ * account shape required by Pattern A — explicit selector args on every
491
+ * tool. Retained because the `@alfe.ai/github-mcp` proxy is the
492
+ * only consumer that knows about Pattern A; legacy env-interpolation
493
+ * callers will keep hitting `/credentials` until they move to the proxy.
494
+ */
495
+ getGithubCredentials(): Promise<{
496
+ login: string;
497
+ accessToken: string;
498
+ }>;
499
+ /**
500
+ * Pattern A: multi-account credential fetch for GitHub.
501
+ *
502
+ * Returns every agent-scoped GitHub connection. The caller is expected
503
+ * to require a `login` selector on every credential-touching tool and
504
+ * look up the matching account at dispatch time.
505
+ *
506
+ * GitHub OAuth tokens have no expiry (`tokenLifecycle: "no_expiry"`),
507
+ * so there is intentionally no `refreshGithubAccountToken` method — if
508
+ * a token is revoked the user must re-run the OAuth flow.
509
+ *
510
+ * Returned `accounts[i].login` is the GitHub username — the stable
511
+ * cross-session identifier the LLM should pass.
512
+ */
513
+ getGithubAccounts(): Promise<{
514
+ accounts: {
515
+ connectionId: string;
516
+ accountIdentifier: string;
517
+ displayName: string | null;
518
+ connectedAt: string;
519
+ accessToken: string;
520
+ login: string;
521
+ scopes: string;
522
+ }[];
523
+ }>;
524
+ /**
525
+ * @deprecated Returns a single primary credential blob (legacy "pick-the-
526
+ * default-connection" shape). Use `getXeroAccounts()` for the multi-
527
+ * account shape required by Pattern A — explicit selector args on every
528
+ * tool. This method will be removed once all consumers migrate.
529
+ */
530
+ getXeroCredentials(): Promise<{
531
+ accessToken: string;
532
+ accessTokenExpiresAt: string;
533
+ xeroTenantId: string;
534
+ }>;
535
+ /**
536
+ * Pattern A: multi-account credential fetch for Xero. Returns every
537
+ * agent-scoped Xero connection. The caller is expected to require a
538
+ * selector arg (e.g. `xeroTenantId`) on every credential-touching tool
539
+ * and look up the matching account by that selector at dispatch time.
540
+ *
541
+ * Returned `accounts[i].accountIdentifier` is the Xero tenantId — the
542
+ * stable cross-session identifier the LLM should pass.
543
+ */
544
+ getXeroAccounts(): Promise<{
545
+ accounts: {
546
+ connectionId: string;
547
+ accountIdentifier: string;
548
+ displayName: string | null;
549
+ connectedAt: string;
550
+ accessToken: string;
551
+ accessTokenExpiresAt: string;
552
+ xeroTenantId: string;
553
+ }[];
554
+ }>;
555
+ refreshXeroToken(): Promise<{
556
+ accessToken: string;
557
+ expiresAt: string;
558
+ }>;
559
+ /**
560
+ * Pattern A: refresh a specific Xero connection by its `accountIdentifier`
561
+ * (the Xero `tenantId`). The legacy `refreshXeroToken()` only refreshes
562
+ * the *primary* connection, which is wrong for multi-tenant Xero where
563
+ * each tenant has its own non-interchangeable access token.
564
+ */
565
+ refreshXeroAccountToken(xeroTenantId: string): Promise<{
566
+ accessToken: string;
567
+ accessTokenExpiresAt: string;
568
+ expiresAt: string;
569
+ }>;
570
+ /**
571
+ * @deprecated Returns a single primary credential blob (legacy "pick-the-
572
+ * default-connection" shape). Use `getNotionAccounts()` for the multi-
573
+ * account shape required by Pattern A.
574
+ */
575
+ getNotionCredentials(): Promise<{
576
+ accessToken: string;
577
+ workspaceId: string;
578
+ workspaceName: string;
579
+ }>;
580
+ /**
581
+ * Pattern A: multi-account credential fetch for Notion. Returns every
582
+ * agent-scoped Notion connection. The caller is expected to require a
583
+ * selector arg (e.g. `workspaceId`) on every credential-touching tool.
584
+ *
585
+ * Returned `accounts[i].accountIdentifier` is the Notion workspaceId.
586
+ */
587
+ getNotionAccounts(): Promise<{
588
+ accounts: {
589
+ connectionId: string;
590
+ accountIdentifier: string;
591
+ displayName: string | null;
592
+ connectedAt: string;
593
+ accessToken: string;
594
+ workspaceId: string;
595
+ workspaceName: string;
596
+ }[];
597
+ }>;
598
+ /**
599
+ * @deprecated Returns a single primary Atlassian Connection's credentials
600
+ * (one OAuth user, one cloudId) — the legacy "pick-the-default-connection"
601
+ * shape. Atlassian is multi-site by nature (each OAuth user may have
602
+ * access to multiple Cloud sites), so Pattern A plugins MUST use
603
+ * `getAtlassianAccounts()` to discover the full set and dispatch via
604
+ * the `cloudId` selector arg.
605
+ */
606
+ getAtlassianCredentials(): Promise<{
607
+ accessToken: string;
608
+ refreshToken: string;
609
+ accessTokenExpiresAt: string;
610
+ cloudId: string;
611
+ siteName: string;
612
+ siteUrl: string;
613
+ email: string;
614
+ enabledProducts: string[];
615
+ clientId: string;
616
+ clientSecret: string;
617
+ }>;
618
+ refreshAtlassianToken(): Promise<{
619
+ accessToken: string;
620
+ expiresAt: string;
621
+ }>;
622
+ /**
623
+ * Pattern A: multi-account / multi-site credential fetch for Atlassian.
624
+ *
625
+ * Returns every agent-scoped Atlassian Connection. Each Connection is
626
+ * one OAuth user with a single access token and N accessible Cloud
627
+ * sites (`availableSites`). The caller is expected to:
628
+ *
629
+ * 1. Flatten (connection × cloudId) into one MCP child per site.
630
+ * 2. Require a `cloudId` selector on every credential-touching tool.
631
+ * 3. Use the access token bound to the Connection that owns the
632
+ * requested `cloudId` (Atlassian shares one access token across
633
+ * all sites accessible to the OAuth user).
634
+ *
635
+ * Per-account token refresh uses `refreshAtlassianAccountToken(email)`
636
+ * — refreshing one Connection rotates its single access token, which
637
+ * then applies to every cloudId for that Connection.
638
+ *
639
+ * Returned `accounts[i].accountIdentifier` is the OAuth user's email
640
+ * — the stable cross-session identifier for refresh purposes. The LLM
641
+ * never sees this directly: it picks a site via the `cloudId` arg
642
+ * instead.
643
+ */
644
+ getAtlassianAccounts(): Promise<{
645
+ accounts: {
646
+ connectionId: string;
647
+ accountIdentifier: string;
648
+ displayName: string | null;
649
+ connectedAt: string;
650
+ accessToken: string;
651
+ accessTokenExpiresAt: string;
652
+ clientId: string;
653
+ clientSecret: string;
654
+ cloudId: string;
655
+ siteName: string;
656
+ siteUrl: string;
657
+ availableSites: {
658
+ id: string;
659
+ url: string;
660
+ name: string;
661
+ scopes?: string[];
662
+ avatarUrl?: string;
663
+ }[];
664
+ }[];
665
+ }>;
666
+ /**
667
+ * Pattern A: refresh a specific Atlassian Connection by `accountIdentifier`
668
+ * (the OAuth user's email).
669
+ *
670
+ * Atlassian rotates refresh tokens (`rotatesRefreshToken: true`); the
671
+ * server-side per-account refresh endpoint handles rotation and
672
+ * persistence. Refreshing one Connection updates its single access
673
+ * token, which applies to every accessible Cloud site (cloudId) for
674
+ * that OAuth user.
675
+ *
676
+ * Returns the new access token + expiry. The proxy is responsible for
677
+ * fanning the new token out to every child server it spawned for
678
+ * cloudIds owned by this Connection.
679
+ */
680
+ refreshAtlassianAccountToken(accountIdentifier: string): Promise<{
681
+ accessToken: string;
682
+ accessTokenExpiresAt: string;
683
+ expiresAt: string;
684
+ }>;
685
+ /**
686
+ * @deprecated Returns a single primary credential blob (legacy "pick-the-
687
+ * default-connection" shape). Use `getMYOBAccounts()` for the multi-
688
+ * account shape required by Pattern A.
689
+ */
690
+ getMYOBCredentials(): Promise<{
691
+ accessToken: string;
692
+ accessTokenExpiresAt: string;
693
+ myobBusinessId: string;
694
+ clientId: string;
695
+ }>;
696
+ /**
697
+ * Pattern A: multi-account credential fetch for MYOB. Returns every
698
+ * agent-scoped MYOB connection. The caller is expected to require a
699
+ * selector arg (e.g. `myobBusinessId` / `accountIdentifier`) on every
700
+ * credential-touching tool.
701
+ *
702
+ * Returned `accounts[i].accountIdentifier` is the MYOB businessId.
703
+ */
704
+ getMYOBAccounts(): Promise<{
705
+ accounts: {
706
+ connectionId: string;
707
+ accountIdentifier: string;
708
+ displayName: string | null;
709
+ connectedAt: string;
710
+ accessToken: string;
711
+ accessTokenExpiresAt: string;
712
+ myobBusinessId: string;
713
+ clientId: string;
714
+ }[];
715
+ }>;
716
+ refreshMYOBToken(): Promise<{
717
+ accessToken: string;
718
+ expiresAt: string;
719
+ }>;
720
+ /**
721
+ * @deprecated Returns a single primary credential blob. Use
722
+ * `getSalesforceAccounts()` for the multi-account shape required by
723
+ * Pattern A.
724
+ */
725
+ getSalesforceCredentials(): Promise<{
726
+ accessToken: string;
727
+ accessTokenExpiresAt: string;
728
+ instanceUrl: string;
729
+ orgId: string;
730
+ }>;
731
+ /**
732
+ * Pattern A: multi-account credential fetch for Salesforce. Returns every
733
+ * agent-scoped Salesforce connection. One OAuth grant maps to one org, so
734
+ * `accounts[i].accountIdentifier` (and `orgId`) is the Salesforce org id —
735
+ * the selector every credential-touching tool requires.
736
+ */
737
+ getSalesforceAccounts(): Promise<{
738
+ accounts: {
739
+ connectionId: string;
740
+ accountIdentifier: string;
741
+ displayName: string | null;
742
+ connectedAt: string;
743
+ accessToken: string;
744
+ accessTokenExpiresAt: string;
745
+ instanceUrl: string;
746
+ orgId: string;
747
+ }[];
748
+ }>;
749
+ /**
750
+ * Refresh the access token for a specific Salesforce org. Salesforce
751
+ * tokens aren't interchangeable across orgs, so the connection is targeted
752
+ * by `accountIdentifier` (the org id) — mirrors `refreshXeroAccountToken`.
753
+ */
754
+ refreshSalesforceAccountToken(orgId: string): Promise<{
755
+ accessToken: string;
756
+ accessTokenExpiresAt: string;
757
+ expiresAt: string;
758
+ }>;
759
+ /**
760
+ * Disconnects one connected Microsoft 365 account for the agent, by its
761
+ * `accountIdentifier`. Hits the generic per-account disconnect route
762
+ * (`DELETE /agent/connect/microsoft/accounts/{accountIdentifier}`), which
763
+ * resolves across the agent's full effective scope chain and deletes the
764
+ * matching Connection row. Returns the remaining accounts.
765
+ *
766
+ * IMPORTANT: pass the `accountIdentifier` from `getMicrosoftAccounts()`, NOT
767
+ * a synthesised email. For Microsoft, `accountIdentifier` is the user's email
768
+ * only when the Graph profile fetch succeeded at connect time; it falls back
769
+ * to the Azure tenant id (`tid` claim) otherwise. The backend matches on
770
+ * `accountIdentifier` exactly, so passing an email would 404 on those
771
+ * fallback-identifier accounts. (This is why the param is not named `email`,
772
+ * unlike `disconnectGoogleAccount` where the identifier is always the email.)
773
+ */
774
+ disconnectMicrosoftAccount(accountIdentifier: string): Promise<{
775
+ accounts: {
776
+ accountIdentifier: string;
777
+ displayName?: string;
778
+ connectedAt?: string;
779
+ }[];
780
+ }>;
781
+ /**
782
+ * Pattern A: multi-account credential fetch for Microsoft 365.
783
+ *
784
+ * Returns every agent-scoped Microsoft connection. The caller is expected
785
+ * to require an `email` selector on every credential-touching tool and
786
+ * look up the matching account at dispatch time.
787
+ *
788
+ * Returned `accounts[i].accountIdentifier` is the user's primary email
789
+ * (or the tid claim as fallback) — the stable cross-session identifier
790
+ * the LLM should pass.
791
+ *
792
+ * Per-account token refresh is exposed via `refreshMicrosoftAccountToken`,
793
+ * NOT `refreshXeroAccountToken` — Microsoft refresh tokens are not
794
+ * interchangeable across (tenant, user) pairs.
795
+ */
796
+ getMicrosoftAccounts(): Promise<{
797
+ accounts: {
798
+ connectionId: string;
799
+ accountIdentifier: string;
800
+ displayName: string | null;
801
+ connectedAt: string;
802
+ accessToken: string;
803
+ accessTokenExpiresAt: string;
804
+ refreshToken: string;
805
+ clientId: string;
806
+ clientSecret: string;
807
+ email: string;
808
+ microsoftTenantId: string;
809
+ workspaceDomain: string;
810
+ }[];
811
+ }>;
812
+ /**
813
+ * Pattern A: refresh a specific Microsoft 365 connection by its
814
+ * `accountIdentifier`. For Microsoft, `accountIdentifier` is the user's
815
+ * email when the Graph profile fetch succeeded at connect time, and the
816
+ * Azure tenant id (`tid` claim) as fallback. Callers should pass the
817
+ * value returned by `getMicrosoftAccounts()` rather than synthesising
818
+ * an email locally.
819
+ *
820
+ * Microsoft refresh tokens are bound to a specific (tenant, user) pair —
821
+ * they are NOT interchangeable across accounts, so per-account refresh
822
+ * is mandatory. The generic /accounts/{accountIdentifier}/refresh
823
+ * endpoint walks the agent's full visible scope chain to find a matching
824
+ * connection (works for inherited team/project Microsoft connections).
825
+ */
826
+ refreshMicrosoftAccountToken(accountIdentifier: string): Promise<{
827
+ accessToken: string;
828
+ accessTokenExpiresAt: string;
829
+ expiresAt: string;
830
+ }>;
831
+ getTeamsCredentials(): Promise<{
832
+ agentId: string;
833
+ tenantId: string;
834
+ azureAppId: string;
835
+ azureBotId: string;
836
+ azureClientSecret: string;
837
+ botDisplayName?: string;
838
+ teamsTenantId?: string;
839
+ serviceUrl?: string;
840
+ }>;
841
+ sendTeamsMessage(data: {
842
+ conversationId: string;
843
+ text?: string;
844
+ adaptiveCard?: Record<string, unknown>;
845
+ }): Promise<{
846
+ ok: boolean;
847
+ activityId: string;
848
+ }>;
849
+ listTeamsChannels(): Promise<{
850
+ channels: {
851
+ id: string;
852
+ name: string;
853
+ description?: string;
854
+ }[];
855
+ }>;
856
+ presignAttachments(files: {
857
+ filename: string;
858
+ mimeType: string;
859
+ size: number;
860
+ }[]): Promise<{
861
+ attachments: {
862
+ id: string;
863
+ uploadUrl: string;
864
+ downloadUrl: string;
865
+ s3Key: string;
866
+ expiresAt: string;
867
+ }[];
868
+ }>;
869
+ /**
870
+ * Generate an image from a text prompt and get back a STABLE, public URL
871
+ * (served from the agent-assets CDN — it does not expire). Embed the returned
872
+ * `imageUrl` in a reply as markdown to show it to the user.
873
+ *
874
+ * ASYNC: `gpt-image-1` routinely runs 30–60s, which exceeds the API Gateway
875
+ * 30s ceiling, so this enqueues a job (`POST /agent/images/generate` →
876
+ * `jobId`) then polls (`GET /agent/images/{jobId}`) until it completes. The
877
+ * worker's real failure message (e.g. an unsupported `size`) surfaces via the
878
+ * job's `error` field.
879
+ */
880
+ generateImage(args: {
881
+ prompt: string;
882
+ model?: string;
883
+ size?: string;
884
+ quality?: string;
885
+ }): Promise<{
886
+ imageUrl: string;
887
+ model: string;
888
+ }>;
889
+ recordActivity(data: {
890
+ userId?: string;
891
+ channel: string;
892
+ role: "user" | "assistant";
893
+ }): Promise<{
894
+ recorded: boolean;
895
+ }>;
896
+ /** Update the agent's own name and/or voice config. Returns the updated agent. */
897
+ updateSelf(update: {
898
+ name?: string;
899
+ voiceConfig?: AgentVoiceConfig;
900
+ }): Promise<AgentSelf>;
901
+ /**
902
+ * Generate the agent's own avatar from a text prompt. The image is generated,
903
+ * stored, and set on the agent server-side; returns the updated agent.
904
+ *
905
+ * ASYNC (same reason as `generateImage`): avatar gen runs `gpt-image-1`
906
+ * (30–60s) which exceeds the API Gateway 30s ceiling, so this enqueues a job
907
+ * (`POST /agent/avatar/generate` → `jobId`) then polls (`GET /agent/avatar/{jobId}`)
908
+ * until the avatar is set. Signature unchanged — the plugin is unaffected.
909
+ */
910
+ generateAvatar(args: {
911
+ prompt: string;
912
+ }): Promise<AgentSelf>;
913
+ /**
914
+ * Get a presigned PUT URL to upload a new avatar image. Upload the bytes to
915
+ * `uploadUrl`, then call `finalizeAvatar(s3Key)` to set it on the agent.
916
+ */
917
+ presignAvatar(args: {
918
+ mimeType: string;
919
+ size: number;
920
+ }): Promise<AgentAvatarPresign>;
921
+ /**
922
+ * Finalize an avatar upload — validates ownership + size, then sets the
923
+ * agent's `avatarUrl` server-side. Returns the updated agent.
924
+ */
925
+ finalizeAvatar(s3Key: string): Promise<AgentSelf>;
926
+ /** List the platform voice catalogue (ElevenLabs) so the agent can pick its own voice. */
927
+ listVoices(): Promise<{
928
+ voices: AgentVoice[];
929
+ }>;
930
+ /**
931
+ * Mint a fresh AES-256 data key for a specific (secret, field) pair. The
932
+ * encryption context is rebuilt server-side from `auth.tenantId` + the body
933
+ * fields including `fieldKey`; the agent cannot forge context for a scope
934
+ * or field it doesn't own. Legacy single-envelope secrets are migrated to
935
+ * `field#value` rows by the data migration, so call with `fieldKey: "value"`
936
+ * to reach them.
937
+ */
938
+ generateSecretDataKey(args: {
939
+ scope: SecretScope;
940
+ scopeId: string;
941
+ secretId: string;
942
+ fieldKey: string;
943
+ }): Promise<GeneratedDataKey>;
944
+ /**
945
+ * Unwrap a wrapped data key so the agent can decrypt the envelope locally.
946
+ * `fieldKey` MUST match the value supplied when the data key was generated
947
+ * (it's bound into KMS encryption context); mismatch fails with
948
+ * `InvalidCiphertextException`.
949
+ */
950
+ decryptSecretDataKey(args: {
951
+ scope: SecretScope;
952
+ scopeId: string;
953
+ secretId: string;
954
+ fieldKey: string;
955
+ dataKeyCiphertext: string;
956
+ }): Promise<{
957
+ plaintextKey: string;
958
+ }>;
959
+ /**
960
+ * Create a new secret with one or more fields. Encrypted fields must arrive
961
+ * pre-sealed (the agent has already obtained per-field data keys via
962
+ * `generateSecretDataKey({ ..., fieldKey })` and AES-encrypted locally).
963
+ * Plaintext fields ship the value inline.
964
+ */
965
+ createSecret(args: {
966
+ scope: SecretScope;
967
+ scopeId: string;
968
+ secretId: string;
969
+ secretName: string;
970
+ category?: SecretCategory;
971
+ description?: string;
972
+ tags?: string[];
973
+ fields: {
974
+ key: string;
975
+ format?: FieldFormat;
976
+ sensitivity: FieldSensitivity;
977
+ value?: string;
978
+ envelope?: EncryptedEnvelopeV1;
979
+ }[];
980
+ reason?: string;
981
+ }): Promise<SecretAggregate>;
982
+ /** Fetch the secret aggregate plus per-field encrypted envelopes. */
983
+ getSecret(args: {
984
+ scope: SecretScope;
985
+ scopeId: string;
986
+ secretId: string;
987
+ }): Promise<{
988
+ aggregate: SecretAggregate;
989
+ envelopes: FieldEnvelope[];
990
+ }>;
991
+ /** Fetch one field. Plaintext: value inline. Encrypted: envelope. */
992
+ getSecretField(args: {
993
+ scope: SecretScope;
994
+ scopeId: string;
995
+ secretId: string;
996
+ fieldKey: string;
997
+ }): Promise<{
998
+ key: string;
999
+ sensitivity: FieldSensitivity;
1000
+ format?: FieldFormat;
1001
+ value?: string;
1002
+ envelope?: EncryptedEnvelopeV1;
1003
+ rotatedAt?: string;
1004
+ createdAt: string;
1005
+ updatedAt: string;
1006
+ }>;
1007
+ /** Add OR rotate one field. */
1008
+ setSecretField(args: {
1009
+ scope: SecretScope;
1010
+ scopeId: string;
1011
+ secretId: string;
1012
+ fieldKey: string;
1013
+ sensitivity: FieldSensitivity;
1014
+ format?: FieldFormat;
1015
+ value?: string;
1016
+ envelope?: EncryptedEnvelopeV1;
1017
+ reason?: string;
1018
+ }): Promise<{
1019
+ fieldKey: string;
1020
+ rotated: boolean;
1021
+ }>;
1022
+ /** Remove one field. */
1023
+ removeSecretField(args: {
1024
+ scope: SecretScope;
1025
+ scopeId: string;
1026
+ secretId: string;
1027
+ fieldKey: string;
1028
+ }): Promise<void>;
1029
+ /** Update secret-level metadata (name/description/tags/category). */
1030
+ updateSecretMetadata(args: {
1031
+ scope: SecretScope;
1032
+ scopeId: string;
1033
+ secretId: string;
1034
+ secretName?: string;
1035
+ description?: string;
1036
+ tags?: string[];
1037
+ category?: SecretCategory;
1038
+ reason?: string;
1039
+ }): Promise<SecretAggregate>;
1040
+ /** List metadata for secrets in a scope. Optional filters route through the byFacet GSI. */
1041
+ listSecrets(args: {
1042
+ scope: SecretScope;
1043
+ scopeId: string;
1044
+ category?: SecretCategory;
1045
+ tag?: string;
1046
+ fieldKey?: string;
1047
+ }): Promise<SecretMetadata[]>;
1048
+ /** Bounded changelog read — metadata-only audit entries. */
1049
+ getSecretHistory(args: {
1050
+ scope: SecretScope;
1051
+ scopeId: string;
1052
+ secretId: string;
1053
+ limit?: number;
1054
+ cursor?: string;
1055
+ }): Promise<{
1056
+ entries: ChangelogEntry[];
1057
+ nextCursor?: string;
1058
+ }>;
1059
+ /** Delete a secret (and all its field rows + tag rows + changelog rows). */
1060
+ deleteSecret(args: {
1061
+ scope: SecretScope;
1062
+ scopeId: string;
1063
+ secretId: string;
1064
+ }): Promise<void>;
1065
+ /** Enumerate scopes (org/team/project/agent) this agent can access. */
1066
+ listSecretScopes(): Promise<ScopeInfo[]>;
1067
+ /**
1068
+ * Returns the calling agent's own identity context — `{ agentId, tenantId }`
1069
+ * decoded server-side from the agent API token. Used by the
1070
+ * `@alfe.ai/openclaw-identity` plugin to bootstrap context when the
1071
+ * OpenClaw daemon doesn't plumb `ctx.agentId` through to plugin hooks.
1072
+ * Plugins should cache this for the daemon's lifetime (single-agent-per-
1073
+ * process invariant). One HTTP round-trip per process activate; not for
1074
+ * per-call use.
1075
+ */
1076
+ whoami(): Promise<{
1077
+ agentId: string;
1078
+ tenantId: string;
1079
+ }>;
1080
+ resolveIdentity(args: {
1081
+ provider: string;
1082
+ platformId: string;
1083
+ kind?: "user" | "agent" | "service" | "bot" | "workspace";
1084
+ displayName?: string;
1085
+ }): Promise<{
1086
+ identityId: string | null;
1087
+ status: string;
1088
+ created?: boolean;
1089
+ reason?: string;
1090
+ /**
1091
+ * Flattened auriclabs permission strings for the resolved identity
1092
+ * (scope-prefixed where applicable). Empty array on miss / org service
1093
+ * outage — the runtime gate fails closed in that case.
1094
+ */
1095
+ permissions: string[];
1096
+ }>;
1097
+ searchIdentities(args?: {
1098
+ q?: string;
1099
+ status?: string;
1100
+ limit?: number;
1101
+ }): Promise<{
1102
+ identities: unknown[];
1103
+ }>;
1104
+ getIdentityContext(identityId: string): Promise<{
1105
+ context: unknown;
1106
+ }>;
1107
+ mergeIdentities(survivorId: string, args: {
1108
+ mergedId: string;
1109
+ changedBy: {
1110
+ type: string;
1111
+ id: string;
1112
+ name?: string;
1113
+ };
1114
+ }): Promise<{
1115
+ ok: boolean;
1116
+ error?: string;
1117
+ }>;
1118
+ unmergeIdentity(identityId: string, args: {
1119
+ changedBy: {
1120
+ type: string;
1121
+ id: string;
1122
+ name?: string;
1123
+ };
1124
+ }): Promise<{
1125
+ ok: boolean;
1126
+ error?: string;
1127
+ }>;
1128
+ addIdentityNote(identityId: string, args: {
1129
+ content: string;
1130
+ category?: string;
1131
+ changedBy: {
1132
+ type: string;
1133
+ id: string;
1134
+ name?: string;
1135
+ };
1136
+ }): Promise<{
1137
+ noteId: string | null;
1138
+ }>;
1139
+ tagIdentity(identityId: string, args: {
1140
+ tag: string;
1141
+ action: "add" | "remove";
1142
+ changedBy: {
1143
+ type: string;
1144
+ id: string;
1145
+ name?: string;
1146
+ };
1147
+ }): Promise<{
1148
+ ok: boolean;
1149
+ }>;
1150
+ getIdentityChangelog(identityId: string, args?: {
1151
+ limit?: number;
1152
+ }): Promise<{
1153
+ entries: unknown[];
1154
+ }>;
1155
+ rollbackIdentity(identityId: string, args: {
1156
+ targetVersion: number;
1157
+ changedBy: {
1158
+ type: string;
1159
+ id: string;
1160
+ name?: string;
1161
+ };
1162
+ }): Promise<{
1163
+ ok: boolean;
1164
+ entry?: unknown;
1165
+ }>;
1166
+ requestIdentityVerification(args: {
1167
+ claimedIdentityId: string;
1168
+ requestingIdentityId: string;
1169
+ requestingProvider: string;
1170
+ requestingPlatformId: string;
1171
+ preferredChannel?: "mobile" | "email";
1172
+ /**
1173
+ * Phase 2: agent-supplied contact endpoint. When provided, the top-level
1174
+ * `preferredChannel` is ignored — the contact's channel wins.
1175
+ */
1176
+ contact?: {
1177
+ channel: "email" | "mobile";
1178
+ value: string;
1179
+ };
1180
+ }): Promise<{
1181
+ verificationId: string;
1182
+ channel: string;
1183
+ deliveredTo: string;
1184
+ expiresAt: string;
1185
+ availableChannels: {
1186
+ channel: string;
1187
+ deliveredTo: string;
1188
+ }[];
1189
+ } | {
1190
+ error: string;
1191
+ }>;
1192
+ confirmIdentityVerification(args: {
1193
+ claimedIdentityId: string;
1194
+ verificationId: string;
1195
+ phrase: string;
1196
+ }): Promise<{
1197
+ verified: boolean;
1198
+ identityId?: string;
1199
+ /** Phase 2: how the confirm resolved — Scenario A vs B. */
1200
+ action?: "merged" | "contact_verified";
1201
+ error?: string;
1202
+ }>;
1203
+ /**
1204
+ * Update display-shape fields on an Identity. Body excludes `email` /
1205
+ * `phone` / `title` / `company` / `metadata` per Section D4 — contacts go
1206
+ * via the verify flow, title/company live on OrgMembership, metadata is
1207
+ * not agent-writable.
1208
+ */
1209
+ updateIdentity(identityId: string, args: {
1210
+ name?: string;
1211
+ avatarUrl?: string;
1212
+ timezone?: string;
1213
+ locale?: string;
1214
+ }): Promise<{
1215
+ ok: boolean;
1216
+ }>;
1217
+ /**
1218
+ * Phase 2 (Section H): server-side verification of a Google Chat sender via
1219
+ * the agent's existing Google OAuth credentials. Returns the resolved
1220
+ * identity (created or matched via Scenario-B email enrichment).
1221
+ */
1222
+ resolveGoogleChatSender(args: {
1223
+ senderUserId: string;
1224
+ spaceId?: string;
1225
+ }): Promise<{
1226
+ identityId: string | null;
1227
+ status: string;
1228
+ }>;
1229
+ memorySearch(query: string, opts?: {
1230
+ limit?: number;
1231
+ topic?: string;
1232
+ subtopic?: string;
1233
+ tag?: string;
1234
+ includeKnowledge?: boolean;
1235
+ }): Promise<{
1236
+ facts: {
1237
+ subject: string;
1238
+ predicate: string;
1239
+ object: string;
1240
+ since: string;
1241
+ confidence: number;
1242
+ }[];
1243
+ memories: {
1244
+ id: string;
1245
+ text: string;
1246
+ topic: string;
1247
+ subtopic: string;
1248
+ tag: string;
1249
+ importance: number;
1250
+ timestamp: number;
1251
+ score: number;
1252
+ }[];
1253
+ }>;
1254
+ memoryStore(text: string, opts?: {
1255
+ topic?: string;
1256
+ subtopic?: string;
1257
+ tag?: string;
1258
+ importance?: number;
1259
+ }): Promise<{
1260
+ memoryId: string;
1261
+ }>;
1262
+ memoryIngest(sessionKey: string, messages: {
1263
+ role: string;
1264
+ content: string;
1265
+ index: number;
1266
+ timestamp?: string;
1267
+ }[], metadata?: {
1268
+ channelId?: string;
1269
+ userId?: string;
1270
+ userName?: string;
1271
+ }): Promise<{
1272
+ queued: boolean;
1273
+ messageCount: number;
1274
+ }>;
1275
+ memoryLoadContext(tier?: number, topicHint?: string): Promise<{
1276
+ formatted: string;
1277
+ [key: string]: unknown;
1278
+ }>;
1279
+ memoryLookupEntity(subject: string): Promise<{
1280
+ subject: string;
1281
+ triples: {
1282
+ tripleId: string;
1283
+ predicate: string;
1284
+ object: string;
1285
+ validFrom: string;
1286
+ validTo?: string;
1287
+ confidence: number;
1288
+ }[];
1289
+ }>;
1290
+ memoryNavigate(): Promise<{
1291
+ topics: {
1292
+ name: string;
1293
+ tripleCount: number;
1294
+ subtopics: string[];
1295
+ }[];
1296
+ cursor: string | null;
1297
+ }>;
1298
+ memoryDelete(memoryId: string): Promise<{
1299
+ deleted: boolean;
1300
+ }>;
1301
+ memoryStats(): Promise<{
1302
+ vectorCount: number;
1303
+ tripleCount: number;
1304
+ storageEstimateBytes: number;
1305
+ lastIngestionAt?: string;
1306
+ }>;
1307
+ memoryLearn(args: {
1308
+ text: string;
1309
+ source?: string;
1310
+ sourceType?: "file" | "url" | "inline";
1311
+ metadata?: {
1312
+ sessionId?: string;
1313
+ channelId?: string;
1314
+ userName?: string;
1315
+ };
1316
+ }): Promise<{
1317
+ memoriesStored: number;
1318
+ triplesStored: number;
1319
+ chunks: number;
1320
+ source?: string;
1321
+ }>;
1322
+ memoryBootstrapStatus(): Promise<{
1323
+ synced: boolean;
1324
+ syncedAt?: string;
1325
+ sessionsBackfillSynced?: boolean;
1326
+ sessionsBackfillSyncedAt?: string;
1327
+ }>;
1328
+ memoryBootstrapStatusMark(scope?: "files" | "sessions"): Promise<{
1329
+ synced: true;
1330
+ syncedAt: string;
1331
+ }>;
1332
+ sendSms(args: {
1333
+ to: string;
1334
+ body: string;
1335
+ }): Promise<{
1336
+ sent: boolean;
1337
+ sid: string;
1338
+ }>;
1339
+ searchWeb(params: {
1340
+ query: string;
1341
+ count?: number;
1342
+ offset?: number;
1343
+ country?: string;
1344
+ freshness?: string;
1345
+ }): Promise<unknown>;
1346
+ searchImages(params: {
1347
+ query: string;
1348
+ count?: number;
1349
+ }): Promise<unknown>;
1350
+ searchNews(params: {
1351
+ query: string;
1352
+ count?: number;
1353
+ freshness?: string;
1354
+ }): Promise<unknown>;
1355
+ /** Search news across the selected provider's corpus. → POST /agent/news/search */
1356
+ newsSearch(params: {
1357
+ query: string;
1358
+ provider?: NewsProvider;
1359
+ source?: string;
1360
+ from?: string;
1361
+ to?: string;
1362
+ language?: string;
1363
+ category?: string;
1364
+ limit?: number;
1365
+ }): Promise<NewsResult>;
1366
+ /** Top headlines for the selected provider. → POST /agent/news/headlines */
1367
+ newsHeadlines(params?: {
1368
+ provider?: NewsProvider;
1369
+ category?: string;
1370
+ source?: string;
1371
+ language?: string;
1372
+ limit?: number;
1373
+ }): Promise<NewsResult>;
1374
+ /**
1375
+ * Semantic search across the agent's member scopes. Fan-out is gated
1376
+ * server-side by `listScopes` set-inclusion (fail-closed). Pass
1377
+ * `scopeType` + `scopeId` to narrow to one scope; a non-member scope
1378
+ * yields empty results (never a cross-scope leak).
1379
+ */
1380
+ knowledgeSearch(query: string, opts?: {
1381
+ limit?: number;
1382
+ scopeType?: KnowledgeScopeType;
1383
+ scopeId?: string;
1384
+ }): Promise<KnowledgeSearchResult>;
1385
+ /** Enumerate the scopes (org + teams + projects) this agent belongs to. */
1386
+ listScopes(): Promise<{
1387
+ scopes: KnowledgeScope[];
1388
+ }>;
1389
+ /** Read a scope's structured knowledge profile (after membership check). */
1390
+ getScopeProfile(scopeType: KnowledgeScopeType, scopeId: string): Promise<KnowledgeProfile>;
1391
+ /** List a scope's docs (the org-files corpus; mirrored to shared/<scope>/). */
1392
+ listScopeDocs(scopeType: KnowledgeScopeType, scopeId: string, opts?: {
1393
+ limit?: number;
1394
+ cursor?: string;
1395
+ }): Promise<{
1396
+ files: KnowledgeDoc[];
1397
+ nextCursor: string | null;
1398
+ }>;
1399
+ /**
1400
+ * Read the full text of a scope doc. Resolves a presigned download URL
1401
+ * from `services/org`, then fetches the bytes directly from S3 (the one
1402
+ * legitimate raw fetch in a plugin — same pattern as sync).
1403
+ */
1404
+ readScopeDoc(scopeType: KnowledgeScopeType, scopeId: string, filePath: string): Promise<{
1405
+ filePath: string;
1406
+ text: string;
1407
+ }>;
1408
+ /**
1409
+ * Write (create or overwrite) a scope doc. Two-step presigned upload:
1410
+ * `services/org` returns a signed URL plus `requiredHeaders` (author /
1411
+ * authorKind / message as `x-amz-meta-*`) that MUST be sent verbatim on
1412
+ * the PUT, alongside the same `Content-Type` that was signed. Author and
1413
+ * authorKind are server-set from the agent token — never trusted here.
1414
+ */
1415
+ writeScopeDoc(scopeType: KnowledgeScopeType, scopeId: string, filePath: string, content: string, opts?: {
1416
+ contentType?: string;
1417
+ message?: string;
1418
+ }): Promise<{
1419
+ filePath: string;
1420
+ }>;
1421
+ /**
1422
+ * Open a change request against a scope's knowledge resource. For a doc
1423
+ * create/update, `services/org` returns a presigned staging PUT; this method
1424
+ * uploads the proposed `content` to it (echoing the same Content-Type that
1425
+ * was signed), mirroring `writeScopeDoc`. The staged body is applied to the
1426
+ * canonical doc — attributed to this agent — only when a reviewer approves.
1427
+ */
1428
+ proposeScopeChange(scopeType: KnowledgeScopeType, scopeId: string, input: ProposeScopeChangeInput): Promise<KnowledgeChangeRequest>;
1429
+ /**
1430
+ * List the agent's OWN change requests in a scope (filtered server-side to
1431
+ * this agent as proposer). Pass `status` to narrow to open / approved / etc.
1432
+ */
1433
+ listScopeChangeRequests(scopeType: KnowledgeScopeType, scopeId: string, opts?: {
1434
+ status?: ChangeRequestStatus;
1435
+ limit?: number;
1436
+ cursor?: string;
1437
+ }): Promise<{
1438
+ changeRequests: KnowledgeChangeRequest[];
1439
+ nextCursor: string | null;
1440
+ }>;
1441
+ registerDatabaseCredentials(): Promise<{
1442
+ connectionString: string;
1443
+ username: string;
1444
+ password: string;
1445
+ databases: string[];
1446
+ }>;
1447
+ reportDatabaseAudit(entry: {
1448
+ database: string;
1449
+ collection: string;
1450
+ operation: string;
1451
+ summary?: string;
1452
+ }): Promise<void>;
1453
+ requestBrowserTakeover(args: {
1454
+ instructions: string;
1455
+ url?: string;
1456
+ conversationId?: string;
1457
+ }): Promise<{
1458
+ sessionId: string;
1459
+ status: string;
1460
+ }>;
1461
+ getRemoteSession(sessionId: string): Promise<RemoteSessionInfo>;
1462
+ completeRemoteSession(sessionId: string): Promise<{
1463
+ ok: boolean;
1464
+ }>;
1465
+ /**
1466
+ * Issue a request that returns the raw `Response` (no JSON parsing, no
1467
+ * forced Content-Type). The caller sets `Content-Type`/`Accept` on `headers`
1468
+ * and reads the body itself (`arrayBuffer()` / `json()`).
1469
+ *
1470
+ * A single retry fires only on the same transient statuses `request()`
1471
+ * retries (authorizer-timeout 500 + LB 502/503/504) and transient network
1472
+ * errors — before the route handler runs — so re-issuing a POST does not
1473
+ * risk a duplicate side effect. Voice TTS/STT are effectively idempotent
1474
+ * (re-synthesize / re-transcribe) and meter server-side keyed on the
1475
+ * gateway requestId, so a retried transcription doesn't double-bill.
1476
+ */
1477
+ private rawFetch;
1478
+ /**
1479
+ * Text-to-speech. Returns raw PCM audio bytes plus their framing — the
1480
+ * voice service defaults to 24 kHz / mono / 16-bit. Wrap in a WAV container
1481
+ * to produce a playable file. Metered per character against the tenant
1482
+ * credit pool server-side; TTS completes regardless of metering outcome.
1483
+ */
1484
+ tts(args: VoiceTtsArgs): Promise<VoiceTtsResult>;
1485
+ /**
1486
+ * Speech-to-text. Accepts raw linear16 (16-bit LE) mono PCM — NOT a WAV or
1487
+ * other container (the endpoint transcribes with a fixed linear16 encoding,
1488
+ * so a container header would be transcribed as noise). Strip any WAV header
1489
+ * and pass `sampleRate` from it before calling. Metered by transcribed
1490
+ * duration against the tenant credit pool server-side.
1491
+ */
1492
+ stt(args: VoiceSttArgs): Promise<VoiceSttResult>;
1493
+ }
1494
+ //# sourceMappingURL=index.d.ts.map
1495
+ //#endregion
1496
+ //#endregion
1497
+ //#region ../chat/dist/index.d.ts
1498
+ //#region src/types.d.ts
1499
+ /**
1500
+ * Types for the @alfe.ai/chat package.
1501
+ */
1502
+ /** Configuration for the ChatServiceClient. */
1503
+ interface ChatServiceClientOptions {
1504
+ /** Chat service WebSocket URL (e.g. wss://chat.dev.alfe.ai/ws) */
1505
+ wsUrl: string;
1506
+ /** Agent API key for Bearer auth */
1507
+ apiKey: string;
1508
+ /** Called when the chat service sends an RPC request */
1509
+ onRequest: (request: RpcRequest) => void;
1510
+ /** Called when connection state changes */
1511
+ onConnectionChange?: (connected: boolean) => void;
1512
+ /** Optional logger (defaults to console) */
1513
+ logger?: Logger;
1514
+ }
1515
+ /** An RPC request from the chat service. */
1516
+ interface RpcRequest {
1517
+ type: 'req';
1518
+ id: string;
1519
+ method: string;
1520
+ params: Record<string, unknown>;
1521
+ }
1522
+ /** Minimal logger interface — no dependency on any logging library. */
1523
+ interface Logger {
1524
+ info(msg: string, ...args: unknown[]): void;
1525
+ warn(msg: string, ...args: unknown[]): void;
1526
+ error(msg: string, ...args: unknown[]): void;
1527
+ debug(msg: string, ...args: unknown[]): void;
1528
+ }
1529
+ //#endregion
1530
+ //#region src/client.d.ts
1531
+
1532
+ declare class ChatServiceClient {
1533
+ private readonly options;
1534
+ private ws;
1535
+ private stopped;
1536
+ private connected;
1537
+ private retryCount;
1538
+ private retryTimer;
1539
+ private heartbeatTimer;
1540
+ private isAlive;
1541
+ private readonly log;
1542
+ private pendingRequests;
1543
+ private requestCounter;
1544
+ private messageQueue;
1545
+ private readonly MAX_QUEUE_SIZE;
1546
+ constructor(options: ChatServiceClientOptions);
1547
+ get isConnected(): boolean;
1548
+ /** Start connecting to the chat service. */
1549
+ start(): void;
1550
+ /** Stop the client and close the connection. */
1551
+ stop(): void;
1552
+ /** Send a raw JSON message to the chat service. Queues if disconnected. */
1553
+ send(msg: unknown): void;
1554
+ /** Send an RPC response back to the chat service. */
1555
+ sendResponse(id: string, ok: boolean, payload?: unknown): void;
1556
+ /** Send a streaming event to the chat service (e.g. chat deltas). */
1557
+ sendEvent(event: string, payload: unknown): void;
1558
+ /** Send a fire-and-forget notification to the chat service. */
1559
+ notify(event: string, payload: Record<string, unknown>): void;
1560
+ /**
1561
+ * Send an RPC request to the chat service and wait for a response.
1562
+ * Used by agent-to-agent tools to invoke server-side logic.
1563
+ */
1564
+ sendRequest(method: string, params: Record<string, unknown>, timeoutMs?: number): Promise<Record<string, unknown>>;
1565
+ private flushMessageQueue;
1566
+ private flushPendingRequests;
1567
+ private doConnect;
1568
+ private handleMessage;
1569
+ private scheduleReconnect;
1570
+ /**
1571
+ * Detect a silently-dropped connection. Each tick: if no inbound liveness
1572
+ * (pong/ping/message) was seen since the last tick, force-close so the
1573
+ * `close` handler reconnects; otherwise send a fresh ping and arm the next.
1574
+ */
1575
+ private startHeartbeat;
1576
+ private clearHeartbeat;
1577
+ }
1578
+ //#endregion
1579
+ //#region src/config.d.ts
1580
+ /**
1581
+ * Chat config resolver — reads API key from ~/.alfe/config.toml
1582
+ * and derives the chat service URL from the token prefix.
1583
+ *
1584
+ * Resolution priority: explicit overrides → config file → env vars.
1585
+ */
1586
+ //#endregion
1587
+ //#region src/stream-mapper.d.ts
1588
+ /**
1589
+ * Claude Code `--output-format stream-json` → Alfe chat-activity mapper.
1590
+ *
1591
+ * This is the load-bearing core of the bridge. It consumes the newline-delimited
1592
+ * JSON events emitted by `claude -p ... --output-format stream-json
1593
+ * --include-partial-messages` and translates them into the SAME wire shapes the
1594
+ * `@alfe.ai/openclaw-chat` plugin emits, so the chat service / adapter / UI
1595
+ * treat a Claude Code turn identically to an OpenClaw turn.
1596
+ *
1597
+ * Ground truth: a real captured turn (thinking + Read tool + text + result) at
1598
+ * `src/__fixtures__/turn-read-tool.stream.jsonl`. The mapper test runs against
1599
+ * that fixture (TEST-FIRST).
1600
+ *
1601
+ * Event model (verified against the fixture + Claude Code stream-json docs):
1602
+ * - `{type:'system', subtype:'init', session_id}` — carries the claude
1603
+ * session id we persist for `--resume`.
1604
+ * - `{type:'stream_event', event:{type:'content_block_delta', delta:{
1605
+ * type:'thinking_delta', thinking}}}` — a thinking chunk.
1606
+ * - `{type:'stream_event', event:{type:'content_block_delta', delta:{
1607
+ * type:'text_delta', text}}}` — a visible-text chunk (accumulate → the
1608
+ * CUMULATIVE snapshot the adapter length-slices).
1609
+ * - `{type:'assistant', message:{content:[{type:'tool_use', id, name,
1610
+ * input}]}}` — a tool CALL. The non-streaming `assistant` message has the
1611
+ * COMPLETE id/name/input (the streaming `content_block_start` has empty
1612
+ * input, filled by `input_json_delta`), so we correlate off THIS.
1613
+ * - `{type:'user', message:{content:[{type:'tool_result', tool_use_id,
1614
+ * content, is_error?}]}}` — a tool RESULT. `tool_use_id === tool_use.id`.
1615
+ * - `{type:'result', is_error, result, duration_ms}` — the turn end.
1616
+ *
1617
+ * The mapper is a PURE reducer over one event: it takes the running state and an
1618
+ * event, mutates the state, and returns the list of `MapperEmission`s to send.
1619
+ * It performs NO IO — the host does the sending / persisting. `progressText` /
1620
+ * the 250ms mid-tool throttle from openclaw-chat is inert here: Claude Code has
1621
+ * no mid-tool progress analog, so we deliberately don't implement it.
1622
+ */
1623
+ interface ThinkingActivity {
1624
+ kind: 'thinking';
1625
+ text: string;
1626
+ delta?: boolean;
1627
+ ts?: number;
1628
+ seq?: number;
1629
+ }
1630
+ interface ToolActivity {
1631
+ kind: 'tool';
1632
+ toolCallId: string;
1633
+ name: string;
1634
+ status: 'running' | 'done' | 'failed';
1635
+ summary?: string;
1636
+ argsText?: string;
1637
+ resultText?: string;
1638
+ isError?: boolean;
1639
+ durationMs?: number;
1640
+ truncated?: {
1641
+ args?: boolean;
1642
+ result?: boolean;
1643
+ };
1644
+ ts?: number;
1645
+ seq?: number;
1646
+ }
1647
+ type ActivityEmission = {
1648
+ type: 'chat-activity';
1649
+ seq: number;
1650
+ ts: number;
1651
+ activity: ThinkingActivity;
1652
+ } | {
1653
+ type: 'chat-activity';
1654
+ seq: number;
1655
+ ts: number;
1656
+ activity: ToolActivity;
1657
+ };
1658
+ /** A cumulative visible-text snapshot → a `chat` delta frame (B2). */
1659
+ interface TextEmission {
1660
+ type: 'chat-delta';
1661
+ seq: number;
1662
+ ts: number;
1663
+ /** CUMULATIVE visible-so-far. Monotonically non-shrinking across the turn. */
1664
+ text: string;
1665
+ }
1666
+ /** The turn ended. `isError` routes the host to the C3 failure path. */
1667
+ interface EndEmission {
1668
+ type: 'turn-end';
1669
+ seq: number;
1670
+ ts: number;
1671
+ isError: boolean;
1672
+ /** Final result text (present on success `result`); '' on error. */
1673
+ finalText: string;
1674
+ durationMs?: number;
1675
+ /** Error reason (present when `isError`). Goes under `payload.message` (C3). */
1676
+ errorMessage?: string;
1677
+ }
1678
+ type MapperEmission = ActivityEmission | TextEmission | EndEmission;
1679
+ interface PendingTool {
1680
+ name: string;
1681
+ argsText?: string;
1682
+ argsTruncated?: boolean;
1683
+ startTs: number;
1684
+ }
1685
+ interface MapperState {
1686
+ /** Claude session id from the `init` event (persist for `--resume`). */
1687
+ claudeSessionId: string | null;
1688
+ /** Monotonic per-turn seq stamped on every emission (E1). */
1689
+ seq: number;
1690
+ /** CUMULATIVE visible text (B2 — never shrinks). */
1691
+ visibleText: string;
1692
+ /** tool_use.id → correlation record, joined on the later tool_result. */
1693
+ pendingTools: Map<string, PendingTool>;
1694
+ /** Whether the terminal `result` event has been seen. */
1695
+ ended: boolean;
1696
+ }
1697
+ declare function createMapperState(): MapperState;
1698
+ /**
1699
+ * Map ONE stream-json event → zero or more emissions, mutating `state`.
1700
+ *
1701
+ * `nowMs` lets the host stamp real wall-clock timestamps (Claude Code events
1702
+ * don't all carry ms epochs); the fixture test injects a fixed clock.
1703
+ */
1704
+ declare function mapStreamEvent(state: MapperState, raw: unknown, nowMs: number): MapperEmission[];
1705
+ /**
1706
+ * Convenience: run the reducer over a whole array of parsed events (e.g. a
1707
+ * fixture). Returns the final state + all emissions in order. The host does NOT
1708
+ * use this at runtime (it maps line-by-line as they stream), but it's the
1709
+ * cleanest thing to assert against in a fixture test.
1710
+ */
1711
+ declare function mapStreamEvents(events: unknown[], clock?: () => number): {
1712
+ state: MapperState;
1713
+ emissions: MapperEmission[];
1714
+ };
1715
+ /**
1716
+ * Terminalize the mapper's in-turn activity into persisted `ChatActivityRecord`
1717
+ * form (session store). Tools still pending at flush time (no tool_result seen)
1718
+ * are persisted as `interrupted`. Thinking deltas are concatenated into ONE
1719
+ * record. This mirrors openclaw-chat's per-turn accumulator flush.
1720
+ */
1721
+ declare function collectTurnActivityRecords(emissions: MapperEmission[]): {
1722
+ kind: 'thinking' | 'tool';
1723
+ [k: string]: unknown;
1724
+ }[];
1725
+ //#endregion
1726
+ //#region src/session-driver.d.ts
1727
+ /**
1728
+ * `--input-format stream-json` does not keep a headless `claude -p` process
1729
+ * resident across turns on the pinned CLI (see header VERIFICATION). Until a
1730
+ * CLI ships a resident headless stdin channel, warm-streaming falls back to
1731
+ * per-turn spawn. Flip this (and implement a resident path) when that lands.
1732
+ */
1733
+ declare const WARM_STREAMING_SUPPORTED = false;
1734
+ /**
1735
+ * Restricted tool set for unattended bridged turns (Phase 2 tool-permission
1736
+ * posture). An inbound chat message could try to induce a destructive shell
1737
+ * command, and these turns run unattended on the user's machine. We therefore
1738
+ * pass an explicit allow-list of read/analysis/edit tools and DENY Bash by
1739
+ * default, and use `--permission-mode default` (NEVER `bypassPermissions` /
1740
+ * `--dangerously-skip-permissions`). See DEVELOPING.md → "Tool-permission
1741
+ * posture". Bash is intentionally absent — a bridged turn cannot run arbitrary
1742
+ * shell. Callers that want a wider set pass `allowedTools` explicitly.
1743
+ */
1744
+ declare const DEFAULT_ALLOWED_TOOLS: readonly ["Read", "Grep", "Glob", "LS", "Edit", "Write", "WebFetch", "WebSearch", "Task", "TodoWrite"];
1745
+ /** Sane default permission posture. NEVER `bypassPermissions`. */
1746
+ declare const DEFAULT_PERMISSION_MODE = "default";
1747
+ /** How a turn ended, surfaced to the host so it can settle / persist. */
1748
+ interface DriverTurnResult {
1749
+ /** The claude session id observed on the `system:init` event (for --resume). */
1750
+ claudeSessionId: string | null;
1751
+ /** All emissions produced during the turn, in order (for activity persistence). */
1752
+ emissions: MapperEmission[];
1753
+ /** True if the terminal `result` event carried `is_error`, or the child failed. */
1754
+ isError: boolean;
1755
+ /** Final visible/result text (cumulative). '' on error. */
1756
+ finalText: string;
1757
+ /** Error reason when `isError` (goes under `payload.message` — C3). */
1758
+ errorMessage?: string;
1759
+ /** True if the turn was aborted via `abort()` (SIGINT). */
1760
+ aborted: boolean;
1761
+ }
1762
+ interface RunTurnParams {
1763
+ /** The user prompt for this turn. */
1764
+ prompt: string;
1765
+ /** Claude session id to `--resume`; null → start a fresh session. */
1766
+ claudeSessionId: string | null;
1767
+ /** Working directory (the agent workspace). */
1768
+ cwd: string;
1769
+ /** Path to the generated MCP config JSON. Omitted → no `--mcp-config`. */
1770
+ mcpConfigPath?: string;
1771
+ /** Persona + reply-style text appended to the system prompt. Omitted → none. */
1772
+ appendSystemPrompt?: string;
1773
+ /** Tool allow-list (defaults to DEFAULT_ALLOWED_TOOLS). */
1774
+ allowedTools?: readonly string[];
1775
+ /** Permission mode (defaults to DEFAULT_PERMISSION_MODE — never bypass). */
1776
+ permissionMode?: string;
1777
+ /** Per-emission callback so the host can stream deltas/activity live. */
1778
+ onEmission: (emission: MapperEmission) => void;
1779
+ /** Wall-clock override for the mapper (tests). Defaults to Date.now. */
1780
+ now?: () => number;
1781
+ }
1782
+ /**
1783
+ * Hard cap on a single turn (E2). Deliberately BELOW the chat service's RPC
1784
+ * timeout (`AGENT_TIMEOUT_SEC=300` → 305s in daemon-server) so the host settles
1785
+ * a maxed-out turn *before* the caller rejects it — an equal cap races the
1786
+ * caller and can surface a spurious "stuck then error" flash. Inject-origin
1787
+ * turns have an even shorter caller timeout (~125s); those may over-run to this
1788
+ * cap, but the late settle is harmlessly no-op'd by the chat service.
1789
+ */
1790
+ declare const TURN_TIMEOUT_MS = 300000;
1791
+ /**
1792
+ * Spawns and drives `claude` subprocesses. Tracks a SINGLE in-flight child, so
1793
+ * the host creates ONE driver PER TURN (`ClaudeCodeHost.makeDriver`) — a driver
1794
+ * shared across concurrent sessions would let the last spawn overwrite the
1795
+ * `child` slot and cross-abort / mis-probe the others.
1796
+ */
1797
+ declare class SessionDriver {
1798
+ private child;
1799
+ private readonly bin;
1800
+ private readonly baseEnv;
1801
+ private readonly log;
1802
+ /** Injected spawn — tests replace this with a fake child. */
1803
+ private readonly spawnImpl;
1804
+ constructor(opts?: {
1805
+ claudeBin?: string;
1806
+ baseEnv?: NodeJS.ProcessEnv;
1807
+ logger?: Logger;
1808
+ /** Test seam: inject a fake `spawn`. */
1809
+ __spawn?: typeof spawn;
1810
+ });
1811
+ /** True while a `claude` child is running — backs the host's B4 drain probe. */
1812
+ get busy(): boolean;
1813
+ /**
1814
+ * Abort the in-flight turn by SIGINT-ing the child (B3). No-op if idle. The
1815
+ * child's exit drives `runTurn` to settle with `aborted: true`.
1816
+ */
1817
+ abort(): void;
1818
+ /**
1819
+ * Run ONE turn. Modes reconcile to the same per-turn spawn (see header
1820
+ * VERIFICATION) — `voice` is accepted for API symmetry and future warm mode.
1821
+ */
1822
+ runTurn(params: RunTurnParams, mode?: 'chat' | 'voice'): Promise<DriverTurnResult>;
1823
+ private buildArgs;
1824
+ private runPerTurn;
1825
+ }
1826
+ //#endregion
1827
+ //#region src/host.d.ts
1828
+ interface ClaudeCodeHostConfig {
1829
+ /** Chat service WS URL (e.g. wss://chat.dev.alfe.ai/ws). */
1830
+ wsUrl: string;
1831
+ /** Agent API key (Bearer auth for both chat WS and AgentApiClient). */
1832
+ apiKey: string;
1833
+ /** Base URL for the Alfe agent API (AgentApiClient). */
1834
+ apiUrl: string;
1835
+ /** Agent workspace dir (cwd for `claude`, source of persona files). */
1836
+ workspace: string;
1837
+ /** Path to the generated MCP config JSON (memory/voice/send + integrations). */
1838
+ mcpConfigPath?: string;
1839
+ /** Path to persist Alfe sessionId → claude session id. */
1840
+ claudeSessionsPath?: string;
1841
+ logger?: Logger;
1842
+ }
1843
+ declare class ClaudeCodeHost {
1844
+ private readonly chat;
1845
+ /**
1846
+ * Factory for a per-turn `SessionDriver`. A driver tracks a single in-flight
1847
+ * `child`, so distinct sessions running concurrently (C4) each get their OWN
1848
+ * driver — a shared singleton would cross-abort and mis-probe (its one `child`
1849
+ * slot would be overwritten by whichever session spawned last).
1850
+ */
1851
+ private readonly makeDriver;
1852
+ private readonly api;
1853
+ private readonly log;
1854
+ private readonly cfg;
1855
+ private readonly queues;
1856
+ /** Alfe sessionId → claude session id map (mirrors ~/.alfe/claude-code/sessions.json). */
1857
+ private readonly claudeSessionIds;
1858
+ private claudeSessionsPath;
1859
+ /** RPC sessionKey → currently RUNNING request id (for chat.abort targeting). */
1860
+ private readonly runningTurns;
1861
+ /** idempotencyKey → settled request id, for E3 duplicate-RPC dedupe. */
1862
+ private readonly seenIdempotencyKeys;
1863
+ /** RPC sessionKey → resolved SessionDriver so chat.abort can SIGINT the child. */
1864
+ private activeDriverBySession;
1865
+ constructor(cfg: ClaudeCodeHostConfig, deps?: {
1866
+ /** Per-turn driver factory (test seam). Called once per turn. */
1867
+ makeDriver?: () => SessionDriver;
1868
+ chat?: ChatServiceClient;
1869
+ api?: AgentApiClient;
1870
+ });
1871
+ /** Start the chat WS. Non-blocking — the client reconnects internally. */
1872
+ start(): Promise<void>;
1873
+ /** Stop the chat WS (SIGTERM path). */
1874
+ stop(): void;
1875
+ /**
1876
+ * Inbound RPC dispatch. Invoked by the chat client's `onRequest` in
1877
+ * production; also the test seam (drive `agent`/`chat.abort`/… directly).
1878
+ */
1879
+ handleRequest(req: RpcRequest): Promise<void>;
1880
+ private handleAgent;
1881
+ private runAgentTurn;
1882
+ /**
1883
+ * Run a turn; on a `--resume` failure (stale/missing claude session id) start
1884
+ * fresh and prepend the last-N stored messages so the model keeps context.
1885
+ */
1886
+ private runWithResumeFallback;
1887
+ private forwardEmission;
1888
+ private handleChatAbort;
1889
+ private handleSessionsList;
1890
+ private handleSessionsGet;
1891
+ private buildSystemPrompt;
1892
+ private readPersona;
1893
+ private buildHistoryPreamble;
1894
+ private ensureSession;
1895
+ private loadClaudeSessionIds;
1896
+ private saveClaudeSessionIds;
1897
+ private pruneIdempotency;
1898
+ }
1899
+ /** Strip the `agent:{agentId}:` prefix from a full server session key. */
1900
+ declare function stripAgentPrefix(sessionKey: string): string;
1901
+ //#endregion
1902
+ //#region src/child-env.d.ts
1903
+ /**
1904
+ * Child-process environment construction for spawned `claude` CLI processes.
1905
+ *
1906
+ * TWO non-negotiable invariants (Phase 2 plan, audit B2 + the hard
1907
+ * constraints):
1908
+ *
1909
+ * 1. **Scrub `ANTHROPIC_API_KEY` / `ANTHROPIC_BASE_URL` /
1910
+ * `ANTHROPIC_AUTH_TOKEN`.** A self-hosted Mac very often has these set (for
1911
+ * other tooling). If ANY of them reaches `claude`, the CLI silently switches
1912
+ * from the user's Claude subscription (OAuth) to API-key/proxy auth — which
1913
+ * is exactly what this whole runtime exists to avoid (no Agent SDK, no
1914
+ * proxy, subscription only). The break is SILENT: the turn "works" but bills
1915
+ * the wrong plane. So we strip them unconditionally from the child env.
1916
+ *
1917
+ * 2. **Set `ALFE_CLAUDE_BRIDGE=1`.** The Phase-1 CLI hooks
1918
+ * (`alfe claude hook ...` in `packages/cli/src/commands/claude.ts`) check
1919
+ * `process.env.ALFE_CLAUDE_BRIDGE` FIRST and no-op when set, so the host —
1920
+ * not the interactive-session hooks — owns memory recall/capture for bridged
1921
+ * turns. Omitting this double-captures every message. The env var NAME is a
1922
+ * cross-phase contract; do not rename without updating the CLI.
1923
+ */
1924
+ /** Anthropic env vars that force API-key / proxy auth and MUST be scrubbed. */
1925
+ declare const SCRUBBED_ANTHROPIC_ENV_VARS: readonly ["ANTHROPIC_API_KEY", "ANTHROPIC_BASE_URL", "ANTHROPIC_AUTH_TOKEN"];
1926
+ /** Cross-phase contract marker (see header + packages/cli/src/commands/claude.ts). */
1927
+ declare const BRIDGE_ENV_VAR = "ALFE_CLAUDE_BRIDGE";
1928
+ /**
1929
+ * Build the env handed to a spawned `claude` child. Pure — takes the base env
1930
+ * explicitly so it unit-tests without touching `process.env`.
1931
+ *
1932
+ * - Every scrubbed Anthropic var is DELETED (not blanked) so `claude`'s
1933
+ * env-detection treats it as absent.
1934
+ * - `ALFE_CLAUDE_BRIDGE=1` is set.
1935
+ * - Any caller-supplied `extra` entries are layered last (e.g. a per-turn
1936
+ * marker) but can NEVER re-introduce a scrubbed var — the scrub runs after
1937
+ * the merge as the final gate.
1938
+ */
1939
+ declare function buildChildEnv(base: NodeJS.ProcessEnv, extra?: Record<string, string>): NodeJS.ProcessEnv;
1940
+ //#endregion
1941
+ //#region src/session/session-store.d.ts
1942
+ type StoredMessageComponent = {
1943
+ type: 'link_button';
1944
+ id: string;
1945
+ label: string;
1946
+ url: string;
1947
+ target?: 'same-tab' | 'new-tab' | 'popup';
1948
+ style?: 'primary' | 'secondary' | 'danger';
1949
+ } | {
1950
+ type: 'quick_reply';
1951
+ id: string;
1952
+ label: string;
1953
+ value: string;
1954
+ style?: 'primary' | 'secondary' | 'danger';
1955
+ };
1956
+ /**
1957
+ * Persisted reference to a message attachment. Deliberately stores NO URL —
1958
+ * the download URL is short-lived and re-signed on demand. Mirrors
1959
+ * openclaw-chat's `StoredAttachment`.
1960
+ */
1961
+ interface StoredAttachment {
1962
+ attachmentId: string;
1963
+ type: 'image' | 'video' | 'audio' | 'document' | 'file';
1964
+ mimeType: string;
1965
+ filename: string;
1966
+ size?: number;
1967
+ }
1968
+ interface ChatMessage {
1969
+ role: 'user' | 'assistant';
1970
+ content: string;
1971
+ timestamp: number;
1972
+ senderId?: string;
1973
+ senderName?: string;
1974
+ components?: StoredMessageComponent[];
1975
+ attachments?: StoredAttachment[];
1976
+ }
1977
+ /**
1978
+ * Persisted form of a turn's agent activity (tool cards + thinking), so a
1979
+ * refresh can replay what the live `chat-activity` stream showed. Terminal by
1980
+ * construction: no `progressText`, no `running` status — a tool still running
1981
+ * at flush time persists as `interrupted`.
1982
+ */
1983
+ type ChatActivityRecord = {
1984
+ kind: 'thinking';
1985
+ text: string;
1986
+ ts: number;
1987
+ } | {
1988
+ kind: 'tool';
1989
+ toolCallId: string;
1990
+ name: string;
1991
+ status: 'done' | 'failed' | 'interrupted';
1992
+ summary?: string;
1993
+ argsText?: string;
1994
+ resultText?: string;
1995
+ isError?: boolean;
1996
+ durationMs?: number;
1997
+ truncated?: {
1998
+ args?: boolean;
1999
+ progress?: boolean;
2000
+ result?: boolean;
2001
+ };
2002
+ ts: number;
2003
+ };
2004
+ interface SessionData {
2005
+ sessionId: string;
2006
+ agentId: string;
2007
+ channel: string;
2008
+ tenantId?: string;
2009
+ userId?: string;
2010
+ createdAt: string;
2011
+ updatedAt: string;
2012
+ messages: ChatMessage[];
2013
+ /**
2014
+ * Activity records replayed on `sessions.get`. Unlike openclaw-chat (whose
2015
+ * PRIMARY source is OpenClaw's transcript), these records are the SOLE
2016
+ * history source here — there is no external transcript. Absent on
2017
+ * pre-persistence files.
2018
+ */
2019
+ activity?: ChatActivityRecord[];
2020
+ }
2021
+ interface SessionSummary {
2022
+ sessionId: string;
2023
+ agentId: string;
2024
+ channel: string;
2025
+ tenantId?: string;
2026
+ userId?: string;
2027
+ createdAt: string;
2028
+ lastMessageAt?: string;
2029
+ preview?: string;
2030
+ messageCount: number;
2031
+ }
2032
+ declare function getSession(sessionId: string): Promise<SessionData | null>;
2033
+ declare function saveSession(session: SessionData): Promise<void>;
2034
+ declare function createSession(sessionId: string, agentId: string, channel: string, tenantId?: string, userId?: string): Promise<SessionData>;
2035
+ declare function addMessage(sessionId: string, role: 'user' | 'assistant', content: string, senderId?: string, senderName?: string, components?: StoredMessageComponent[], attachments?: StoredAttachment[]): Promise<void>;
2036
+ /**
2037
+ * Append a turn's terminalized activity records. Called ONCE per turn (from the
2038
+ * dispatch `finally`) — never per-delta.
2039
+ */
2040
+ declare function appendActivity(sessionId: string, entries: ChatActivityRecord[]): Promise<void>;
2041
+ declare function listSessions(filters?: {
2042
+ agentId?: string;
2043
+ channel?: string;
2044
+ tenantId?: string;
2045
+ userId?: string;
2046
+ }, limit?: number): Promise<SessionSummary[]>;
2047
+ //#endregion
2048
+ //#region src/host-config.d.ts
2049
+ /**
2050
+ * Build the host config. Throws (→ non-zero exit from the CLI) when the chat
2051
+ * URL / api key can't resolve.
2052
+ */
2053
+ declare function resolveHostConfig(): ClaudeCodeHostConfig;
2054
+ //#endregion
2055
+ export { type ActivityEmission, BRIDGE_ENV_VAR, type ChatActivityRecord, type ChatMessage, ClaudeCodeHost, type ClaudeCodeHostConfig, DEFAULT_ALLOWED_TOOLS, DEFAULT_PERMISSION_MODE, type DriverTurnResult, type EndEmission, type MapperEmission, type MapperState, type RunTurnParams, SCRUBBED_ANTHROPIC_ENV_VARS, type SessionData, SessionDriver, type SessionSummary, TURN_TIMEOUT_MS, type TextEmission, type ThinkingActivity, type ToolActivity, WARM_STREAMING_SUPPORTED, addMessage, appendActivity, buildChildEnv, collectTurnActivityRecords, createMapperState, createSession, getSession, listSessions, mapStreamEvent, mapStreamEvents, resolveHostConfig, saveSession, stripAgentPrefix };