@alfe.ai/gateway 0.8.3 → 0.9.1

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.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as installService, c as uninstallService, f as SOCKET_PATH, i as checkExistingDaemon, n as queryDaemonHealth, r as startDaemon, s as stopExistingDaemon, t as formatHealthReport } from "../health.js";
2
+ import { a as installService, d as uninstallService, h as SOCKET_PATH, i as checkExistingDaemon, l as stopExistingDaemon, n as queryDaemonHealth, r as startDaemon, t as formatHealthReport } from "../health.js";
3
3
  import { t as LOG_FILE } from "../logger.js";
4
4
  import { spawn } from "node:child_process";
5
5
  //#region bin/gateway.ts
package/dist/health.js CHANGED
@@ -76,6 +76,26 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
76
76
  */
77
77
  const PINNED_OPENCLAW_VERSION = "2026.6.11";
78
78
  //#endregion
79
+ //#region src/claude-code-host-version.ts
80
+ /**
81
+ * The `@alfe.ai/claude-code-host` version Alfe pins the claude-code runtime to —
82
+ * the single source of truth for its install paths:
83
+ * - `@alfe.ai/cli` `installClaudeCodeHost` (fresh `alfe setup`)
84
+ * - the daemon `runtime.update` default (this package)
85
+ *
86
+ * Pinning (rather than floating `npm install -g @alfe.ai/claude-code-host` →
87
+ * `latest`) is deliberate: the host is the claude-code runtime child on every
88
+ * self-hosted claude-code agent, so a bad `latest` publish would auto-reach
89
+ * every new `alfe setup` with no CLI gate. Bump deliberately after validating a
90
+ * new host, then cut a CLI release so the pin travels with the CLI version. The
91
+ * dashboard still surfaces npm-`latest` as an explicit opt-in upgrade.
92
+ *
93
+ * NOTE: claude-code is self-hosted-only (no managed Docker image), so — unlike
94
+ * `PINNED_OPENCLAW_VERSION` — there is no `services/compute/Dockerfile` mirror to
95
+ * keep in sync.
96
+ */
97
+ const PINNED_CLAUDE_CODE_HOST_VERSION = "0.1.3";
98
+ //#endregion
79
99
  //#region ../../packages-internal/ids/dist/prefixes.js
80
100
  const ID_PREFIXES = {
81
101
  agent: "agt",
@@ -325,465 +345,72 @@ var AlfeApiClient = class {
325
345
  error: "Session expired",
326
346
  status: 401
327
347
  }
328
- };
329
- }
330
- const body = await res.json();
331
- if (!res.ok) return {
332
- ok: false,
333
- result: {
334
- ok: false,
335
- error: body.message || `API error: ${String(res.status)}`,
336
- status: res.status
337
- }
338
- };
339
- return {
340
- ok: true,
341
- res,
342
- body
343
- };
344
- } catch (err) {
345
- return {
346
- ok: false,
347
- result: {
348
- ok: false,
349
- error: err instanceof Error ? err.message : "Network error"
350
- }
351
- };
352
- }
353
- }
354
- /**
355
- * Make an authenticated request to an Alfe API endpoint.
356
- * Unwraps the @auriclabs/api-core `{ data, timestamp, requestId }` envelope.
357
- */
358
- async request(path, options) {
359
- const result = await this._fetch(path, options);
360
- if (!result.ok) return result.result;
361
- return {
362
- ok: true,
363
- data: result.body.data
364
- };
365
- }
366
- /**
367
- * Make a request to a PUBLIC endpoint that does not require authentication.
368
- * Skips both the Authorization header injection AND the onAuthFailure
369
- * callback. Use for endpoints like /auth/device-code that the CLI hits
370
- * before it has a token.
371
- */
372
- async publicRequest(path, options) {
373
- const result = await this._fetch(path, options, true);
374
- if (!result.ok) return result.result;
375
- return {
376
- ok: true,
377
- data: result.body.data
378
- };
379
- }
380
- /**
381
- * Make an authenticated request that returns the body directly (no envelope unwrap).
382
- * Use for APIs that don't use the @auriclabs/api-core response format (e.g. gateway).
383
- */
384
- async rawRequest(path, options) {
385
- const result = await this._fetch(path, options);
386
- if (!result.ok) return result.result;
387
- return {
388
- ok: true,
389
- data: result.body
390
- };
391
- }
392
- getApiBaseUrl() {
393
- return this.apiBaseUrl;
394
- }
395
- };
396
- //#endregion
397
- //#region ../../packages-internal/api-client/dist/services/auth.js
398
- var AuthService = class {
399
- client;
400
- constructor(client) {
401
- this.client = client;
402
- }
403
- prefix = "/auth";
404
- validate(token) {
405
- return this.client.request(`${this.prefix}/validate`, {
406
- method: "POST",
407
- body: JSON.stringify({ token })
408
- });
409
- }
410
- listTokens() {
411
- return this.client.request(`${this.prefix}/tokens`);
412
- }
413
- createToken(data) {
414
- return this.client.request(`${this.prefix}/tokens`, {
415
- method: "POST",
416
- body: JSON.stringify(data)
417
- });
418
- }
419
- deleteToken(tokenId) {
420
- return this.client.request(`${this.prefix}/tokens/${tokenId}`, { method: "DELETE" });
421
- }
422
- /**
423
- * Start a device-code flow. Called by the CLI when the user runs
424
- * `alfe login` and picks the browser path. The returned `device_code`
425
- * is the CLI's bearer secret for polling /auth/device-token; the
426
- * `user_code` is what the user types/sees on the dashboard.
427
- *
428
- * Uses `publicRequest` because the CLI has no token yet — the standard
429
- * `request` path would short-circuit with a synthetic 401.
430
- */
431
- startDeviceCode(input) {
432
- return this.client.publicRequest(`${this.prefix}/device-code`, {
433
- method: "POST",
434
- body: JSON.stringify(input)
435
- });
436
- }
437
- /**
438
- * Poll for the minted API key. Public endpoint. Returns:
439
- * 200 → { api_key, tenantId, tokenExpiresAt } (success — write to config)
440
- * 428 → still pending (caller should sleep `interval` and retry)
441
- * 429 → polling too fast; caller should back off
442
- * 410 → device-code expired; user must run `alfe login` again
443
- * 400 → already redeemed (security: do not retry)
444
- *
445
- * Surface the HTTP-status code via the standard ApiResult error shape
446
- * so callers can branch.
447
- */
448
- pollDeviceToken(deviceCode) {
449
- return this.client.publicRequest(`${this.prefix}/device-token`, {
450
- method: "POST",
451
- body: JSON.stringify({ device_code: deviceCode })
452
- });
453
- }
454
- /** Dashboard-side. Fetch the device fingerprint for an approval card. */
455
- lookupDeviceCode(userCode) {
456
- return this.client.request(`${this.prefix}/device-code/lookup`, {
457
- method: "POST",
458
- body: JSON.stringify({ user_code: userCode })
459
- });
460
- }
461
- /** Dashboard-side. Approve the pending device-code; CLI's next poll gets the key. */
462
- approveDeviceCode(input) {
463
- return this.client.request(`${this.prefix}/device-code/approve`, {
464
- method: "POST",
465
- body: JSON.stringify({
466
- user_code: input.userCode,
467
- expiresIn: input.expiresIn
468
- })
469
- });
470
- }
471
- getOnboardingStatus() {
472
- return this.client.request(`${this.prefix}/onboarding/status`);
473
- }
474
- completeOnboarding(input) {
475
- return this.client.request(`${this.prefix}/onboarding/complete`, {
476
- method: "POST",
477
- body: JSON.stringify(input)
478
- });
479
- }
480
- getAccessStatus() {
481
- return this.client.request(`${this.prefix}/access/status`);
482
- }
483
- redeemAccessCode(code) {
484
- return this.client.request(`${this.prefix}/access/redeem`, {
485
- method: "POST",
486
- body: JSON.stringify({ code })
487
- });
488
- }
489
- /**
490
- * Fetch the caller's current permission list as enforced by the gateway.
491
- * UI permission providers should call this on mount and refetch on
492
- * Clerk org switch to stay in sync with sub-tenant permission changes.
493
- */
494
- getMyPermissions() {
495
- return this.client.request(`${this.prefix}/me/permissions`);
496
- }
497
- /**
498
- * Preview what deleting the caller's own account will do. Drives the Danger
499
- * Zone copy so the user sees an honest, kind-appropriate warning before the
500
- * irreversible action (a last-admin cascade wipes the whole workspace).
501
- */
502
- getDeletionPreview() {
503
- return this.client.request(`${this.prefix}/me/deletion-preview`);
504
- }
505
- /**
506
- * Permanently delete the caller's own account. Returns `{ status: "deleting" }`
507
- * and tears the account down asynchronously — the Clerk user is deleted shortly
508
- * after, so the caller's session dies. On success, sign out and route the user
509
- * to a terminal "account is being deleted" state; do NOT retry.
510
- *
511
- * Pass `confirmKind` (the accountKind the user actually confirmed, from the
512
- * deletion preview): if membership changed between preview and execute, the
513
- * server answers 409 `account_kind_changed` instead of cascading a scope the
514
- * user never agreed to.
515
- */
516
- deleteAccount(confirmKind) {
517
- return this.client.request(`${this.prefix}/me`, {
518
- method: "DELETE",
519
- ...confirmKind ? { body: JSON.stringify({ confirmKind }) } : {}
520
- });
521
- }
522
- getSubscriptionStatus() {
523
- return this.client.request(`${this.prefix}/subscription`);
524
- }
525
- /**
526
- * Mint a short-lived, single-use Clerk sign-in token for the calling user so
527
- * a native client can hand its logged-in session to the web dashboard
528
- * (`${dashboard}/handoff?__clerk_ticket=<token>`). Server mints only for the
529
- * authenticated user — never a caller-supplied id.
530
- */
531
- createHandoffToken() {
532
- return this.client.request(`${this.prefix}/handoff-token`, { method: "POST" });
533
- }
534
- applyForStartup(input) {
535
- return this.client.request(`${this.prefix}/subscription/apply-startup`, {
536
- method: "POST",
537
- body: JSON.stringify(input)
538
- });
539
- }
540
- /**
541
- * Accept an approved Startup Program invite. Call after the applicant has a
542
- * card on file (confirmed or freshly added) — this mints the Professional
543
- * grant subscription. Idempotent: a repeat call on an already-accepted
544
- * application returns `{ status: "accepted" }` without re-charging.
545
- */
546
- acceptStartup() {
547
- return this.client.request(`${this.prefix}/subscription/accept-startup`, { method: "POST" });
548
- }
549
- createCheckoutSession(input) {
550
- return this.client.request(`${this.prefix}/subscription/checkout`, {
551
- method: "POST",
552
- body: JSON.stringify(input)
553
- });
554
- }
555
- /**
556
- * Change a personal (individual-tier) subscriber's platform plan in place on
557
- * their existing Stripe subscription — no second Checkout, no duplicate sub,
558
- * no double charge. Upgrades prorate immediately; downgrades apply with no
559
- * immediate charge/refund. Returns the updated subscription.
560
- */
561
- changePlatformPlan(input) {
562
- return this.client.request(`${this.prefix}/subscription/change`, {
563
- method: "POST",
564
- body: JSON.stringify(input)
565
- });
566
- }
567
- /**
568
- * Preview the proration cost for changing a personal (individual-tier)
569
- * subscriber's platform plan — read-only companion to `changePlatformPlan`.
570
- * Nothing is charged.
571
- */
572
- previewPlatformPlanChange(input) {
573
- return this.client.request(`${this.prefix}/subscription/change/preview`, {
574
- method: "POST",
575
- body: JSON.stringify(input)
576
- });
577
- }
578
- updateSeats(seats) {
579
- return this.client.request(`${this.prefix}/subscription/seats`, {
580
- method: "POST",
581
- body: JSON.stringify({ seats })
582
- });
583
- }
584
- previewSeatChange(seats) {
585
- return this.client.request(`${this.prefix}/subscription/seats/preview`, {
586
- method: "POST",
587
- body: JSON.stringify({ seats })
588
- });
589
- }
590
- listOrgMembers() {
591
- return this.client.request(`${this.prefix}/org/members`);
592
- }
593
- patchOrgMember(userId, body) {
594
- return this.client.request(`${this.prefix}/org/members/${encodeURIComponent(userId)}`, {
595
- method: "PATCH",
596
- body: JSON.stringify(body)
597
- });
598
- }
599
- removeOrgMember(userId) {
600
- return this.client.request(`${this.prefix}/org/members/${encodeURIComponent(userId)}`, { method: "DELETE" });
601
- }
602
- listOrgInvitations() {
603
- return this.client.request(`${this.prefix}/org/invitations`);
604
- }
605
- createOrgInvitation(input) {
606
- return this.client.request(`${this.prefix}/org/invitations`, {
607
- method: "POST",
608
- body: JSON.stringify(input)
609
- });
610
- }
611
- revokeOrgInvitation(invitationId) {
612
- return this.client.request(`${this.prefix}/org/invitations/${encodeURIComponent(invitationId)}`, { method: "DELETE" });
613
- }
614
- listOrgDomains() {
615
- return this.client.request(`${this.prefix}/org/domains`);
616
- }
617
- createOrgDomain(input) {
618
- return this.client.request(`${this.prefix}/org/domains`, {
619
- method: "POST",
620
- body: JSON.stringify(input)
621
- });
622
- }
623
- deleteOrgDomain(domainId) {
624
- return this.client.request(`${this.prefix}/org/domains/${encodeURIComponent(domainId)}`, { method: "DELETE" });
625
- }
626
- updateOrgSettings(input) {
627
- return this.client.request(`${this.prefix}/org/settings`, {
628
- method: "PATCH",
629
- body: JSON.stringify(input)
630
- });
631
- }
632
- };
633
- //#endregion
634
- //#region ../../packages-internal/api-client/dist/services/integrations.js
635
- var IntegrationsService = class {
636
- client;
637
- constructor(client) {
638
- this.client = client;
639
- }
640
- listScopedInstalls(scope, scopeId) {
641
- const params = new URLSearchParams({
642
- scope,
643
- scopeId
644
- });
645
- return this.client.request(`/integrations/scoped?${params}`);
646
- }
647
- installScoped(data) {
648
- return this.client.request("/integrations/scoped", {
649
- method: "POST",
650
- body: JSON.stringify(data)
651
- });
652
- }
653
- getScopedInstall(integrationId, scope, scopeId) {
654
- const params = new URLSearchParams({
655
- scope,
656
- scopeId
657
- });
658
- return this.client.request(`/integrations/scoped/${encodeURIComponent(integrationId)}?${params}`);
659
- }
660
- updateScopedInstall(integrationId, data) {
661
- return this.client.request(`/integrations/scoped/${encodeURIComponent(integrationId)}`, {
662
- method: "PATCH",
663
- body: JSON.stringify(data)
664
- });
665
- }
666
- removeScopedInstall(integrationId, scope, scopeId) {
667
- const params = new URLSearchParams({
668
- scope,
669
- scopeId
670
- });
671
- return this.client.request(`/integrations/scoped/${encodeURIComponent(integrationId)}?${params}`, { method: "DELETE" });
672
- }
673
- /**
674
- * Reinstall (re-apply latest) a scope-level install. Agent scope is rejected
675
- * by the route — use {@link reinstallIntegration} for agent-scoped reinstall.
676
- */
677
- reinstallScoped(integrationId, scope, scopeId) {
678
- return this.client.request(`/integrations/scoped/${encodeURIComponent(integrationId)}/reinstall`, {
679
- method: "POST",
680
- body: JSON.stringify({
681
- scope,
682
- scopeId
683
- })
684
- });
685
- }
686
- listIntegrations(agentId, options) {
687
- const params = new URLSearchParams();
688
- if (options?.includeInherited) params.set("includeInherited", "true");
689
- if (options?.effective) params.set("effective", "true");
690
- const qs = params.toString();
691
- return this.client.request(`/integrations/agents/${agentId}${qs ? `?${qs}` : ""}`);
692
- }
693
- installIntegration(agentId, data) {
694
- return this.client.request(`/integrations/agents/${agentId}`, {
695
- method: "POST",
696
- body: JSON.stringify(data)
697
- });
698
- }
699
- getIntegrationConfig(agentId, integrationId) {
700
- return this.client.request(`/integrations/agents/${agentId}/${integrationId}/config`);
701
- }
702
- updateIntegration(agentId, integrationId, data) {
703
- return this.client.request(`/integrations/agents/${agentId}/${integrationId}`, {
704
- method: "PATCH",
705
- body: JSON.stringify(data)
706
- });
707
- }
708
- removeIntegration(agentId, integrationId) {
709
- return this.client.request(`/integrations/agents/${agentId}/${integrationId}`, { method: "DELETE" });
710
- }
711
- reinstallIntegration(agentId, integrationId) {
712
- return this.client.request(`/integrations/agents/${agentId}/${integrationId}/reinstall`, { method: "POST" });
713
- }
714
- /**
715
- * Upgrade an integration to the registry's latest version via the fast,
716
- * diff-based daemon path (bumps `version` without `reinstallRequestedAt`).
717
- * The agent stays online — no destructive teardown. Distinct from
718
- * {@link reinstallIntegration}, which is the destructive repair path.
719
- */
720
- upgradeIntegration(agentId, integrationId) {
721
- return this.client.request(`/integrations/agents/${agentId}/${integrationId}/upgrade`, { method: "POST" });
722
- }
723
- getRegistry() {
724
- return this.client.request("/integrations/registry");
725
- }
726
- triggerSync(agentId) {
727
- return this.client.request("/integrations/sync/trigger", {
728
- method: "POST",
729
- body: JSON.stringify({ agentId })
730
- });
731
- }
732
- getDiscordGuildChannels(guildId) {
733
- return this.client.request(`/discord/guilds/${encodeURIComponent(guildId)}/channels`);
734
- }
735
- listMobileNumbers() {
736
- return this.client.request("/mobile/numbers");
737
- }
738
- searchMobileNumbers(country, query) {
739
- const params = new URLSearchParams();
740
- if (country) params.set("country", country);
741
- if (query) params.set("query", query);
742
- return this.client.request(`/mobile/numbers/search?${params}`);
743
- }
744
- assignMobileNumber(agentId, phoneNumber, countryCode) {
745
- return this.client.request("/mobile/numbers/assign", {
746
- method: "POST",
747
- body: JSON.stringify({
748
- agentId,
749
- phoneNumber,
750
- countryCode
751
- })
752
- });
753
- }
754
- releaseMobileNumber(agentId) {
755
- return this.client.request("/mobile/numbers/release", {
756
- method: "POST",
757
- body: JSON.stringify({ agentId })
758
- });
759
- }
760
- getMobileNumber(agentId) {
761
- return this.client.request(`/mobile/numbers?agentId=${encodeURIComponent(agentId)}`);
762
- }
763
- disconnectGoogle(agentId, email) {
764
- const query = email ? `?email=${encodeURIComponent(email)}` : "";
765
- return this.client.request(`/google/agents/${encodeURIComponent(agentId)}/account${query}`, { method: "DELETE" });
348
+ };
349
+ }
350
+ const body = await res.json();
351
+ if (!res.ok) return {
352
+ ok: false,
353
+ result: {
354
+ ok: false,
355
+ error: body.message || `API error: ${String(res.status)}`,
356
+ status: res.status
357
+ }
358
+ };
359
+ return {
360
+ ok: true,
361
+ res,
362
+ body
363
+ };
364
+ } catch (err) {
365
+ return {
366
+ ok: false,
367
+ result: {
368
+ ok: false,
369
+ error: err instanceof Error ? err.message : "Network error"
370
+ }
371
+ };
372
+ }
766
373
  }
767
- getAtlassianSites(agentId) {
768
- return this.client.request(`/atlassian/agents/${encodeURIComponent(agentId)}/sites`);
374
+ /**
375
+ * Make an authenticated request to an Alfe API endpoint.
376
+ * Unwraps the @auriclabs/api-core `{ data, timestamp, requestId }` envelope.
377
+ */
378
+ async request(path, options) {
379
+ const result = await this._fetch(path, options);
380
+ if (!result.ok) return result.result;
381
+ return {
382
+ ok: true,
383
+ data: result.body.data
384
+ };
769
385
  }
770
- selectAtlassianSite(agentId, cloudId) {
771
- return this.client.request(`/atlassian/agents/${encodeURIComponent(agentId)}/sites`, {
772
- method: "PUT",
773
- body: JSON.stringify({ cloudId })
774
- });
386
+ /**
387
+ * Make a request to a PUBLIC endpoint that does not require authentication.
388
+ * Skips both the Authorization header injection AND the onAuthFailure
389
+ * callback. Use for endpoints like /auth/device-code that the CLI hits
390
+ * before it has a token.
391
+ */
392
+ async publicRequest(path, options) {
393
+ const result = await this._fetch(path, options, true);
394
+ if (!result.ok) return result.result;
395
+ return {
396
+ ok: true,
397
+ data: result.body.data
398
+ };
775
399
  }
776
- listSlackChannels(agentId) {
777
- return this.client.request(`/slack/agents/${encodeURIComponent(agentId)}/channels`);
400
+ /**
401
+ * Make an authenticated request that returns the body directly (no envelope unwrap).
402
+ * Use for APIs that don't use the @auriclabs/api-core response format (e.g. gateway).
403
+ */
404
+ async rawRequest(path, options) {
405
+ const result = await this._fetch(path, options);
406
+ if (!result.ok) return result.result;
407
+ return {
408
+ ok: true,
409
+ data: result.body
410
+ };
778
411
  }
779
- sendSlackMessage(agentId, channel, text) {
780
- return this.client.request(`/slack/agents/${encodeURIComponent(agentId)}/send`, {
781
- method: "POST",
782
- body: JSON.stringify({
783
- channel,
784
- text
785
- })
786
- });
412
+ getApiBaseUrl() {
413
+ return this.apiBaseUrl;
787
414
  }
788
415
  };
789
416
  //#endregion
@@ -4515,6 +4142,7 @@ enumValues({
4515
4142
  Other: "other"
4516
4143
  });
4517
4144
  enumValues({
4145
+ IndividualPayg: "individual_payg",
4518
4146
  IndividualLite: "individual_lite",
4519
4147
  IndividualNormal: "individual_normal",
4520
4148
  IndividualPro: "individual_pro",
@@ -4878,6 +4506,7 @@ const NotificationType = {
4878
4506
  SubscriptionCreated: "subscription.created",
4879
4507
  SubscriptionCancelled: "subscription.cancelled",
4880
4508
  StartupGrantEnded: "startup.grant_ended",
4509
+ GrantEnded: "subscription.grant_ended",
4881
4510
  SubscriptionPastDue: "subscription.past_due",
4882
4511
  BalanceThresholdWarning: "balance.threshold.warning",
4883
4512
  PlatformTierPriceIncrease: "platform.tier_price_increase",
@@ -4898,11 +4527,404 @@ const NotificationChannel = {
4898
4527
  Push: "push",
4899
4528
  Sms: "sms"
4900
4529
  };
4901
- const RecipientStrategy = {
4902
- TenantAdmins: "tenant_admins",
4903
- SpecificUser: "specific_user"
4530
+ const RecipientStrategy = {
4531
+ TenantAdmins: "tenant_admins",
4532
+ SpecificUser: "specific_user"
4533
+ };
4534
+ NotificationType.PaymentSucceeded, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.PaymentFailed, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationChannel.Sms, NotificationType.TopUpCompleted, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.AutoRechargeCompleted, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationType.AutoRechargeFailed, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationChannel.Sms, NotificationType.SubscriptionCreated, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationType.SubscriptionCancelled, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.StartupGrantEnded, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.GrantEnded, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.SubscriptionPastDue, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationChannel.Sms, NotificationType.BalanceThresholdWarning, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.PlatformTierPriceIncrease, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationType.AgentCreated, NotificationCategory.Agents, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.AgentProvisionFailed, NotificationCategory.Agents, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.AgentBillingSuspended, NotificationCategory.Agents, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.AgentDisconnectedExtended, NotificationCategory.Agents, RecipientStrategy.TenantAdmins, NotificationChannel.Push, NotificationType.BrowserTakeoverRequested, NotificationCategory.Agents, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.InviteCreated, NotificationCategory.System, RecipientStrategy.SpecificUser, NotificationChannel.Email, NotificationType.OrgClaimed, NotificationCategory.System, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.TeamMemberAdded, NotificationCategory.Team, RecipientStrategy.SpecificUser, NotificationChannel.Email, NotificationChannel.Push, NotificationType.ProjectMemberAdded, NotificationCategory.Team, RecipientStrategy.SpecificUser, NotificationChannel.Email, NotificationChannel.Push, NotificationType.IntegrationInstalled, NotificationCategory.Integrations, RecipientStrategy.TenantAdmins, NotificationChannel.Push, NotificationType.IntegrationRemoved, NotificationCategory.Integrations, RecipientStrategy.TenantAdmins, NotificationChannel.Push;
4535
+ //#endregion
4536
+ //#region ../../packages-internal/api-client/dist/services/auth.js
4537
+ var AuthService = class {
4538
+ client;
4539
+ constructor(client) {
4540
+ this.client = client;
4541
+ }
4542
+ prefix = "/auth";
4543
+ validate(token) {
4544
+ return this.client.request(`${this.prefix}/validate`, {
4545
+ method: "POST",
4546
+ body: JSON.stringify({ token })
4547
+ });
4548
+ }
4549
+ listTokens() {
4550
+ return this.client.request(`${this.prefix}/tokens`);
4551
+ }
4552
+ createToken(data) {
4553
+ return this.client.request(`${this.prefix}/tokens`, {
4554
+ method: "POST",
4555
+ body: JSON.stringify(data)
4556
+ });
4557
+ }
4558
+ deleteToken(tokenId) {
4559
+ return this.client.request(`${this.prefix}/tokens/${tokenId}`, { method: "DELETE" });
4560
+ }
4561
+ /**
4562
+ * Start a device-code flow. Called by the CLI when the user runs
4563
+ * `alfe login` and picks the browser path. The returned `device_code`
4564
+ * is the CLI's bearer secret for polling /auth/device-token; the
4565
+ * `user_code` is what the user types/sees on the dashboard.
4566
+ *
4567
+ * Uses `publicRequest` because the CLI has no token yet — the standard
4568
+ * `request` path would short-circuit with a synthetic 401.
4569
+ */
4570
+ startDeviceCode(input) {
4571
+ return this.client.publicRequest(`${this.prefix}/device-code`, {
4572
+ method: "POST",
4573
+ body: JSON.stringify(input)
4574
+ });
4575
+ }
4576
+ /**
4577
+ * Poll for the minted API key. Public endpoint. Returns:
4578
+ * 200 → { api_key, tenantId, tokenExpiresAt } (success — write to config)
4579
+ * 428 → still pending (caller should sleep `interval` and retry)
4580
+ * 429 → polling too fast; caller should back off
4581
+ * 410 → device-code expired; user must run `alfe login` again
4582
+ * 400 → already redeemed (security: do not retry)
4583
+ *
4584
+ * Surface the HTTP-status code via the standard ApiResult error shape
4585
+ * so callers can branch.
4586
+ */
4587
+ pollDeviceToken(deviceCode) {
4588
+ return this.client.publicRequest(`${this.prefix}/device-token`, {
4589
+ method: "POST",
4590
+ body: JSON.stringify({ device_code: deviceCode })
4591
+ });
4592
+ }
4593
+ /** Dashboard-side. Fetch the device fingerprint for an approval card. */
4594
+ lookupDeviceCode(userCode) {
4595
+ return this.client.request(`${this.prefix}/device-code/lookup`, {
4596
+ method: "POST",
4597
+ body: JSON.stringify({ user_code: userCode })
4598
+ });
4599
+ }
4600
+ /** Dashboard-side. Approve the pending device-code; CLI's next poll gets the key. */
4601
+ approveDeviceCode(input) {
4602
+ return this.client.request(`${this.prefix}/device-code/approve`, {
4603
+ method: "POST",
4604
+ body: JSON.stringify({
4605
+ user_code: input.userCode,
4606
+ expiresIn: input.expiresIn
4607
+ })
4608
+ });
4609
+ }
4610
+ getOnboardingStatus() {
4611
+ return this.client.request(`${this.prefix}/onboarding/status`);
4612
+ }
4613
+ completeOnboarding(input) {
4614
+ return this.client.request(`${this.prefix}/onboarding/complete`, {
4615
+ method: "POST",
4616
+ body: JSON.stringify(input)
4617
+ });
4618
+ }
4619
+ getAccessStatus() {
4620
+ return this.client.request(`${this.prefix}/access/status`);
4621
+ }
4622
+ redeemAccessCode(code) {
4623
+ return this.client.request(`${this.prefix}/access/redeem`, {
4624
+ method: "POST",
4625
+ body: JSON.stringify({ code })
4626
+ });
4627
+ }
4628
+ /**
4629
+ * Fetch the caller's current permission list as enforced by the gateway.
4630
+ * UI permission providers should call this on mount and refetch on
4631
+ * Clerk org switch to stay in sync with sub-tenant permission changes.
4632
+ */
4633
+ getMyPermissions() {
4634
+ return this.client.request(`${this.prefix}/me/permissions`);
4635
+ }
4636
+ /**
4637
+ * Preview what deleting the caller's own account will do. Drives the Danger
4638
+ * Zone copy so the user sees an honest, kind-appropriate warning before the
4639
+ * irreversible action (a last-admin cascade wipes the whole workspace).
4640
+ */
4641
+ getDeletionPreview() {
4642
+ return this.client.request(`${this.prefix}/me/deletion-preview`);
4643
+ }
4644
+ /**
4645
+ * Permanently delete the caller's own account. Returns `{ status: "deleting" }`
4646
+ * and tears the account down asynchronously — the Clerk user is deleted shortly
4647
+ * after, so the caller's session dies. On success, sign out and route the user
4648
+ * to a terminal "account is being deleted" state; do NOT retry.
4649
+ *
4650
+ * Pass `confirmKind` (the accountKind the user actually confirmed, from the
4651
+ * deletion preview): if membership changed between preview and execute, the
4652
+ * server answers 409 `account_kind_changed` instead of cascading a scope the
4653
+ * user never agreed to.
4654
+ */
4655
+ deleteAccount(confirmKind) {
4656
+ return this.client.request(`${this.prefix}/me`, {
4657
+ method: "DELETE",
4658
+ ...confirmKind ? { body: JSON.stringify({ confirmKind }) } : {}
4659
+ });
4660
+ }
4661
+ getSubscriptionStatus() {
4662
+ return this.client.request(`${this.prefix}/subscription`);
4663
+ }
4664
+ /**
4665
+ * Mint a short-lived, single-use Clerk sign-in token for the calling user so
4666
+ * a native client can hand its logged-in session to the web dashboard
4667
+ * (`${dashboard}/handoff?__clerk_ticket=<token>`). Server mints only for the
4668
+ * authenticated user — never a caller-supplied id.
4669
+ */
4670
+ createHandoffToken() {
4671
+ return this.client.request(`${this.prefix}/handoff-token`, { method: "POST" });
4672
+ }
4673
+ applyForStartup(input) {
4674
+ return this.client.request(`${this.prefix}/subscription/apply-startup`, {
4675
+ method: "POST",
4676
+ body: JSON.stringify(input)
4677
+ });
4678
+ }
4679
+ /**
4680
+ * Accept an approved Startup Program invite. Call after the applicant has a
4681
+ * card on file (confirmed or freshly added) — this mints the Professional
4682
+ * grant subscription. Idempotent: a repeat call on an already-accepted
4683
+ * application returns `{ status: "accepted" }` without re-charging.
4684
+ */
4685
+ acceptStartup() {
4686
+ return this.client.request(`${this.prefix}/subscription/accept-startup`, { method: "POST" });
4687
+ }
4688
+ createCheckoutSession(input) {
4689
+ return this.client.request(`${this.prefix}/subscription/checkout`, {
4690
+ method: "POST",
4691
+ body: JSON.stringify(input)
4692
+ });
4693
+ }
4694
+ /**
4695
+ * Change a personal (individual-tier) subscriber's platform plan in place on
4696
+ * their existing Stripe subscription — no second Checkout, no duplicate sub,
4697
+ * no double charge. Upgrades prorate immediately; downgrades apply with no
4698
+ * immediate charge/refund. Returns the updated subscription.
4699
+ */
4700
+ changePlatformPlan(input) {
4701
+ return this.client.request(`${this.prefix}/subscription/change`, {
4702
+ method: "POST",
4703
+ body: JSON.stringify(input)
4704
+ });
4705
+ }
4706
+ /**
4707
+ * Preview the proration cost for changing a personal (individual-tier)
4708
+ * subscriber's platform plan — read-only companion to `changePlatformPlan`.
4709
+ * Nothing is charged.
4710
+ */
4711
+ previewPlatformPlanChange(input) {
4712
+ return this.client.request(`${this.prefix}/subscription/change/preview`, {
4713
+ method: "POST",
4714
+ body: JSON.stringify(input)
4715
+ });
4716
+ }
4717
+ updateSeats(seats) {
4718
+ return this.client.request(`${this.prefix}/subscription/seats`, {
4719
+ method: "POST",
4720
+ body: JSON.stringify({ seats })
4721
+ });
4722
+ }
4723
+ previewSeatChange(seats) {
4724
+ return this.client.request(`${this.prefix}/subscription/seats/preview`, {
4725
+ method: "POST",
4726
+ body: JSON.stringify({ seats })
4727
+ });
4728
+ }
4729
+ listOrgMembers() {
4730
+ return this.client.request(`${this.prefix}/org/members`);
4731
+ }
4732
+ patchOrgMember(userId, body) {
4733
+ return this.client.request(`${this.prefix}/org/members/${encodeURIComponent(userId)}`, {
4734
+ method: "PATCH",
4735
+ body: JSON.stringify(body)
4736
+ });
4737
+ }
4738
+ removeOrgMember(userId) {
4739
+ return this.client.request(`${this.prefix}/org/members/${encodeURIComponent(userId)}`, { method: "DELETE" });
4740
+ }
4741
+ listOrgInvitations() {
4742
+ return this.client.request(`${this.prefix}/org/invitations`);
4743
+ }
4744
+ createOrgInvitation(input) {
4745
+ return this.client.request(`${this.prefix}/org/invitations`, {
4746
+ method: "POST",
4747
+ body: JSON.stringify(input)
4748
+ });
4749
+ }
4750
+ revokeOrgInvitation(invitationId) {
4751
+ return this.client.request(`${this.prefix}/org/invitations/${encodeURIComponent(invitationId)}`, { method: "DELETE" });
4752
+ }
4753
+ listOrgDomains() {
4754
+ return this.client.request(`${this.prefix}/org/domains`);
4755
+ }
4756
+ createOrgDomain(input) {
4757
+ return this.client.request(`${this.prefix}/org/domains`, {
4758
+ method: "POST",
4759
+ body: JSON.stringify(input)
4760
+ });
4761
+ }
4762
+ deleteOrgDomain(domainId) {
4763
+ return this.client.request(`${this.prefix}/org/domains/${encodeURIComponent(domainId)}`, { method: "DELETE" });
4764
+ }
4765
+ updateOrgSettings(input) {
4766
+ return this.client.request(`${this.prefix}/org/settings`, {
4767
+ method: "PATCH",
4768
+ body: JSON.stringify(input)
4769
+ });
4770
+ }
4771
+ };
4772
+ //#endregion
4773
+ //#region ../../packages-internal/api-client/dist/services/integrations.js
4774
+ var IntegrationsService = class {
4775
+ client;
4776
+ constructor(client) {
4777
+ this.client = client;
4778
+ }
4779
+ listScopedInstalls(scope, scopeId) {
4780
+ const params = new URLSearchParams({
4781
+ scope,
4782
+ scopeId
4783
+ });
4784
+ return this.client.request(`/integrations/scoped?${params}`);
4785
+ }
4786
+ installScoped(data) {
4787
+ return this.client.request("/integrations/scoped", {
4788
+ method: "POST",
4789
+ body: JSON.stringify(data)
4790
+ });
4791
+ }
4792
+ getScopedInstall(integrationId, scope, scopeId) {
4793
+ const params = new URLSearchParams({
4794
+ scope,
4795
+ scopeId
4796
+ });
4797
+ return this.client.request(`/integrations/scoped/${encodeURIComponent(integrationId)}?${params}`);
4798
+ }
4799
+ updateScopedInstall(integrationId, data) {
4800
+ return this.client.request(`/integrations/scoped/${encodeURIComponent(integrationId)}`, {
4801
+ method: "PATCH",
4802
+ body: JSON.stringify(data)
4803
+ });
4804
+ }
4805
+ removeScopedInstall(integrationId, scope, scopeId) {
4806
+ const params = new URLSearchParams({
4807
+ scope,
4808
+ scopeId
4809
+ });
4810
+ return this.client.request(`/integrations/scoped/${encodeURIComponent(integrationId)}?${params}`, { method: "DELETE" });
4811
+ }
4812
+ /**
4813
+ * Reinstall (re-apply latest) a scope-level install. Agent scope is rejected
4814
+ * by the route — use {@link reinstallIntegration} for agent-scoped reinstall.
4815
+ */
4816
+ reinstallScoped(integrationId, scope, scopeId) {
4817
+ return this.client.request(`/integrations/scoped/${encodeURIComponent(integrationId)}/reinstall`, {
4818
+ method: "POST",
4819
+ body: JSON.stringify({
4820
+ scope,
4821
+ scopeId
4822
+ })
4823
+ });
4824
+ }
4825
+ listIntegrations(agentId, options) {
4826
+ const params = new URLSearchParams();
4827
+ if (options?.includeInherited) params.set("includeInherited", "true");
4828
+ if (options?.effective) params.set("effective", "true");
4829
+ const qs = params.toString();
4830
+ return this.client.request(`/integrations/agents/${agentId}${qs ? `?${qs}` : ""}`);
4831
+ }
4832
+ installIntegration(agentId, data) {
4833
+ return this.client.request(`/integrations/agents/${agentId}`, {
4834
+ method: "POST",
4835
+ body: JSON.stringify(data)
4836
+ });
4837
+ }
4838
+ getIntegrationConfig(agentId, integrationId) {
4839
+ return this.client.request(`/integrations/agents/${agentId}/${integrationId}/config`);
4840
+ }
4841
+ updateIntegration(agentId, integrationId, data) {
4842
+ return this.client.request(`/integrations/agents/${agentId}/${integrationId}`, {
4843
+ method: "PATCH",
4844
+ body: JSON.stringify(data)
4845
+ });
4846
+ }
4847
+ removeIntegration(agentId, integrationId) {
4848
+ return this.client.request(`/integrations/agents/${agentId}/${integrationId}`, { method: "DELETE" });
4849
+ }
4850
+ reinstallIntegration(agentId, integrationId) {
4851
+ return this.client.request(`/integrations/agents/${agentId}/${integrationId}/reinstall`, { method: "POST" });
4852
+ }
4853
+ /**
4854
+ * Upgrade an integration to the registry's latest version via the fast,
4855
+ * diff-based daemon path (bumps `version` without `reinstallRequestedAt`).
4856
+ * The agent stays online — no destructive teardown. Distinct from
4857
+ * {@link reinstallIntegration}, which is the destructive repair path.
4858
+ */
4859
+ upgradeIntegration(agentId, integrationId) {
4860
+ return this.client.request(`/integrations/agents/${agentId}/${integrationId}/upgrade`, { method: "POST" });
4861
+ }
4862
+ getRegistry() {
4863
+ return this.client.request("/integrations/registry");
4864
+ }
4865
+ triggerSync(agentId) {
4866
+ return this.client.request("/integrations/sync/trigger", {
4867
+ method: "POST",
4868
+ body: JSON.stringify({ agentId })
4869
+ });
4870
+ }
4871
+ getDiscordGuildChannels(guildId) {
4872
+ return this.client.request(`/discord/guilds/${encodeURIComponent(guildId)}/channels`);
4873
+ }
4874
+ listMobileNumbers() {
4875
+ return this.client.request("/mobile/numbers");
4876
+ }
4877
+ searchMobileNumbers(country, query) {
4878
+ const params = new URLSearchParams();
4879
+ if (country) params.set("country", country);
4880
+ if (query) params.set("query", query);
4881
+ return this.client.request(`/mobile/numbers/search?${params}`);
4882
+ }
4883
+ assignMobileNumber(agentId, phoneNumber, countryCode) {
4884
+ return this.client.request("/mobile/numbers/assign", {
4885
+ method: "POST",
4886
+ body: JSON.stringify({
4887
+ agentId,
4888
+ phoneNumber,
4889
+ countryCode
4890
+ })
4891
+ });
4892
+ }
4893
+ releaseMobileNumber(agentId) {
4894
+ return this.client.request("/mobile/numbers/release", {
4895
+ method: "POST",
4896
+ body: JSON.stringify({ agentId })
4897
+ });
4898
+ }
4899
+ getMobileNumber(agentId) {
4900
+ return this.client.request(`/mobile/numbers?agentId=${encodeURIComponent(agentId)}`);
4901
+ }
4902
+ disconnectGoogle(agentId, email) {
4903
+ const query = email ? `?email=${encodeURIComponent(email)}` : "";
4904
+ return this.client.request(`/google/agents/${encodeURIComponent(agentId)}/account${query}`, { method: "DELETE" });
4905
+ }
4906
+ getAtlassianSites(agentId) {
4907
+ return this.client.request(`/atlassian/agents/${encodeURIComponent(agentId)}/sites`);
4908
+ }
4909
+ selectAtlassianSite(agentId, cloudId) {
4910
+ return this.client.request(`/atlassian/agents/${encodeURIComponent(agentId)}/sites`, {
4911
+ method: "PUT",
4912
+ body: JSON.stringify({ cloudId })
4913
+ });
4914
+ }
4915
+ listSlackChannels(agentId) {
4916
+ return this.client.request(`/slack/agents/${encodeURIComponent(agentId)}/channels`);
4917
+ }
4918
+ sendSlackMessage(agentId, channel, text) {
4919
+ return this.client.request(`/slack/agents/${encodeURIComponent(agentId)}/send`, {
4920
+ method: "POST",
4921
+ body: JSON.stringify({
4922
+ channel,
4923
+ text
4924
+ })
4925
+ });
4926
+ }
4904
4927
  };
4905
- NotificationType.PaymentSucceeded, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.PaymentFailed, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationChannel.Sms, NotificationType.TopUpCompleted, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.AutoRechargeCompleted, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationType.AutoRechargeFailed, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationChannel.Sms, NotificationType.SubscriptionCreated, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationType.SubscriptionCancelled, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.StartupGrantEnded, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.SubscriptionPastDue, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationChannel.Sms, NotificationType.BalanceThresholdWarning, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.PlatformTierPriceIncrease, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationType.AgentCreated, NotificationCategory.Agents, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.AgentProvisionFailed, NotificationCategory.Agents, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.AgentBillingSuspended, NotificationCategory.Agents, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.AgentDisconnectedExtended, NotificationCategory.Agents, RecipientStrategy.TenantAdmins, NotificationChannel.Push, NotificationType.BrowserTakeoverRequested, NotificationCategory.Agents, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.InviteCreated, NotificationCategory.System, RecipientStrategy.SpecificUser, NotificationChannel.Email, NotificationType.OrgClaimed, NotificationCategory.System, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.TeamMemberAdded, NotificationCategory.Team, RecipientStrategy.SpecificUser, NotificationChannel.Email, NotificationChannel.Push, NotificationType.ProjectMemberAdded, NotificationCategory.Team, RecipientStrategy.SpecificUser, NotificationChannel.Email, NotificationChannel.Push, NotificationType.IntegrationInstalled, NotificationCategory.Integrations, RecipientStrategy.TenantAdmins, NotificationChannel.Push, NotificationType.IntegrationRemoved, NotificationCategory.Integrations, RecipientStrategy.TenantAdmins, NotificationChannel.Push;
4906
4928
  //#endregion
4907
4929
  //#region src/config.ts
4908
4930
  /**
@@ -5126,6 +5148,17 @@ async function loadDaemonConfig() {
5126
5148
  const gatewayWsUrl = alfeConfig.gateway_url ?? deriveGatewayWsUrl(apiEndpoint);
5127
5149
  const runtimes = await loadRuntimeConfigs();
5128
5150
  const runtime = alfeConfig.runtime ?? identity.runtime;
5151
+ let defaultModel;
5152
+ if (runtime === "claude-code") {
5153
+ let timer;
5154
+ const ws = await Promise.race([fetchAgentConfig(alfeConfig.api_key, apiEndpoint), new Promise((resolve) => {
5155
+ timer = setTimeout(() => {
5156
+ resolve(null);
5157
+ }, 5e3);
5158
+ })]);
5159
+ if (timer) clearTimeout(timer);
5160
+ defaultModel = ws?.defaultModel;
5161
+ }
5129
5162
  return {
5130
5163
  apiKey: alfeConfig.api_key,
5131
5164
  apiEndpoint,
@@ -5136,7 +5169,8 @@ async function loadDaemonConfig() {
5136
5169
  orgId: identity.orgId,
5137
5170
  runtime,
5138
5171
  runtimes,
5139
- autoStartRuntime: alfeConfig.auto_start_runtime ?? true
5172
+ autoStartRuntime: alfeConfig.auto_start_runtime ?? true,
5173
+ ...defaultModel ? { defaultModel } : {}
5140
5174
  };
5141
5175
  }
5142
5176
  /**
@@ -7073,12 +7107,25 @@ async function uninstallSystemd() {
7073
7107
  return `Uninstalled: ${unitPath}`;
7074
7108
  }
7075
7109
  /**
7076
- * Start the installed service via systemctl/launchctl.
7110
+ * Bootstrap (load) the launchd service from its plist if it isn't already
7111
+ * loaded. Already-loaded is an error we ignore. Needed so `start`/`restart`
7112
+ * work after `stopService()` boots the service OUT.
7113
+ */
7114
+ function ensureLaunchdLoaded(uid) {
7115
+ try {
7116
+ execSync(`launchctl bootstrap gui/${uid} ${getLaunchdPlistPath()}`, { stdio: "pipe" });
7117
+ } catch {}
7118
+ }
7119
+ /**
7120
+ * Start the installed service via systemctl/launchctl. Bootstraps the launchd
7121
+ * service first so this also works after `stopService()` booted it out.
7077
7122
  */
7078
7123
  function startService() {
7079
7124
  const platform = process.platform;
7080
7125
  if (platform === "darwin") {
7081
- execSync(`launchctl kickstart -k gui/${getLaunchdUid()}/${LAUNCHD_LABEL}`, { stdio: "pipe" });
7126
+ const uid = getLaunchdUid();
7127
+ ensureLaunchdLoaded(uid);
7128
+ execSync(`launchctl kickstart -k gui/${uid}/${LAUNCHD_LABEL}`, { stdio: "pipe" });
7082
7129
  logger$1.info("Started launchd service");
7083
7130
  return;
7084
7131
  }
@@ -7090,6 +7137,59 @@ function startService() {
7090
7137
  throw new Error(`Unsupported platform: ${platform}`);
7091
7138
  }
7092
7139
  /**
7140
+ * Restart the installed service via systemctl/launchctl. This is the
7141
+ * service-manager-native restart (`kickstart -k` / `systemctl restart`) — the
7142
+ * CLI uses it instead of killing the process inline, which would fight launchd
7143
+ * `KeepAlive` / systemd `Restart=always`.
7144
+ */
7145
+ function restartService() {
7146
+ const platform = process.platform;
7147
+ if (platform === "darwin") {
7148
+ const uid = getLaunchdUid();
7149
+ ensureLaunchdLoaded(uid);
7150
+ execSync(`launchctl kickstart -k gui/${uid}/${LAUNCHD_LABEL}`, { stdio: "pipe" });
7151
+ logger$1.info("Restarted launchd service");
7152
+ return;
7153
+ }
7154
+ if (platform === "linux") {
7155
+ execSync(`${isRootUser() ? "systemctl" : "systemctl --user"} restart ${SYSTEMD_SERVICE}`, { stdio: "pipe" });
7156
+ logger$1.info("Restarted systemd service");
7157
+ return;
7158
+ }
7159
+ throw new Error(`Unsupported platform: ${platform}`);
7160
+ }
7161
+ /**
7162
+ * Stop the installed service via systemctl/launchctl. On macOS this boots the
7163
+ * service OUT (unloads it) so launchd's `KeepAlive` does NOT respawn it;
7164
+ * `startService()` bootstraps it again. On Linux the unit stays enabled (starts
7165
+ * on next boot); `systemctl stop` just halts the current run.
7166
+ */
7167
+ function stopService() {
7168
+ const platform = process.platform;
7169
+ if (platform === "darwin") {
7170
+ execSync(`launchctl bootout gui/${getLaunchdUid()}/${LAUNCHD_LABEL}`, { stdio: "pipe" });
7171
+ logger$1.info("Stopped launchd service");
7172
+ return;
7173
+ }
7174
+ if (platform === "linux") {
7175
+ execSync(`${isRootUser() ? "systemctl" : "systemctl --user"} stop ${SYSTEMD_SERVICE}`, { stdio: "pipe" });
7176
+ logger$1.info("Stopped systemd service");
7177
+ return;
7178
+ }
7179
+ throw new Error(`Unsupported platform: ${platform}`);
7180
+ }
7181
+ /**
7182
+ * True when the gateway is installed as a system service (launchd plist /
7183
+ * systemd unit present). Lets the CLI restart/stop/start THROUGH the service
7184
+ * manager instead of driving the daemon inline via the PID file.
7185
+ */
7186
+ function isServiceInstalled() {
7187
+ const platform = process.platform;
7188
+ if (platform === "darwin") return existsSync(getLaunchdPlistPath());
7189
+ if (platform === "linux") return existsSync(isRootUser() ? getSystemdSystemServicePath() : getSystemdServicePath());
7190
+ return false;
7191
+ }
7192
+ /**
7093
7193
  * Write the current process PID to the PID file.
7094
7194
  */
7095
7195
  async function writePidFile() {
@@ -23913,7 +24013,7 @@ async function startDaemon() {
23913
24013
  runtimeProcess = new RuntimeProcess({
23914
24014
  runtime: config.runtime,
23915
24015
  workspace: runtimeCfg.workspace,
23916
- env: {}
24016
+ env: config.runtime === "claude-code" && config.defaultModel ? { ALFE_DEFAULT_MODEL: config.defaultModel } : {}
23917
24017
  });
23918
24018
  runtimeProcess.setTurnActivityProbe(turnActivityProbe);
23919
24019
  runtimeProcess.start();
@@ -24105,7 +24205,7 @@ async function executeCloudCommand(command) {
24105
24205
  };
24106
24206
  const payload = command.payload;
24107
24207
  const runtime = config.runtime;
24108
- const version = payload?.version ?? (runtime === "openclaw" ? "2026.6.11" : void 0);
24208
+ const version = payload?.version ?? (runtime === "openclaw" ? "2026.6.11" : runtime === "claude-code" ? "0.1.3" : void 0);
24109
24209
  upgradingRuntime = true;
24110
24210
  setTimeout(() => {
24111
24211
  (async () => {
@@ -24805,4 +24905,4 @@ function formatDuration(ms) {
24805
24905
  return `${String(Math.round(seconds / 3600))}h`;
24806
24906
  }
24807
24907
  //#endregion
24808
- export { installService as a, uninstallService as c, PID_PATH as d, SOCKET_PATH as f, PINNED_OPENCLAW_VERSION as g, resolveAgentIdentity as h, checkExistingDaemon as i, PROTOCOL_VERSION as l, loadDaemonConfig as m, queryDaemonHealth as n, startService as o, fetchAgentConfig as p, startDaemon as r, stopExistingDaemon as s, formatHealthReport as t, ALFE_DIR as u };
24908
+ export { loadDaemonConfig as _, installService as a, PINNED_OPENCLAW_VERSION as b, startService as c, uninstallService as d, PROTOCOL_VERSION as f, fetchAgentConfig as g, SOCKET_PATH as h, checkExistingDaemon as i, stopExistingDaemon as l, PID_PATH as m, queryDaemonHealth as n, isServiceInstalled as o, ALFE_DIR as p, startDaemon as r, restartService as s, formatHealthReport as t, stopService as u, resolveAgentIdentity as v, PINNED_CLAUDE_CODE_HOST_VERSION as y };
@@ -19,13 +19,19 @@ const execFileAsync = promisify(execFile);
19
19
  /**
20
20
  * Resolve the per-runtime upgrade command.
21
21
  *
22
- * - `openclaw`: `npm install -g openclaw@<version>` — version-pinned (unchanged).
23
- * - `hermes`: `hermes update --yes` Hermes self-updates from its own channel.
24
- * A pinned version does not map to a Hermes CLI flag, so the
25
- * `version` arg is intentionally ignored on this branch.
22
+ * - `openclaw`: `npm install -g openclaw@<version>` — version-pinned.
23
+ * - `claude-code`: `npm install -g @alfe.ai/claude-code-host@<version>` the
24
+ * runtime child is the host binary; mirrors openclaw. The
25
+ * caller defaults a missing version to the pin.
26
+ * - `hermes`: `hermes update --yes` — Hermes self-updates from its own
27
+ * channel. A pinned version does not map to a Hermes CLI flag,
28
+ * so the `version` arg is intentionally ignored on this branch.
26
29
  *
27
- * Returns `undefined` for an unknown runtime (or openclaw with no version),
28
- * which the caller treats as a failed upgrade (restart on the old version).
30
+ * Returns `undefined` for an unknown runtime (or openclaw/claude-code with no
31
+ * version), which the caller treats as a failed upgrade (restart on the old
32
+ * version). `upgradeRuntime` restarts the RuntimeProcess child regardless of
33
+ * runtime, so a new branch here is all that's needed to cycle onto the new
34
+ * version.
29
35
  */
30
36
  function resolveUpgradeCommand(runtime, version) {
31
37
  switch (runtime) {
@@ -39,6 +45,16 @@ function resolveUpgradeCommand(runtime, version) {
39
45
  `openclaw@${version}`
40
46
  ]
41
47
  };
48
+ case "claude-code":
49
+ if (!version) return void 0;
50
+ return {
51
+ command: "npm",
52
+ args: [
53
+ "install",
54
+ "-g",
55
+ `@alfe.ai/claude-code-host@${version}`
56
+ ]
57
+ };
42
58
  case "hermes": return {
43
59
  command: "hermes",
44
60
  args: ["update", "--yes"]
@@ -59,6 +59,26 @@ declare function startDaemon(): Promise<void>;
59
59
  */
60
60
  declare const PINNED_OPENCLAW_VERSION = "2026.6.11";
61
61
  //#endregion
62
+ //#region src/claude-code-host-version.d.ts
63
+ /**
64
+ * The `@alfe.ai/claude-code-host` version Alfe pins the claude-code runtime to —
65
+ * the single source of truth for its install paths:
66
+ * - `@alfe.ai/cli` `installClaudeCodeHost` (fresh `alfe setup`)
67
+ * - the daemon `runtime.update` default (this package)
68
+ *
69
+ * Pinning (rather than floating `npm install -g @alfe.ai/claude-code-host` →
70
+ * `latest`) is deliberate: the host is the claude-code runtime child on every
71
+ * self-hosted claude-code agent, so a bad `latest` publish would auto-reach
72
+ * every new `alfe setup` with no CLI gate. Bump deliberately after validating a
73
+ * new host, then cut a CLI release so the pin travels with the CLI version. The
74
+ * dashboard still surfaces npm-`latest` as an explicit opt-in upgrade.
75
+ *
76
+ * NOTE: claude-code is self-hosted-only (no managed Docker image), so — unlike
77
+ * `PINNED_OPENCLAW_VERSION` — there is no `services/compute/Dockerfile` mirror to
78
+ * keep in sync.
79
+ */
80
+ declare const PINNED_CLAUDE_CODE_HOST_VERSION = "0.1.3";
81
+ //#endregion
62
82
  //#region src/sentry.d.ts
63
83
  /**
64
84
  * Agent-side error reporting (Sentry) for the Alfe CLI + gateway daemon.
@@ -261,6 +281,13 @@ interface DaemonConfig {
261
281
  runtimes: Record<string, RuntimeConfig>;
262
282
  /** Whether to auto-start the agent runtime process (e.g. openclaw) */
263
283
  autoStartRuntime: boolean;
284
+ /**
285
+ * The agent's bare `defaultModel`, resolved for claude-code only (other
286
+ * runtimes receive their model via the desired-state reconcile, not here).
287
+ * The daemon injects it into the host env so `claude -p --model` uses the
288
+ * dashboard-selected model. Never provider-qualified.
289
+ */
290
+ defaultModel?: string;
264
291
  }
265
292
  interface AgentIdentity {
266
293
  agentId: string;
@@ -349,9 +376,30 @@ declare function installService(): Promise<string>;
349
376
  */
350
377
  declare function uninstallService(): Promise<string>;
351
378
  /**
352
- * Start the installed service via systemctl/launchctl.
379
+ * Start the installed service via systemctl/launchctl. Bootstraps the launchd
380
+ * service first so this also works after `stopService()` booted it out.
353
381
  */
354
382
  declare function startService(): void;
383
+ /**
384
+ * Restart the installed service via systemctl/launchctl. This is the
385
+ * service-manager-native restart (`kickstart -k` / `systemctl restart`) — the
386
+ * CLI uses it instead of killing the process inline, which would fight launchd
387
+ * `KeepAlive` / systemd `Restart=always`.
388
+ */
389
+ declare function restartService(): void;
390
+ /**
391
+ * Stop the installed service via systemctl/launchctl. On macOS this boots the
392
+ * service OUT (unloads it) so launchd's `KeepAlive` does NOT respawn it;
393
+ * `startService()` bootstraps it again. On Linux the unit stays enabled (starts
394
+ * on next boot); `systemctl stop` just halts the current run.
395
+ */
396
+ declare function stopService(): void;
397
+ /**
398
+ * True when the gateway is installed as a system service (launchd plist /
399
+ * systemd unit present). Lets the CLI restart/stop/start THROUGH the service
400
+ * manager instead of driving the daemon inline via the PID file.
401
+ */
402
+ declare function isServiceInstalled(): boolean;
355
403
  /**
356
404
  * Write the current process PID to the PID file.
357
405
  */
@@ -366,4 +414,4 @@ declare function checkExistingDaemon(): Promise<number | null>;
366
414
  */
367
415
  declare function stopExistingDaemon(): Promise<boolean>;
368
416
  //#endregion
369
- export { AGENT_DAEMON_SENTRY_DSN, AGENT_MCP_SENTRY_DSN, AGENT_RUNTIME_SENTRY_DSN, ALFE_DIR, type AgentIdentity, type AgentWorkspaceConfig, type DaemonConfig, type DaemonHealth, type IPCEvent, type IPCRequest, type IPCResponse, type InitAgentSentryOptions, PID_PATH, PINNED_OPENCLAW_VERSION, PROTOCOL_VERSION, type RuntimeOutputLine, SOCKET_PATH, type SentrySurface, captureCliFailure, captureFatal, captureIntegrationFailure, captureMcpFailure, captureRuntimeCrash, captureRuntimeErrorOutput, checkExistingDaemon, fetchAgentConfig, flushSentry, formatHealthReport, initAgentSentry, installService, loadDaemonConfig, logger, queryDaemonHealth, resolveAgentIdentity, setAgentContext, startDaemon, startService, stopExistingDaemon, uninstallService };
417
+ export { AGENT_DAEMON_SENTRY_DSN, AGENT_MCP_SENTRY_DSN, AGENT_RUNTIME_SENTRY_DSN, ALFE_DIR, type AgentIdentity, type AgentWorkspaceConfig, type DaemonConfig, type DaemonHealth, type IPCEvent, type IPCRequest, type IPCResponse, type InitAgentSentryOptions, PID_PATH, PINNED_CLAUDE_CODE_HOST_VERSION, PINNED_OPENCLAW_VERSION, PROTOCOL_VERSION, type RuntimeOutputLine, SOCKET_PATH, type SentrySurface, captureCliFailure, captureFatal, captureIntegrationFailure, captureMcpFailure, captureRuntimeCrash, captureRuntimeErrorOutput, checkExistingDaemon, fetchAgentConfig, flushSentry, formatHealthReport, initAgentSentry, installService, isServiceInstalled, loadDaemonConfig, logger, queryDaemonHealth, resolveAgentIdentity, restartService, setAgentContext, startDaemon, startService, stopExistingDaemon, stopService, uninstallService };
package/dist/src/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as installService, c as uninstallService, d as PID_PATH, f as SOCKET_PATH, g as PINNED_OPENCLAW_VERSION, h as resolveAgentIdentity, i as checkExistingDaemon, l as PROTOCOL_VERSION, m as loadDaemonConfig, n as queryDaemonHealth, o as startService, p as fetchAgentConfig, r as startDaemon, s as stopExistingDaemon, t as formatHealthReport, u as ALFE_DIR } from "../health.js";
1
+ import { _ as loadDaemonConfig, a as installService, b as PINNED_OPENCLAW_VERSION, c as startService, d as uninstallService, f as PROTOCOL_VERSION, g as fetchAgentConfig, h as SOCKET_PATH, i as checkExistingDaemon, l as stopExistingDaemon, m as PID_PATH, n as queryDaemonHealth, o as isServiceInstalled, p as ALFE_DIR, r as startDaemon, s as restartService, t as formatHealthReport, u as stopService, v as resolveAgentIdentity, y as PINNED_CLAUDE_CODE_HOST_VERSION } from "../health.js";
2
2
  import { n as logger } from "../logger.js";
3
3
  import { a as captureFatal, c as captureRuntimeCrash, d as initAgentSentry, f as setAgentContext, i as captureCliFailure, l as captureRuntimeErrorOutput, n as AGENT_MCP_SENTRY_DSN, o as captureIntegrationFailure, r as AGENT_RUNTIME_SENTRY_DSN, s as captureMcpFailure, t as AGENT_DAEMON_SENTRY_DSN, u as flushSentry } from "../sentry.js";
4
- export { AGENT_DAEMON_SENTRY_DSN, AGENT_MCP_SENTRY_DSN, AGENT_RUNTIME_SENTRY_DSN, ALFE_DIR, PID_PATH, PINNED_OPENCLAW_VERSION, PROTOCOL_VERSION, SOCKET_PATH, captureCliFailure, captureFatal, captureIntegrationFailure, captureMcpFailure, captureRuntimeCrash, captureRuntimeErrorOutput, checkExistingDaemon, fetchAgentConfig, flushSentry, formatHealthReport, initAgentSentry, installService, loadDaemonConfig, logger, queryDaemonHealth, resolveAgentIdentity, setAgentContext, startDaemon, startService, stopExistingDaemon, uninstallService };
4
+ export { AGENT_DAEMON_SENTRY_DSN, AGENT_MCP_SENTRY_DSN, AGENT_RUNTIME_SENTRY_DSN, ALFE_DIR, PID_PATH, PINNED_CLAUDE_CODE_HOST_VERSION, PINNED_OPENCLAW_VERSION, PROTOCOL_VERSION, SOCKET_PATH, captureCliFailure, captureFatal, captureIntegrationFailure, captureMcpFailure, captureRuntimeCrash, captureRuntimeErrorOutput, checkExistingDaemon, fetchAgentConfig, flushSentry, formatHealthReport, initAgentSentry, installService, isServiceInstalled, loadDaemonConfig, logger, queryDaemonHealth, resolveAgentIdentity, restartService, setAgentContext, startDaemon, startService, stopExistingDaemon, stopService, uninstallService };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alfe.ai/gateway",
3
- "version": "0.8.3",
3
+ "version": "0.9.1",
4
4
  "description": "Alfe local gateway daemon — persistent control plane for agent integrations",
5
5
  "type": "module",
6
6
  "bin": {
@@ -23,11 +23,11 @@
23
23
  "pino-roll": "^1.2.0",
24
24
  "smol-toml": ">=1.6.1",
25
25
  "ws": "^8.18.0",
26
- "@alfe.ai/agent-api-client": "^0.11.0",
26
+ "@alfe.ai/agent-api-client": "^0.11.1",
27
27
  "@alfe.ai/ai-proxy-local": "^0.0.13",
28
28
  "@alfe.ai/config": "^0.3.0",
29
- "@alfe.ai/integration-manifest": "^0.3.2",
30
- "@alfe.ai/integrations": "^0.5.2",
29
+ "@alfe.ai/integration-manifest": "^0.3.3",
30
+ "@alfe.ai/integrations": "^0.5.3",
31
31
  "@alfe.ai/mcp-bundler": "^0.3.2"
32
32
  },
33
33
  "license": "UNLICENSED",