@alfe.ai/claude-code-host 0.1.13 → 0.1.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -48,12 +48,18 @@ declare class AgentApiTransport {
48
48
  * @param extra.retry Whether to retry once on transient failures. Safe reads
49
49
  * (GET/HEAD/OPTIONS) default to true; mutations default to false. Set true
50
50
  * only when the endpoint's server-side contract is explicitly idempotent.
51
+ * @param extra.retryStatuses Narrow the HTTP statuses that trigger the retry
52
+ * (default 500/502/503/504). Network errors and timeouts are always retried
53
+ * when `retry` is on. Pass a subset when a status in the default set is a
54
+ * deterministic answer for this endpoint (e.g. a 502 that means "upstream
55
+ * denied", which a retry cannot change).
51
56
  * @param extra.signal Optional caller cancellation combined with the client's
52
57
  * own timeout budget. Aborting either signal cancels the request.
53
58
  */
54
59
  request<T>(path: string, options?: RequestInit, extra?: {
55
60
  timeoutMs?: number;
56
61
  retry?: boolean;
62
+ retryStatuses?: ReadonlySet<number>;
57
63
  signal?: AbortSignal;
58
64
  }): Promise<T>;
59
65
  }
@@ -82,7 +88,37 @@ interface ConnectProviderAccounts {
82
88
  provider: string;
83
89
  accounts: ConnectProviderAccount[];
84
90
  }
91
+ /**
92
+ * One AWS CLI profile an agent may use (`GET /agent/connect/aws/accounts`).
93
+ * `roleArn` is null for a connection's Direct identity. Carries no secret
94
+ * material; sessions are minted per call via `getAwsSession`.
95
+ */
96
+ interface AwsProfile {
97
+ profile: string;
98
+ roleArn: string | null;
99
+ accountId: string;
100
+ region: string;
101
+ label: string | null;
102
+ connectionId: string;
103
+ accountIdentifier: string;
104
+ displayName: string | null;
105
+ connectedAt: string;
106
+ }
107
+ /** AWS CLI `credential_process` output (Version 1), as minted by Connect. */
108
+ interface AwsSessionCredentials {
109
+ Version: 1;
110
+ AccessKeyId: string;
111
+ SecretAccessKey: string;
112
+ SessionToken: string;
113
+ /** ISO 8601 expiry. */
114
+ Expiration: string;
115
+ }
85
116
  declare class ConnectCredentialsApi extends ApiBase {
117
+ /** Platform application identity for private, local Telegram login. Never expose this to tools. */
118
+ telegramGetApplication(): Promise<{
119
+ apiId: number;
120
+ apiHash: string;
121
+ }>;
86
122
  /**
87
123
  * Discover all active Connections visible to this agent for one provider.
88
124
  * Preserve the provider-specific credential projection without guessing a
@@ -476,7 +512,9 @@ declare class ConnectCredentialsApi extends ApiBase {
476
512
  * endpoint walks the agent's full visible scope chain to find a matching
477
513
  * connection (works for inherited team/project Microsoft connections).
478
514
  */
479
- refreshMicrosoftAccountToken(accountIdentifier: string): Promise<{
515
+ refreshMicrosoftAccountToken(accountIdentifier: string, options?: {
516
+ connectionId?: string;
517
+ }): Promise<{
480
518
  accessToken: string;
481
519
  accessTokenExpiresAt: string;
482
520
  expiresAt: string;
@@ -691,12 +729,33 @@ declare class ConnectCredentialsApi extends ApiBase {
691
729
  * the fresh `accessJwt`) — callers typically ignore it and re-fetch via
692
730
  * `getSocialAccounts` for a consistent shape.
693
731
  */
694
- refreshSocialAccount(provider: string, accountIdentifier: string): Promise<{
732
+ refreshSocialAccount(provider: string, accountIdentifier: string, options?: {
733
+ connectionId?: string;
734
+ }): Promise<{
695
735
  accountIdentifier: string;
696
736
  accessToken: string;
697
737
  accessTokenExpiresAt: string;
698
738
  expiresAt: string;
699
739
  }>;
740
+ /**
741
+ * List the AWS CLI profiles visible to this agent across every in-scope AWS
742
+ * connection. Each entry names the `connectionId` + `profile` pair that
743
+ * {@link getAwsSession} mints a session for. Fields are selected explicitly;
744
+ * the wire row is never spread.
745
+ */
746
+ getAwsProfiles(): Promise<AwsProfile[]>;
747
+ /**
748
+ * Mint short-lived AWS session credentials for one profile of one AWS
749
+ * connection, in the AWS CLI `credential_process` (Version 1) shape. The
750
+ * server re-checks scope + active status on every call (403 otherwise; 404
751
+ * for an unknown profile; 403 `aws_access_denied` when AWS denies the
752
+ * assume, 502 on older servers). Minting has no persistent side effect, so
753
+ * one retry is safe, but only on genuinely transient failures (500/503/504
754
+ * and network errors): a denial is deterministic and a retry just doubles
755
+ * the latency of every failing `aws` call. The response is secret material:
756
+ * never log it.
757
+ */
758
+ getAwsSession(connectionId: string, profile: string): Promise<AwsSessionCredentials>;
700
759
  }
701
760
  //# sourceMappingURL=connect-credentials.d.ts.map
702
761
  //#endregion
@@ -790,6 +849,33 @@ interface AgentWorkspaceInfo {
790
849
  teamIds?: string[];
791
850
  projectIds?: string[];
792
851
  }
852
+ /**
853
+ * The agent's own Claude credential (claude-code runtime), from
854
+ * GET /agent/claude-credential. `oauth_token` is a Claude subscription token
855
+ * (`claude setup-token`) for `CLAUDE_CODE_OAUTH_TOKEN`; `api_key` is an
856
+ * Anthropic API key for `ANTHROPIC_API_KEY`. `revision` changes on every
857
+ * dashboard write. Hold the value in memory only — never log or persist it.
858
+ */
859
+ interface AgentClaudeCredential {
860
+ kind: "oauth_token" | "api_key";
861
+ value: string;
862
+ revision: string;
863
+ updatedAt: string;
864
+ }
865
+ /** Claude auth health a claude-code host reports for its agent. */
866
+ type ClaudeCredentialHealth = "ok" | "missing" | "invalid";
867
+ /** Body of PUT /agent/claude-credential/status. */
868
+ interface ClaudeCredentialStatusReport {
869
+ status: ClaudeCredentialHealth;
870
+ /** Short (≤200 chars), fixed host-side explanation. Never `claude` output or a secret. */
871
+ detail?: string;
872
+ /**
873
+ * The credential revision the host checked (from `getClaudeCredential()`);
874
+ * omit when it found none. The server drops reports for a superseded
875
+ * revision and answers `{ accepted: false }`.
876
+ */
877
+ revision?: string;
878
+ }
793
879
  declare class WorkspaceApi extends ApiBase {
794
880
  /**
795
881
  * GET /agent/workspace — workspace config for the authenticated agent
@@ -806,6 +892,22 @@ declare class WorkspaceApi extends ApiBase {
806
892
  }): Promise<{
807
893
  files: Record<string, string>;
808
894
  }>;
895
+ /**
896
+ * GET /agent/claude-credential — the authenticated claude-code agent's own
897
+ * Claude credential, or `null` when none is stored (a self-hosted host then
898
+ * falls back to the local `claude` login). Fetch on every runtime start: the
899
+ * daemon restarts the host whenever the credential changes.
900
+ */
901
+ getClaudeCredential(): Promise<AgentClaudeCredential | null>;
902
+ /**
903
+ * PUT /agent/claude-credential/status — report whether this claude-code agent
904
+ * can authenticate to Claude. `accepted: false` means the report described a
905
+ * credential that has since been replaced; nothing was written. Not retried
906
+ * automatically (a mutation): the host re-reports on its next signal.
907
+ */
908
+ reportClaudeCredentialStatus(report: ClaudeCredentialStatusReport): Promise<{
909
+ accepted: boolean;
910
+ }>;
809
911
  }
810
912
  //# sourceMappingURL=workspace.d.ts.map
811
913
  //#endregion
@@ -1392,17 +1494,34 @@ declare class IdentityApi extends ApiBase {
1392
1494
  platformId: string;
1393
1495
  kind?: "user" | "agent" | "service" | "bot" | "workspace";
1394
1496
  displayName?: string;
1497
+ /**
1498
+ * Provider `email` only: the sender address, matched read-only against an
1499
+ * existing identity's verified email contact. Ignored for other providers.
1500
+ */
1501
+ email?: string;
1502
+ /**
1503
+ * Provider `email` only: `true` when this agent verified the sender
1504
+ * (SPF/DKIM/DMARC). Unverified email senders are never matched to an
1505
+ * identity. Ignored for other providers.
1506
+ */
1507
+ emailVerified?: boolean;
1395
1508
  }): Promise<{
1396
1509
  identityId: string | null;
1397
1510
  status: string;
1398
1511
  created?: boolean;
1399
1512
  reason?: string;
1400
1513
  /**
1401
- * Flattened auriclabs permission strings for the resolved identity
1402
- * (scope-prefixed where applicable). Empty array on miss / org service
1403
- * outage — the runtime gate fails closed in that case.
1514
+ * Flattened auriclabs permission strings that apply at the calling
1515
+ * agent's own scope (`agent:<agentId>`, plus unscoped entries). A sender
1516
+ * with no identity still receives the tenant's `principalKind="all"`
1517
+ * grants. Empty when nothing applies, or when `permissionsUnavailable`.
1404
1518
  */
1405
1519
  permissions: string[];
1520
+ /**
1521
+ * `true` when the org service could not resolve permissions. The empty
1522
+ * `permissions` is then an outage, not a denial: retry rather than deny.
1523
+ */
1524
+ permissionsUnavailable?: boolean;
1406
1525
  }>;
1407
1526
  searchIdentities(args?: {
1408
1527
  q?: string;
@@ -1632,6 +1751,48 @@ declare class WebhooksApi extends ApiBase {
1632
1751
  }
1633
1752
  //# sourceMappingURL=webhooks.d.ts.map
1634
1753
  //#endregion
1754
+ //#region src/domains/wallet.d.ts
1755
+ interface AgentWalletAccount {
1756
+ connectionId: string;
1757
+ chainType: "ethereum" | "solana";
1758
+ address: string;
1759
+ }
1760
+ type AgentWalletTransferStatus = "pending_approval" | "processing" | "succeeded" | "rejected" | "failed";
1761
+ interface AgentWalletTransfer {
1762
+ requestId: string;
1763
+ agentId: string;
1764
+ connectionId: string;
1765
+ chainType: "ethereum" | "solana";
1766
+ network: string;
1767
+ fromAddress: string;
1768
+ toAddress: string;
1769
+ amount: string;
1770
+ reason?: string;
1771
+ status: AgentWalletTransferStatus;
1772
+ providerStatus?: string;
1773
+ providerActionId?: string;
1774
+ failureReason?: string;
1775
+ billingStatus: "unmetered" | "metered" | "failed";
1776
+ createdAt: string;
1777
+ updatedAt: string;
1778
+ decidedAt?: string;
1779
+ }
1780
+ declare class WalletApi extends ApiBase {
1781
+ listWalletAccounts(): Promise<AgentWalletAccount[]>;
1782
+ /** Create a proposal only; a human approves it in the Alfe dashboard. */
1783
+ requestWalletTransfer(input: {
1784
+ connectionId: string;
1785
+ chainType: "ethereum" | "solana";
1786
+ network: "ethereum" | "base" | "arbitrum" | "optimism" | "solana";
1787
+ fromAddress: string;
1788
+ toAddress: string;
1789
+ amount: string;
1790
+ reason?: string;
1791
+ }): Promise<AgentWalletTransfer>;
1792
+ getWalletTransfer(requestId: string): Promise<AgentWalletTransfer>;
1793
+ }
1794
+ //# sourceMappingURL=wallet.d.ts.map
1795
+ //#endregion
1635
1796
  //#region src/domains/chat.d.ts
1636
1797
  declare class ChatApi extends ApiBase {
1637
1798
  ensureDirectConversation(identityId: string): Promise<{
@@ -2003,7 +2164,7 @@ declare class TeamsApi extends ApiBase {
2003
2164
 
2004
2165
  //#endregion
2005
2166
  //#region src/index.d.ts
2006
- interface AgentApiClient extends SyncApi, IntegrationsApi, WorkspaceApi, ConnectCredentialsApi, TeamsApi, ChatApi, SecretsApi, IdentityApi, MemoryApi, SearchApi, KnowledgeApi, DatabaseApi, MobileApi, RemoteApi, SelfApi, VoiceApi, ImagesApi, WebhooksApi {}
2167
+ interface AgentApiClient extends SyncApi, IntegrationsApi, WorkspaceApi, ConnectCredentialsApi, TeamsApi, ChatApi, SecretsApi, IdentityApi, MemoryApi, SearchApi, KnowledgeApi, DatabaseApi, MobileApi, RemoteApi, SelfApi, VoiceApi, ImagesApi, WebhooksApi, WalletApi {}
2007
2168
  declare class AgentApiClient extends ApiBase {
2008
2169
  constructor(config: AgentApiClientConfig);
2009
2170
  }
@@ -2106,6 +2267,64 @@ declare class ChatServiceClient {
2106
2267
  * Resolution priority: explicit overrides → config file → env vars.
2107
2268
  */
2108
2269
  //#endregion
2270
+ //#region src/child-env.d.ts
2271
+ /**
2272
+ * Child-process environment construction for spawned `claude` CLI processes.
2273
+ *
2274
+ * THREE non-negotiable invariants:
2275
+ *
2276
+ * 1. **Scrub inherited Claude auth.** `ANTHROPIC_API_KEY` / `ANTHROPIC_BASE_URL`
2277
+ * / `ANTHROPIC_AUTH_TOKEN` / `CLAUDE_CODE_OAUTH_TOKEN` are removed from BOTH
2278
+ * the parent env and any caller `extra`. A self-hosted Mac very often has
2279
+ * these set for other tooling; if one reaches `claude` it silently switches
2280
+ * accounts or billing plane (or routes through a proxy). The break is
2281
+ * silent: the turn "works" but bills the wrong account.
2282
+ *
2283
+ * 2. **Inject the agent's own credential, last.** When the agents service holds
2284
+ * a Claude credential for this agent, it is set AFTER the scrub as exactly
2285
+ * one variable: `CLAUDE_CODE_OAUTH_TOKEN` (subscription token from
2286
+ * `claude setup-token`) or `ANTHROPIC_API_KEY` (Anthropic API key). Nothing
2287
+ * else can override it. With no credential, no auth variable is set and
2288
+ * `claude` uses the machine's own login (self-hosted fallback). The value
2289
+ * lives only in the host's memory and this env — never on disk, never logged.
2290
+ *
2291
+ * 3. **Set `ALFE_CLAUDE_BRIDGE=1`.** The Phase-1 CLI hooks
2292
+ * (`alfe claude hook ...` in `packages/cli/src/commands/claude.ts`) check
2293
+ * `process.env.ALFE_CLAUDE_BRIDGE` FIRST and no-op when set, so the host —
2294
+ * not the interactive-session hooks — owns memory recall/capture for bridged
2295
+ * turns. Omitting this double-captures every message. The env var NAME is a
2296
+ * cross-phase contract; do not rename without updating the CLI.
2297
+ */
2298
+ /** Claude auth env vars that MUST NOT be inherited by a `claude` child. */
2299
+ declare const SCRUBBED_ANTHROPIC_ENV_VARS: readonly ["ANTHROPIC_API_KEY", "ANTHROPIC_BASE_URL", "ANTHROPIC_AUTH_TOKEN", "CLAUDE_CODE_OAUTH_TOKEN"];
2300
+ /**
2301
+ * Provider-selection and endpoint overrides that would route `claude` away from
2302
+ * the Anthropic API. Dropped whenever an agent credential is injected, so an
2303
+ * inherited `CLAUDE_CODE_USE_BEDROCK=1` (say) can't send the turn to Bedrock
2304
+ * and ignore the stored credential. Without a credential they are left alone:
2305
+ * the machine's own `claude` setup is the fallback, whatever provider it uses.
2306
+ */
2307
+
2308
+ /** Cross-phase contract marker (see header + packages/cli/src/commands/claude.ts). */
2309
+ declare const BRIDGE_ENV_VAR = "ALFE_CLAUDE_BRIDGE";
2310
+ /** The agent's Claude credential, as fetched from `GET /agent/claude-credential`. */
2311
+ interface ChildCredential {
2312
+ kind: 'oauth_token' | 'api_key';
2313
+ value: string;
2314
+ }
2315
+ /** The single env var each credential kind is exported as. */
2316
+ declare const CREDENTIAL_ENV_VAR: Record<ChildCredential['kind'], string>;
2317
+ /**
2318
+ * Build the env handed to a spawned `claude` child. Pure — takes the base env
2319
+ * explicitly so it unit-tests without touching `process.env`.
2320
+ *
2321
+ * Order: merge `base` + `extra` → drop every scrubbed var (plus provider
2322
+ * overrides when a credential is injected) → set the bridge
2323
+ * marker → set the agent credential (if any). Neither `base` nor `extra` can
2324
+ * smuggle an auth variable in or override the injected credential.
2325
+ */
2326
+ declare function buildChildEnv(base: NodeJS.ProcessEnv, extra?: Record<string, string>, credential?: ChildCredential | null): NodeJS.ProcessEnv;
2327
+ //#endregion
2109
2328
  //#region src/stream-mapper.d.ts
2110
2329
  /**
2111
2330
  * Claude Code `--output-format stream-json` → Alfe chat-activity mapper.
@@ -2196,6 +2415,12 @@ interface EndEmission {
2196
2415
  durationMs?: number;
2197
2416
  /** Error reason (present when `isError`). Goes under `payload.message` (C3). */
2198
2417
  errorMessage?: string;
2418
+ /**
2419
+ * True when Claude refused the credential (assistant `error:
2420
+ * "authentication_failed"`, a 401/403 `api_error_status`, or a known auth
2421
+ * message). Present only on error turns.
2422
+ */
2423
+ authFailed?: true;
2199
2424
  }
2200
2425
  type MapperEmission = ActivityEmission | TextEmission | EndEmission;
2201
2426
  interface PendingTool {
@@ -2215,6 +2440,8 @@ interface MapperState {
2215
2440
  pendingTools: Map<string, PendingTool>;
2216
2441
  /** Whether the terminal `result` event has been seen. */
2217
2442
  ended: boolean;
2443
+ /** Set by an assistant `error: "authentication_failed"` frame. */
2444
+ authFailed: boolean;
2218
2445
  }
2219
2446
  declare function createMapperState(): MapperState;
2220
2447
  /**
@@ -2264,6 +2491,26 @@ declare const WARM_STREAMING_SUPPORTED = false;
2264
2491
  * shell. Callers that want a wider set pass `allowedTools` explicitly.
2265
2492
  */
2266
2493
  declare const DEFAULT_ALLOWED_TOOLS: readonly ["Read", "Grep", "Glob", "LS", "Edit", "Write", "WebFetch", "WebSearch", "Task", "TodoWrite"];
2494
+ /**
2495
+ * Managed VMs (`ALFE_MANAGED=1|true`) add `Bash` to the allow-list. The VM is
2496
+ * the agent's own machine (no user data beside the agent's workspace), and
2497
+ * shell access is what makes a managed coding agent useful. Still
2498
+ * `--permission-mode dontAsk` (never bypass), and the managed VM runs as root:
2499
+ * claude refuses only `--dangerously-skip-permissions` under root, not
2500
+ * `dontAsk`. `Edit`/`Write` are already in the default set; `MultiEdit` is not
2501
+ * a tool in the pinned claude (2.1.x), so it is not listed.
2502
+ */
2503
+ declare const MANAGED_ALLOWED_TOOLS: readonly ["Read", "Grep", "Glob", "LS", "Edit", "Write", "WebFetch", "WebSearch", "Task", "TodoWrite", "Bash"];
2504
+ /**
2505
+ * Deny rules layered on the managed allow-list (`--disallowedTools`, which
2506
+ * wins over `--allowedTools`). They keep the agent from re-running Google
2507
+ * Workspace CLI auth and from reading its token directory with the Read tool.
2508
+ * They are NOT a security boundary: with Bash allowed, `cat`, `cp`, `env` and
2509
+ * friends can still read anything the root user can. Verified on claude
2510
+ * 2.1.284: a `Bash(<prefix>:*)` rule denies the matching command under
2511
+ * `dontAsk` while other Bash commands run.
2512
+ */
2513
+ declare const MANAGED_DISALLOWED_TOOLS: readonly ["Bash(gws auth)", "Bash(gws auth:*)", "Read(~/.config/gws-*/**)"];
2267
2514
  declare const CLAUDE_PERMISSION_MODES: readonly ["acceptEdits", "auto", "manual", "dontAsk", "plan"];
2268
2515
  type ClaudePermissionMode = (typeof CLAUDE_PERMISSION_MODES)[number];
2269
2516
  /**
@@ -2286,6 +2533,8 @@ interface DriverTurnResult {
2286
2533
  errorMessage?: string;
2287
2534
  /** True if the turn was aborted via `abort()` (SIGINT). */
2288
2535
  aborted: boolean;
2536
+ /** Present (true) when the turn failed because Claude refused the credential. */
2537
+ authFailed?: true;
2289
2538
  }
2290
2539
  interface RunTurnParams {
2291
2540
  /** The user prompt for this turn. */
@@ -2331,12 +2580,21 @@ declare class SessionDriver {
2331
2580
  private abortCurrent;
2332
2581
  private readonly bin;
2333
2582
  private readonly baseEnv;
2583
+ private readonly credential;
2584
+ private readonly managed;
2334
2585
  private readonly log;
2335
2586
  /** Injected spawn — tests replace this with a fake child. */
2336
2587
  private readonly spawnImpl;
2337
2588
  constructor(opts?: {
2338
2589
  claudeBin?: string;
2339
2590
  baseEnv?: NodeJS.ProcessEnv;
2591
+ /**
2592
+ * The agent's Claude credential, injected into the child env after the
2593
+ * scrub. Null/absent → the machine's own `claude` login.
2594
+ */
2595
+ credential?: ChildCredential | null;
2596
+ /** Managed VM: Bash allowed + deny rules (see MANAGED_ALLOWED_TOOLS). */
2597
+ managed?: boolean;
2340
2598
  logger?: Logger;
2341
2599
  /** Test seam: inject a fake `spawn`. */
2342
2600
  __spawn?: typeof spawn;
@@ -2357,6 +2615,89 @@ declare class SessionDriver {
2357
2615
  private runPerTurn;
2358
2616
  }
2359
2617
  //#endregion
2618
+ //#region src/credential.d.ts
2619
+ interface HostCredential extends ChildCredential {
2620
+ /** Non-secret revision; travels with status reports as the fence. */
2621
+ revision: string;
2622
+ }
2623
+ interface LoadCredentialOptions {
2624
+ /** Aborted on host stop; the loop then resolves `null`. */
2625
+ signal: AbortSignal;
2626
+ log: Logger;
2627
+ baseDelayMs?: number;
2628
+ maxDelayMs?: number;
2629
+ /** Jitter source in [0,1]; the delay is `cap * (0.5 + random/2)`. */
2630
+ random?: () => number;
2631
+ sleep?: (ms: number, signal: AbortSignal) => Promise<void>;
2632
+ }
2633
+ /**
2634
+ * Fetch until it answers. `{ credential: null }` = nothing stored (a real
2635
+ * answer); `null` = aborted before any answer.
2636
+ */
2637
+ declare function loadCredentialWithRetry(fetchCredential: () => Promise<HostCredential | null>, opts: LoadCredentialOptions): Promise<{
2638
+ credential: HostCredential | null;
2639
+ } | null>;
2640
+ declare function isAuthFailureText(text: string): boolean;
2641
+ /** HTTP statuses on a `result` event that mean "credential refused". */
2642
+
2643
+ type ProbeOutcome = {
2644
+ result: 'ok';
2645
+ } | {
2646
+ result: 'invalid';
2647
+ } | {
2648
+ result: 'unknown';
2649
+ reason: string;
2650
+ };
2651
+ declare const AUTH_PROBE_TIMEOUT_MS = 60000;
2652
+ /** argv for the probe. No tools, no session file, no MCP, no settings files. */
2653
+ declare function buildProbeArgs(): string[];
2654
+ declare function parseProbeOutput(stdout: string, stderr: string, exitCode: number | null): ProbeOutcome;
2655
+ interface AuthProbeOptions {
2656
+ credential: ChildCredential | null;
2657
+ baseEnv: NodeJS.ProcessEnv;
2658
+ /** Any existing directory; the probe reads and writes nothing there. */
2659
+ cwd: string;
2660
+ claudeBin?: string;
2661
+ timeoutMs?: number;
2662
+ spawnImpl?: typeof spawn;
2663
+ /** Host stop: the child is SIGKILLed and the probe settles `unknown`. */
2664
+ signal?: AbortSignal;
2665
+ }
2666
+ /**
2667
+ * One minimal real turn against Anthropic with the agent's credential (or,
2668
+ * when none is stored, the machine's own login). Never throws.
2669
+ */
2670
+ declare function runAuthProbe(opts: AuthProbeOptions): Promise<ProbeOutcome>;
2671
+ interface CredentialStatusReport {
2672
+ status: 'ok' | 'missing' | 'invalid';
2673
+ /** Fixed, host-authored text only. Never `claude` output. */
2674
+ detail?: string;
2675
+ revision?: string;
2676
+ }
2677
+ /** An unchanged status is re-sent at most this often (a liveness signal). */
2678
+ declare const STATUS_REPORT_INTERVAL_MS: number;
2679
+ /**
2680
+ * Sends health reports on change, or when the same report is older than
2681
+ * {@link STATUS_REPORT_INTERVAL_MS}. Sends are serialized; a failed send is
2682
+ * not remembered, so the next signal retries. Never throws.
2683
+ */
2684
+ declare class CredentialStatusReporter {
2685
+ private readonly send;
2686
+ private lastKey;
2687
+ private lastSentAt;
2688
+ private chain;
2689
+ private readonly now;
2690
+ private readonly log;
2691
+ constructor(send: (report: CredentialStatusReport) => Promise<{
2692
+ accepted: boolean;
2693
+ }>, opts: {
2694
+ log: Logger;
2695
+ now?: () => number;
2696
+ });
2697
+ report(report: CredentialStatusReport): Promise<void>;
2698
+ private deliver;
2699
+ }
2700
+ //#endregion
2360
2701
  //#region src/host.d.ts
2361
2702
  interface ClaudeCodeHostConfig {
2362
2703
  /** Chat service WS URL (e.g. wss://chat.dev.alfe.ai/ws). */
@@ -2378,8 +2719,20 @@ interface ClaudeCodeHostConfig {
2378
2719
  * NEVER provider-qualified (`anthropic/…`) — that's an OpenClaw-only shape.
2379
2720
  */
2380
2721
  model?: string;
2722
+ /**
2723
+ * Managed VM (`ALFE_MANAGED=1|true`, set by the daemon). Enables Bash, and a
2724
+ * missing Claude credential is a hard "missing" state (no local login to
2725
+ * fall back to). Self-hosted when false/absent.
2726
+ */
2727
+ managed?: boolean;
2381
2728
  logger?: Logger;
2382
2729
  }
2730
+ declare const CREDENTIAL_INVALID_MESSAGE = "This agent's Claude credential is invalid or expired. Update it in agent settings.";
2731
+ declare const LOCAL_LOGIN_FAILED_MESSAGE = "Claude Code is not signed in on this machine. Run `claude` and log in, or add a Claude credential in agent settings.";
2732
+ declare const CREDENTIAL_MISSING_MESSAGE = "This agent has no Claude credential. Add a Claude subscription token or an Anthropic API key in agent settings.";
2733
+ declare const CREDENTIAL_UNAVAILABLE_MESSAGE = "This agent's Claude credential couldn't be loaded yet. Try again in a moment.";
2734
+ /** How long a turn waits for the start-up credential fetch before failing. */
2735
+
2383
2736
  declare class ClaudeCodeHost {
2384
2737
  private readonly chat;
2385
2738
  /**
@@ -2403,14 +2756,69 @@ declare class ClaudeCodeHost {
2403
2756
  private claudeSessionSave;
2404
2757
  private stopping;
2405
2758
  private stopPromise;
2759
+ /**
2760
+ * Resolves once the first fetch has answered. Null until `start()` (the
2761
+ * direct handleRequest test seam runs on the local login, no wait).
2762
+ */
2763
+ private credentialReady;
2764
+ private resolveCredentialReady;
2765
+ /** Turns wait for the first fetch only until this time (start + wait). */
2766
+ private credentialDeadline;
2767
+ private credentialLoaded;
2768
+ /** Turns waiting for the credential, before queue admission (chat.abort). */
2769
+ private readonly waitingTurns;
2770
+ private readonly loaderRestartMs;
2771
+ /** In memory only. Never logged, never persisted. */
2772
+ private credential;
2773
+ private readonly credentialAbort;
2774
+ private readonly reporter;
2775
+ private readonly probe;
2776
+ private readonly credentialWaitMs;
2777
+ private readonly credentialRetry;
2406
2778
  constructor(cfg: ClaudeCodeHostConfig, deps?: {
2407
- /** Per-turn driver factory (test seam). Called once per turn. */
2408
- makeDriver?: () => SessionDriver;
2779
+ /**
2780
+ * Per-turn driver factory (test seam). Called once per turn with the
2781
+ * agent's credential (null → the machine's own `claude` login).
2782
+ */
2783
+ makeDriver?: (credential: ChildCredential | null) => SessionDriver;
2409
2784
  chat?: ChatServiceClient;
2410
2785
  api?: AgentApiClient;
2786
+ /** Start-up auth check (test seam; default: a minimal `claude -p`). */
2787
+ probe?: (credential: ChildCredential | null, signal: AbortSignal) => Promise<ProbeOutcome>;
2788
+ credentialWaitMs?: number;
2789
+ /** Delay before restarting a credential loader that threw (default 5 s). */
2790
+ loaderRestartMs?: number;
2791
+ credentialRetry?: {
2792
+ baseDelayMs?: number;
2793
+ maxDelayMs?: number;
2794
+ };
2411
2795
  });
2412
2796
  /** Start the chat WS. Non-blocking — the client reconnects internally. */
2413
2797
  start(): Promise<void>;
2798
+ /**
2799
+ * Run the fetch + check, restarting it after an unexpected throw so a bug
2800
+ * can never leave the host without a credential until the next restart.
2801
+ */
2802
+ private runCredentialLoader;
2803
+ private loadCredential;
2804
+ private childCredential;
2805
+ /** Start-up health check: one minimal real call, reported to the cloud. */
2806
+ private checkCredential;
2807
+ /** Report the credential (or the local login) as working or refused. */
2808
+ private reportAuth;
2809
+ /**
2810
+ * Wait for the first credential answer BEFORE queue admission, so a waiting
2811
+ * turn holds no slot. Bounded by the start-up deadline: once it has passed,
2812
+ * later turns stop waiting until the load completes. Cancelled by
2813
+ * `chat.abort` or host stop through `cancellation`.
2814
+ */
2815
+ private awaitCredential;
2816
+ /**
2817
+ * Instant credential gate inside the turn (no waiting: `handleAgent` already
2818
+ * waited). Returns a user-facing failure message, or null when the turn may
2819
+ * run. A host driven without `start()` runs on the local login.
2820
+ */
2821
+ private credentialGateMessage;
2414
2822
  /** Stop admission, abort owned children, and give active turns time to settle. */
2415
2823
  stop(): Promise<void>;
2416
2824
  /**
@@ -2421,6 +2829,27 @@ declare class ClaudeCodeHost {
2421
2829
  private handleAgent;
2422
2830
  private deliverAgentOutcome;
2423
2831
  private runAgentTurn;
2832
+ /**
2833
+ * Send every message the memory service has not accepted yet. Indices are
2834
+ * the messages' positions in the session file, so they advance across turns
2835
+ * and survive host restarts; the per-conversation `ingestEpoch` tells the
2836
+ * service when that index space restarted. Ingest under the BARE session
2837
+ * id: the same key the session-store filename and the memory backfill use,
2838
+ * so there is one memory lineage.
2839
+ *
2840
+ * Cursor rules:
2841
+ * - success: advance through the batch. If the server trimmed the batch
2842
+ * (`messageCount` < sent) it kept the NEWEST messages and its watermark
2843
+ * moved past the dropped head, so a resend would be filtered anyway;
2844
+ * advance and warn. The byte bounds keep this path unreachable normally.
2845
+ * - non-retryable 4xx (400/413/422): skip only the oldest message, so one
2846
+ * poison message cannot wedge the conversation.
2847
+ * - anything else (429, 5xx, network): leave the cursor; the next turn
2848
+ * resends.
2849
+ */
2850
+ private ingestPendingMemory;
2851
+ /** Regenerate CLAUDE.md from the workspace persona files. Never fatal. */
2852
+ private syncPersona;
2424
2853
  /**
2425
2854
  * Run a turn; on a `--resume` failure (stale/missing claude session id) start
2426
2855
  * fresh and prepend the last-N stored messages so the model keeps context.
@@ -2441,45 +2870,6 @@ declare class ClaudeCodeHost {
2441
2870
  /** Strip the `agent:{agentId}:` prefix from a full server session key. */
2442
2871
  declare function stripAgentPrefix(sessionKey: string): string;
2443
2872
  //#endregion
2444
- //#region src/child-env.d.ts
2445
- /**
2446
- * Child-process environment construction for spawned `claude` CLI processes.
2447
- *
2448
- * TWO non-negotiable invariants (Phase 2 plan, audit B2 + the hard
2449
- * constraints):
2450
- *
2451
- * 1. **Scrub `ANTHROPIC_API_KEY` / `ANTHROPIC_BASE_URL` /
2452
- * `ANTHROPIC_AUTH_TOKEN`.** A self-hosted Mac very often has these set (for
2453
- * other tooling). If ANY of them reaches `claude`, the CLI silently switches
2454
- * from the user's Claude subscription (OAuth) to API-key/proxy auth — which
2455
- * is exactly what this whole runtime exists to avoid (no Agent SDK, no
2456
- * proxy, subscription only). The break is SILENT: the turn "works" but bills
2457
- * the wrong plane. So we strip them unconditionally from the child env.
2458
- *
2459
- * 2. **Set `ALFE_CLAUDE_BRIDGE=1`.** The Phase-1 CLI hooks
2460
- * (`alfe claude hook ...` in `packages/cli/src/commands/claude.ts`) check
2461
- * `process.env.ALFE_CLAUDE_BRIDGE` FIRST and no-op when set, so the host —
2462
- * not the interactive-session hooks — owns memory recall/capture for bridged
2463
- * turns. Omitting this double-captures every message. The env var NAME is a
2464
- * cross-phase contract; do not rename without updating the CLI.
2465
- */
2466
- /** Anthropic env vars that force API-key / proxy auth and MUST be scrubbed. */
2467
- declare const SCRUBBED_ANTHROPIC_ENV_VARS: readonly ["ANTHROPIC_API_KEY", "ANTHROPIC_BASE_URL", "ANTHROPIC_AUTH_TOKEN"];
2468
- /** Cross-phase contract marker (see header + packages/cli/src/commands/claude.ts). */
2469
- declare const BRIDGE_ENV_VAR = "ALFE_CLAUDE_BRIDGE";
2470
- /**
2471
- * Build the env handed to a spawned `claude` child. Pure — takes the base env
2472
- * explicitly so it unit-tests without touching `process.env`.
2473
- *
2474
- * - Every scrubbed Anthropic var is DELETED (not blanked) so `claude`'s
2475
- * env-detection treats it as absent.
2476
- * - `ALFE_CLAUDE_BRIDGE=1` is set.
2477
- * - Any caller-supplied `extra` entries are layered last (e.g. a per-turn
2478
- * marker) but can NEVER re-introduce a scrubbed var — the scrub runs after
2479
- * the merge as the final gate.
2480
- */
2481
- declare function buildChildEnv(base: NodeJS.ProcessEnv, extra?: Record<string, string>): NodeJS.ProcessEnv;
2482
- //#endregion
2483
2873
  //#region src/session/session-store.d.ts
2484
2874
  type StoredMessageComponent = {
2485
2875
  type: 'link_button';
@@ -2559,7 +2949,25 @@ interface SessionData {
2559
2949
  * pre-persistence files.
2560
2950
  */
2561
2951
  activity?: ChatActivityRecord[];
2952
+ /**
2953
+ * Alfe memory ingest cursor. Message indices are POSITIONS in `messages`
2954
+ * (append-only, never trimmed), so they are stable across host restarts and
2955
+ * match the index space the memory backfill uses for these files.
2956
+ * `ingestEpoch` is stamped once per conversation lineage (when this file is
2957
+ * created) and tells the memory service the index space restarted, e.g.
2958
+ * after the 30-day cleanup deleted the file and the conversation resumed.
2959
+ * Absent on files written before the cursor existed.
2960
+ */
2961
+ memory?: SessionMemoryCursor;
2962
+ }
2963
+ interface SessionMemoryCursor {
2964
+ /** Per-conversation-lineage epoch sent as `ingestEpoch`. */
2965
+ ingestEpoch: number;
2966
+ /** Highest message index the memory service has accepted (-1 = none). */
2967
+ lastIngestedIndex: number;
2562
2968
  }
2969
+ /** One message as sent to `memoryIngest`. */
2970
+
2563
2971
  interface SessionSummary {
2564
2972
  sessionId: string;
2565
2973
  agentId: string;
@@ -2574,7 +2982,20 @@ interface SessionSummary {
2574
2982
  declare function getSession(sessionId: string): Promise<SessionData | null>;
2575
2983
  declare function saveSession(session: SessionData): Promise<void>;
2576
2984
  declare function createSession(sessionId: string, agentId: string, channel: string, tenantId?: string, userId?: string): Promise<SessionData>;
2577
- declare function addMessage(sessionId: string, role: 'user' | 'assistant', content: string, senderId?: string, senderName?: string, components?: StoredMessageComponent[], attachments?: StoredAttachment[]): Promise<void>;
2985
+ /**
2986
+ * Append a message. Returns its index (position in `messages`), the stable
2987
+ * memory-ingest index, or -1 when the session does not exist.
2988
+ */
2989
+ declare function addMessage(sessionId: string, role: 'user' | 'assistant', content: string, senderId?: string, senderName?: string, components?: StoredMessageComponent[], attachments?: StoredAttachment[]): Promise<number>;
2990
+ /**
2991
+ * Memory ingest bounds, in serialised UTF-8 bytes. The ingest endpoint
2992
+ * forwards the whole body as ONE SQS message and trims the OLDEST messages
2993
+ * past 250,000 bytes (`services/memory/api-agents/ingest/fit-batch.ts`); a
2994
+ * trimmed head is lost for good because the consumer's watermark jumps past
2995
+ * it. Staying well under that limit means the server never trims. Bytes, not
2996
+ * characters: one CJK character is three UTF-8 bytes.
2997
+ */
2998
+
2578
2999
  /**
2579
3000
  * Append a turn's terminalized activity records. Called ONCE per turn (from the
2580
3001
  * dispatch `finally`) — never per-delta.
@@ -2594,4 +3015,4 @@ declare function listSessions(filters?: {
2594
3015
  */
2595
3016
  declare function resolveHostConfig(): ClaudeCodeHostConfig;
2596
3017
  //#endregion
2597
- export { type ActivityEmission, BRIDGE_ENV_VAR, CLAUDE_PERMISSION_MODES, type ChatActivityRecord, type ChatMessage, ClaudeCodeHost, type ClaudeCodeHostConfig, type ClaudePermissionMode, 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, TERMINATION_GRACE_MS, 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 };
3018
+ export { AUTH_PROBE_TIMEOUT_MS, type ActivityEmission, BRIDGE_ENV_VAR, CLAUDE_PERMISSION_MODES, CREDENTIAL_ENV_VAR, CREDENTIAL_INVALID_MESSAGE, CREDENTIAL_MISSING_MESSAGE, CREDENTIAL_UNAVAILABLE_MESSAGE, type ChatActivityRecord, type ChatMessage, type ChildCredential, ClaudeCodeHost, type ClaudeCodeHostConfig, type ClaudePermissionMode, type CredentialStatusReport, CredentialStatusReporter, DEFAULT_ALLOWED_TOOLS, DEFAULT_PERMISSION_MODE, type DriverTurnResult, type EndEmission, type HostCredential, LOCAL_LOGIN_FAILED_MESSAGE, MANAGED_ALLOWED_TOOLS, MANAGED_DISALLOWED_TOOLS, type MapperEmission, type MapperState, type ProbeOutcome, type RunTurnParams, SCRUBBED_ANTHROPIC_ENV_VARS, STATUS_REPORT_INTERVAL_MS, type SessionData, SessionDriver, type SessionSummary, TERMINATION_GRACE_MS, TURN_TIMEOUT_MS, type TextEmission, type ThinkingActivity, type ToolActivity, WARM_STREAMING_SUPPORTED, addMessage, appendActivity, buildChildEnv, buildProbeArgs, collectTurnActivityRecords, createMapperState, createSession, getSession, isAuthFailureText, listSessions, loadCredentialWithRetry, mapStreamEvent, mapStreamEvents, parseProbeOutput, resolveHostConfig, runAuthProbe, saveSession, stripAgentPrefix };