@topolo/sdk 0.21.1 → 0.21.3

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.
@@ -29,6 +29,7 @@ __export(credential_store_exports, {
29
29
  configPath: () => configPath,
30
30
  contextKey: () => contextKey,
31
31
  createPlatformInstallationSecretBackend: () => createPlatformInstallationSecretBackend,
32
+ createProtectedActionSecretStore: () => createProtectedActionSecretStore,
32
33
  credentialSourceFromConfig: () => credentialSourceFromConfig,
33
34
  deleteExternalAgentInstallation: () => deleteExternalAgentInstallation,
34
35
  envVarPrefix: () => envVarPrefix,
@@ -55,7 +56,7 @@ module.exports = __toCommonJS(credential_store_exports);
55
56
  var import_promises2 = require("fs/promises");
56
57
  var import_node_fs2 = require("fs");
57
58
  var import_node_os2 = require("os");
58
- var import_node_path2 = require("path");
59
+ var import_node_path3 = require("path");
59
60
 
60
61
  // src/errors.ts
61
62
  var TopoloSdkError = class extends Error {
@@ -411,68 +412,12 @@ var TopoloOAuth = class {
411
412
  }
412
413
  };
413
414
 
414
- // src/environment.ts
415
- var TOPOLO_RUNTIME_ENVIRONMENTS = ["production", "staging", "development", "local"];
416
- var TOPOLO_ENVIRONMENT_SERVICE_URLS = {
417
- production: {
418
- auth: "https://auth.topolo.app",
419
- developers: "https://developers.topolo.app"
420
- },
421
- staging: {
422
- auth: "https://auth.stg.topolo.us",
423
- developers: "https://developers.stg.topolo.us"
424
- },
425
- development: {
426
- auth: "https://auth.topolo.dev",
427
- developers: "https://developers.topolo.dev"
428
- },
429
- local: {}
430
- };
431
- function hostnameHasSuffix(hostname, suffix) {
432
- return hostname === suffix || hostname.endsWith(`.${suffix}`);
433
- }
434
- function workersDevAccountMatches(hostname, account) {
435
- if (!hostname.endsWith(".workers.dev")) return false;
436
- const labels = hostname.slice(0, -".workers.dev".length).split(".");
437
- return labels.length >= 2 && labels.at(-1) === account;
438
- }
439
- function originMatchesEnvironment(url, environment) {
440
- const hostname = url.hostname.toLowerCase();
441
- if (environment === "local") {
442
- return hostname === "localhost" || hostname.endsWith(".localhost") || hostname === "127.0.0.1" || hostname === "[::1]";
443
- }
444
- if (url.protocol !== "https:") return false;
445
- if (environment === "production") {
446
- return hostnameHasSuffix(hostname, "topolo.app") || hostnameHasSuffix(hostname, "topolo.io") || hostname === "api.ol0.me" || workersDevAccountMatches(hostname, "topolo");
447
- }
448
- if (environment === "staging") {
449
- return hostnameHasSuffix(hostname, "stg.topolo.us") || workersDevAccountMatches(hostname, "topolo-staging");
450
- }
451
- return hostnameHasSuffix(hostname, "topolo.dev") || workersDevAccountMatches(hostname, "topolo-development");
452
- }
453
- function assertTopoloServiceUrlEnvironment(service, value, environment) {
454
- let url;
455
- try {
456
- url = new URL(value);
457
- } catch {
458
- throw new TopoloSdkError("invalid_service_url", `Invalid URL configured for Topolo service "${service}".`);
459
- }
460
- if (url.protocol !== "https:" && url.protocol !== "http:" || url.username || url.password || !originMatchesEnvironment(url, environment)) {
461
- throw new TopoloSdkError(
462
- "environment_mismatch",
463
- `Refusing Topolo service "${service}": ${url.origin} does not belong to ${environment}.`
464
- );
465
- }
466
- return url.origin;
467
- }
468
- function topoloServiceOrigins(serviceUrls, environment) {
469
- return Object.fromEntries(
470
- Object.entries(serviceUrls ?? {}).sort(([left], [right]) => left.localeCompare(right)).map(([service, value]) => [
471
- service,
472
- assertTopoloServiceUrlEnvironment(service, value, environment)
473
- ])
474
- );
475
- }
415
+ // src/node/action-secret-store.ts
416
+ var import_node_crypto = require("crypto");
417
+ var import_node_path2 = require("path");
418
+
419
+ // src/action-secrets.ts
420
+ var ACTION_SECRET_PREFIX = "topolo-secret:";
476
421
 
477
422
  // src/node/installation-store.ts
478
423
  var import_node_child_process = require("child_process");
@@ -481,6 +426,8 @@ var import_node_fs = require("fs");
481
426
  var import_node_os = require("os");
482
427
  var import_node_path = require("path");
483
428
  var KEYCHAIN_SERVICE = "topolo-external-agent-installation";
429
+ var MACOS_KEYCHAIN_CHUNK_SIZE = 96;
430
+ var MACOS_KEYCHAIN_CHUNK_PREFIX = "topolo-keychain-chunks:v1:";
484
431
  var MACOS_KEYCHAIN_WRITE_SCRIPT = [
485
432
  "log_user 0",
486
433
  "set timeout 10",
@@ -520,41 +467,75 @@ function createPlatformInstallationSecretBackend(options = {}) {
520
467
  const platform2 = options.platform ?? (0, import_node_os.platform)();
521
468
  const spawn = options.spawn ?? ((command, args, spawnOptions) => (0, import_node_child_process.spawnSync)(command, args, spawnOptions));
522
469
  const root = options.root ?? installationStorePath();
470
+ const service = options.service ?? KEYCHAIN_SERVICE;
523
471
  if (platform2 === "darwin") {
472
+ const write = (account, value) => runOrThrow(spawn("/usr/bin/expect", ["-c", MACOS_KEYCHAIN_WRITE_SCRIPT], {
473
+ input: `${value}
474
+ `,
475
+ encoding: "utf8",
476
+ env: {
477
+ ...process.env,
478
+ TOPOLO_KEYCHAIN_ACCOUNT: account,
479
+ TOPOLO_KEYCHAIN_SERVICE: service
480
+ }
481
+ }), "macOS Keychain write");
482
+ const read = (account) => {
483
+ const result = spawn("security", [
484
+ "find-generic-password",
485
+ "-a",
486
+ account,
487
+ "-s",
488
+ service,
489
+ "-w"
490
+ ], { encoding: "utf8" });
491
+ if (result.status === 44) return null;
492
+ return runOrThrow(result, "macOS Keychain read");
493
+ };
494
+ const remove = (account) => {
495
+ const result = spawn("security", [
496
+ "delete-generic-password",
497
+ "-a",
498
+ account,
499
+ "-s",
500
+ service
501
+ ], { encoding: "utf8" });
502
+ if (result.status !== 44) runOrThrow(result, "macOS Keychain delete");
503
+ };
504
+ const chunkAccount = (account, index) => `${account}:chunk:${index}`;
505
+ const chunkCount = (value) => {
506
+ if (!value?.startsWith(MACOS_KEYCHAIN_CHUNK_PREFIX)) return 0;
507
+ const count = Number(value.slice(MACOS_KEYCHAIN_CHUNK_PREFIX.length));
508
+ if (!Number.isSafeInteger(count) || count < 1) throw new Error("macOS Keychain chunk manifest is invalid.");
509
+ return count;
510
+ };
524
511
  return {
525
512
  async set(account, value) {
526
- runOrThrow(spawn("/usr/bin/expect", ["-c", MACOS_KEYCHAIN_WRITE_SCRIPT], {
527
- input: `${value}
528
- `,
529
- encoding: "utf8",
530
- env: {
531
- ...process.env,
532
- TOPOLO_KEYCHAIN_ACCOUNT: account,
533
- TOPOLO_KEYCHAIN_SERVICE: KEYCHAIN_SERVICE
534
- }
535
- }), "macOS Keychain write");
513
+ const previousCount = chunkCount(read(account));
514
+ const chunks = Array.from(
515
+ { length: Math.max(1, Math.ceil(value.length / MACOS_KEYCHAIN_CHUNK_SIZE)) },
516
+ (_, index) => value.slice(index * MACOS_KEYCHAIN_CHUNK_SIZE, (index + 1) * MACOS_KEYCHAIN_CHUNK_SIZE)
517
+ );
518
+ try {
519
+ chunks.forEach((chunk, index) => write(chunkAccount(account, index), chunk));
520
+ write(account, `${MACOS_KEYCHAIN_CHUNK_PREFIX}${chunks.length}`);
521
+ for (let index = chunks.length; index < previousCount; index += 1) remove(chunkAccount(account, index));
522
+ } catch (error) {
523
+ chunks.forEach((_, index) => remove(chunkAccount(account, index)));
524
+ throw error;
525
+ }
536
526
  },
537
527
  async get(account) {
538
- const result = spawn("security", [
539
- "find-generic-password",
540
- "-a",
541
- account,
542
- "-s",
543
- KEYCHAIN_SERVICE,
544
- "-w"
545
- ], { encoding: "utf8" });
546
- if (result.status === 44) return null;
547
- return runOrThrow(result, "macOS Keychain read");
528
+ const manifest = read(account);
529
+ const count = chunkCount(manifest);
530
+ if (!count) return manifest;
531
+ const chunks = Array.from({ length: count }, (_, index) => read(chunkAccount(account, index)));
532
+ if (chunks.some((chunk) => chunk === null)) throw new Error("macOS Keychain installation secret is incomplete.");
533
+ return chunks.join("");
548
534
  },
549
535
  async delete(account) {
550
- const result = spawn("security", [
551
- "delete-generic-password",
552
- "-a",
553
- account,
554
- "-s",
555
- KEYCHAIN_SERVICE
556
- ], { encoding: "utf8" });
557
- if (result.status !== 44) runOrThrow(result, "macOS Keychain delete");
536
+ const count = chunkCount(read(account));
537
+ for (let index = 0; index < count; index += 1) remove(chunkAccount(account, index));
538
+ remove(account);
558
539
  }
559
540
  };
560
541
  }
@@ -563,9 +544,9 @@ function createPlatformInstallationSecretBackend(options = {}) {
563
544
  async set(account, value) {
564
545
  runOrThrow(spawn("secret-tool", [
565
546
  "store",
566
- "--label=Topolo external agent installation",
547
+ "--label=Topolo protected credential",
567
548
  "service",
568
- KEYCHAIN_SERVICE,
549
+ service,
569
550
  "account",
570
551
  account
571
552
  ], { input: value, encoding: "utf8" }), "Linux Secret Service write");
@@ -574,7 +555,7 @@ function createPlatformInstallationSecretBackend(options = {}) {
574
555
  const result = spawn("secret-tool", [
575
556
  "lookup",
576
557
  "service",
577
- KEYCHAIN_SERVICE,
558
+ service,
578
559
  "account",
579
560
  account
580
561
  ], { encoding: "utf8" });
@@ -585,7 +566,7 @@ function createPlatformInstallationSecretBackend(options = {}) {
585
566
  const result = spawn("secret-tool", [
586
567
  "clear",
587
568
  "service",
588
- KEYCHAIN_SERVICE,
569
+ service,
589
570
  "account",
590
571
  account
591
572
  ], { encoding: "utf8" });
@@ -656,6 +637,118 @@ async function deleteExternalAgentInstallation(environment, id, backend = create
656
637
  await (0, import_promises.rm)(installationPath(environment, id), { force: true });
657
638
  }
658
639
 
640
+ // src/node/action-secret-store.ts
641
+ function createProtectedActionSecretStore(options) {
642
+ const backend = options.backend ?? createPlatformInstallationSecretBackend({
643
+ service: "topolo-action-handoff",
644
+ root: (0, import_node_path2.join)(installationStorePath(), "action-handoff")
645
+ });
646
+ const now = options.now ?? Date.now;
647
+ async function account(reference) {
648
+ if (!/^topolo-secret:[0-9a-f-]{36}$/.test(reference)) throw new TopoloSdkError("invalid_action_secret_reference", "Invalid protected action reference.");
649
+ const identity = await options.identity();
650
+ if (!identity.user.id) throw new TopoloSdkError("action_secret_identity_required", "A verified identity is required for protected action handoff.");
651
+ if (identity.kind === "api_key" && !identity.apiKey?.id) throw new TopoloSdkError("action_secret_identity_required", "A verified API key identity is required for protected action handoff.");
652
+ const scope = (0, import_node_crypto.createHash)("sha256").update(JSON.stringify([
653
+ options.environment,
654
+ identity.user.principalId ?? identity.user.id,
655
+ identity.organization?.id ?? null,
656
+ identity.apiKey?.id ?? null
657
+ ])).digest("hex");
658
+ return `${scope}-${reference.slice(ACTION_SECRET_PREFIX.length)}`;
659
+ }
660
+ return {
661
+ async save(value) {
662
+ const reference = `${ACTION_SECRET_PREFIX}${(0, import_node_crypto.randomUUID)()}`;
663
+ await backend.set(await account(reference), JSON.stringify({ value, expiresAt: now() + 30 * 60 * 1e3 }));
664
+ return reference;
665
+ },
666
+ async load(reference) {
667
+ const key = await account(reference);
668
+ const encoded = await backend.get(key);
669
+ if (!encoded) throw new TopoloSdkError("action_secret_unavailable", "Protected action reference is missing or belongs to another identity or environment.");
670
+ let record;
671
+ try {
672
+ record = JSON.parse(encoded);
673
+ } catch {
674
+ throw new TopoloSdkError("action_secret_invalid", "Protected action reference is corrupt.");
675
+ }
676
+ if (!Number.isFinite(record?.expiresAt) || !Object.hasOwn(record, "value")) throw new TopoloSdkError("action_secret_invalid", "Protected action reference is corrupt.");
677
+ if (record.expiresAt <= now()) {
678
+ await backend.delete(key);
679
+ throw new TopoloSdkError("action_secret_expired", "Protected action reference expired. Repeat the originating action if its contract permits.");
680
+ }
681
+ return record.value;
682
+ },
683
+ async delete(reference) {
684
+ await backend.delete(await account(reference));
685
+ }
686
+ };
687
+ }
688
+
689
+ // src/environment.ts
690
+ var TOPOLO_RUNTIME_ENVIRONMENTS = ["production", "staging", "development", "local"];
691
+ var TOPOLO_ENVIRONMENT_SERVICE_URLS = {
692
+ production: {
693
+ auth: "https://auth.topolo.app",
694
+ developers: "https://developers.topolo.app"
695
+ },
696
+ staging: {
697
+ auth: "https://auth.stg.topolo.us",
698
+ developers: "https://developers.stg.topolo.us"
699
+ },
700
+ development: {
701
+ auth: "https://auth.topolo.dev",
702
+ developers: "https://developers.topolo.dev"
703
+ },
704
+ local: {}
705
+ };
706
+ function hostnameHasSuffix(hostname, suffix) {
707
+ return hostname === suffix || hostname.endsWith(`.${suffix}`);
708
+ }
709
+ function workersDevAccountMatches(hostname, account) {
710
+ if (!hostname.endsWith(".workers.dev")) return false;
711
+ const labels = hostname.slice(0, -".workers.dev".length).split(".");
712
+ return labels.length >= 2 && labels.at(-1) === account;
713
+ }
714
+ function originMatchesEnvironment(url, environment) {
715
+ const hostname = url.hostname.toLowerCase();
716
+ if (environment === "local") {
717
+ return hostname === "localhost" || hostname.endsWith(".localhost") || hostname === "127.0.0.1" || hostname === "[::1]";
718
+ }
719
+ if (url.protocol !== "https:") return false;
720
+ if (environment === "production") {
721
+ return hostnameHasSuffix(hostname, "topolo.app") || hostnameHasSuffix(hostname, "topolo.io") || hostname === "api.ol0.me" || workersDevAccountMatches(hostname, "topolo");
722
+ }
723
+ if (environment === "staging") {
724
+ return hostnameHasSuffix(hostname, "stg.topolo.us") || workersDevAccountMatches(hostname, "topolo-staging");
725
+ }
726
+ return hostnameHasSuffix(hostname, "topolo.dev") || workersDevAccountMatches(hostname, "topolo-development");
727
+ }
728
+ function assertTopoloServiceUrlEnvironment(service, value, environment) {
729
+ let url;
730
+ try {
731
+ url = new URL(value);
732
+ } catch {
733
+ throw new TopoloSdkError("invalid_service_url", `Invalid URL configured for Topolo service "${service}".`);
734
+ }
735
+ if (url.protocol !== "https:" && url.protocol !== "http:" || url.username || url.password || !originMatchesEnvironment(url, environment)) {
736
+ throw new TopoloSdkError(
737
+ "environment_mismatch",
738
+ `Refusing Topolo service "${service}": ${url.origin} does not belong to ${environment}.`
739
+ );
740
+ }
741
+ return url.origin;
742
+ }
743
+ function topoloServiceOrigins(serviceUrls, environment) {
744
+ return Object.fromEntries(
745
+ Object.entries(serviceUrls ?? {}).sort(([left], [right]) => left.localeCompare(right)).map(([service, value]) => [
746
+ service,
747
+ assertTopoloServiceUrlEnvironment(service, value, environment)
748
+ ])
749
+ );
750
+ }
751
+
659
752
  // src/node/credential-store.ts
660
753
  var TOPOLO_ENVIRONMENTS = TOPOLO_RUNTIME_ENVIRONMENTS;
661
754
  function contextKey(contextType, organizationId) {
@@ -665,8 +758,8 @@ var ENVIRONMENT_DEFAULT_SERVICE_URLS = TOPOLO_ENVIRONMENT_SERVICE_URLS;
665
758
  function configPath() {
666
759
  const override = process.env.TOPOLO_CONFIG_PATH;
667
760
  if (override) return override;
668
- const base = process.env.XDG_CONFIG_HOME || ((0, import_node_os2.platform)() === "win32" ? (0, import_node_path2.join)((0, import_node_os2.homedir)(), "AppData", "Roaming") : (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".config"));
669
- return (0, import_node_path2.join)(base, "topolo", "config.json");
761
+ const base = process.env.XDG_CONFIG_HOME || ((0, import_node_os2.platform)() === "win32" ? (0, import_node_path3.join)((0, import_node_os2.homedir)(), "AppData", "Roaming") : (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".config"));
762
+ return (0, import_node_path3.join)(base, "topolo", "config.json");
670
763
  }
671
764
  async function readConfig() {
672
765
  const path = configPath();
@@ -680,7 +773,7 @@ async function readConfig() {
680
773
  }
681
774
  async function writeConfig(config) {
682
775
  const path = configPath();
683
- await (0, import_promises2.mkdir)((0, import_node_path2.dirname)(path), { recursive: true });
776
+ await (0, import_promises2.mkdir)((0, import_node_path3.dirname)(path), { recursive: true });
684
777
  await (0, import_promises2.writeFile)(path, JSON.stringify(config, null, 2) + "\n", "utf8");
685
778
  if ((0, import_node_os2.platform)() !== "win32") {
686
779
  try {
@@ -800,7 +893,7 @@ var CONFIG_LOCK_TIMEOUT_MS = 5e3;
800
893
  var CONFIG_LOCK_STALE_MS = 12e4;
801
894
  async function withConfigWriteLock(operation) {
802
895
  const lockPath = `${configPath()}.lock`;
803
- await (0, import_promises2.mkdir)((0, import_node_path2.dirname)(lockPath), { recursive: true });
896
+ await (0, import_promises2.mkdir)((0, import_node_path3.dirname)(lockPath), { recursive: true });
804
897
  const startedAt = Date.now();
805
898
  while (true) {
806
899
  try {
@@ -977,6 +1070,7 @@ function resolvedProfileConfig(config, environment = selectedEnvironment()) {
977
1070
  configPath,
978
1071
  contextKey,
979
1072
  createPlatformInstallationSecretBackend,
1073
+ createProtectedActionSecretStore,
980
1074
  credentialSourceFromConfig,
981
1075
  deleteExternalAgentInstallation,
982
1076
  envVarPrefix,
@@ -26,11 +26,53 @@ type TopoloCredential = {
26
26
  expiresAt?: number;
27
27
  };
28
28
 
29
+ interface TopoloActionSecretStore {
30
+ save(value: unknown): Promise<string>;
31
+ load(reference: string): Promise<unknown>;
32
+ delete(reference: string): Promise<void>;
33
+ }
34
+
29
35
  declare const TOPOLO_RUNTIME_ENVIRONMENTS: readonly ["production", "staging", "development", "local"];
30
36
  type TopoloRuntimeEnvironment = (typeof TOPOLO_RUNTIME_ENVIRONMENTS)[number];
31
37
  declare function assertTopoloServiceUrlEnvironment(service: string, value: string, environment: TopoloRuntimeEnvironment): string;
32
38
  declare function topoloServiceOrigins(serviceUrls: Record<string, string> | undefined, environment: TopoloRuntimeEnvironment): Record<string, string>;
33
39
 
40
+ interface CredentialIntrospection {
41
+ kind: 'api_key' | 'access_token';
42
+ authorizationMode: 'context_rbac' | 'oauth_scopes' | 'session_rbac' | 'api_key' | 'unknown';
43
+ user: {
44
+ id: string;
45
+ email: string;
46
+ name: string | null;
47
+ role: string;
48
+ permissions: string[];
49
+ principalId?: string | null;
50
+ principalType?: 'agent_employee' | 'external_agent' | null;
51
+ accountableUserId?: string | null;
52
+ agentInstanceId?: string | null;
53
+ installationId?: string | null;
54
+ provider?: string | null;
55
+ model?: string | null;
56
+ serviceScopes?: string[];
57
+ policyRef?: string | null;
58
+ };
59
+ organization: {
60
+ id: string;
61
+ slug: string;
62
+ name: string;
63
+ } | null;
64
+ apiKey: {
65
+ id: string | null;
66
+ name: string | null;
67
+ appId: string | null;
68
+ scopes: string[];
69
+ resourceBindings: Array<{
70
+ resourceType: string;
71
+ resourceId: string;
72
+ }>;
73
+ } | null;
74
+ }
75
+
34
76
  interface ExternalAgentInstallation {
35
77
  id: string;
36
78
  environment: TopoloRuntimeEnvironment;
@@ -64,6 +106,7 @@ declare function createPlatformInstallationSecretBackend(options?: {
64
106
  platform?: NodeJS.Platform;
65
107
  spawn?: Spawn;
66
108
  root?: string;
109
+ service?: string;
67
110
  }): InstallationSecretBackend;
68
111
  declare function writeExternalAgentInstallation(installation: ExternalAgentInstallation, secret: ExternalAgentInstallationSecret, backend?: InstallationSecretBackend): Promise<void>;
69
112
  declare function readExternalAgentInstallation(environment: TopoloRuntimeEnvironment, id: string, backend?: InstallationSecretBackend): Promise<{
@@ -73,6 +116,13 @@ declare function readExternalAgentInstallation(environment: TopoloRuntimeEnviron
73
116
  declare function listExternalAgentInstallations(environment: TopoloRuntimeEnvironment): Promise<ExternalAgentInstallation[]>;
74
117
  declare function deleteExternalAgentInstallation(environment: TopoloRuntimeEnvironment, id: string, backend?: InstallationSecretBackend): Promise<void>;
75
118
 
119
+ declare function createProtectedActionSecretStore(options: {
120
+ environment: TopoloRuntimeEnvironment;
121
+ identity: () => Promise<CredentialIntrospection>;
122
+ backend?: InstallationSecretBackend;
123
+ now?: () => number;
124
+ }): TopoloActionSecretStore;
125
+
76
126
  /**
77
127
  * Node-only credential store shared by the `topolo` CLI and `@topolo/mcp`.
78
128
  *
@@ -166,4 +216,4 @@ declare function resolveServiceUrls(configUrls?: Record<string, string>, environ
166
216
  declare function assertServiceUrlForEnvironment(service: string, serviceUrl: string | undefined, environment?: TopoloEnvironment): void;
167
217
  declare function resolvedProfileConfig(config: TopoloConfig, environment?: TopoloEnvironment): TopoloEnvironmentProfile;
168
218
 
169
- export { ENVIRONMENT_DEFAULT_SERVICE_URLS, type ExternalAgentInstallation, type ExternalAgentInstallationSecret, type InstallationSecretBackend, TOPOLO_ENVIRONMENTS, type TopoloConfig, type TopoloContextCredential, type TopoloEnvironment, type TopoloEnvironmentProfile, activateContext, assertServiceUrlForEnvironment, assertTopoloServiceUrlEnvironment, clearProfileCredential, configPath, contextKey, createPlatformInstallationSecretBackend, credentialSourceFromConfig, deleteExternalAgentInstallation, envVarPrefix, installationStorePath, listExternalAgentInstallations, normalizeEnvironment, profileForEnvironment, readConfig, readExternalAgentInstallation, refreshContextCredentialIfNeeded, refreshCredentialIfNeeded, resolveAgentName, resolveCredential, resolveServiceUrls, resolvedProfileConfig, selectedEnvironment, topoloServiceOrigins, writeConfig, writeContextCredential, writeExternalAgentInstallation, writeProfileConfig };
219
+ export { ENVIRONMENT_DEFAULT_SERVICE_URLS, type ExternalAgentInstallation, type ExternalAgentInstallationSecret, type InstallationSecretBackend, TOPOLO_ENVIRONMENTS, type TopoloConfig, type TopoloContextCredential, type TopoloEnvironment, type TopoloEnvironmentProfile, activateContext, assertServiceUrlForEnvironment, assertTopoloServiceUrlEnvironment, clearProfileCredential, configPath, contextKey, createPlatformInstallationSecretBackend, createProtectedActionSecretStore, credentialSourceFromConfig, deleteExternalAgentInstallation, envVarPrefix, installationStorePath, listExternalAgentInstallations, normalizeEnvironment, profileForEnvironment, readConfig, readExternalAgentInstallation, refreshContextCredentialIfNeeded, refreshCredentialIfNeeded, resolveAgentName, resolveCredential, resolveServiceUrls, resolvedProfileConfig, selectedEnvironment, topoloServiceOrigins, writeConfig, writeContextCredential, writeExternalAgentInstallation, writeProfileConfig };
@@ -26,11 +26,53 @@ type TopoloCredential = {
26
26
  expiresAt?: number;
27
27
  };
28
28
 
29
+ interface TopoloActionSecretStore {
30
+ save(value: unknown): Promise<string>;
31
+ load(reference: string): Promise<unknown>;
32
+ delete(reference: string): Promise<void>;
33
+ }
34
+
29
35
  declare const TOPOLO_RUNTIME_ENVIRONMENTS: readonly ["production", "staging", "development", "local"];
30
36
  type TopoloRuntimeEnvironment = (typeof TOPOLO_RUNTIME_ENVIRONMENTS)[number];
31
37
  declare function assertTopoloServiceUrlEnvironment(service: string, value: string, environment: TopoloRuntimeEnvironment): string;
32
38
  declare function topoloServiceOrigins(serviceUrls: Record<string, string> | undefined, environment: TopoloRuntimeEnvironment): Record<string, string>;
33
39
 
40
+ interface CredentialIntrospection {
41
+ kind: 'api_key' | 'access_token';
42
+ authorizationMode: 'context_rbac' | 'oauth_scopes' | 'session_rbac' | 'api_key' | 'unknown';
43
+ user: {
44
+ id: string;
45
+ email: string;
46
+ name: string | null;
47
+ role: string;
48
+ permissions: string[];
49
+ principalId?: string | null;
50
+ principalType?: 'agent_employee' | 'external_agent' | null;
51
+ accountableUserId?: string | null;
52
+ agentInstanceId?: string | null;
53
+ installationId?: string | null;
54
+ provider?: string | null;
55
+ model?: string | null;
56
+ serviceScopes?: string[];
57
+ policyRef?: string | null;
58
+ };
59
+ organization: {
60
+ id: string;
61
+ slug: string;
62
+ name: string;
63
+ } | null;
64
+ apiKey: {
65
+ id: string | null;
66
+ name: string | null;
67
+ appId: string | null;
68
+ scopes: string[];
69
+ resourceBindings: Array<{
70
+ resourceType: string;
71
+ resourceId: string;
72
+ }>;
73
+ } | null;
74
+ }
75
+
34
76
  interface ExternalAgentInstallation {
35
77
  id: string;
36
78
  environment: TopoloRuntimeEnvironment;
@@ -64,6 +106,7 @@ declare function createPlatformInstallationSecretBackend(options?: {
64
106
  platform?: NodeJS.Platform;
65
107
  spawn?: Spawn;
66
108
  root?: string;
109
+ service?: string;
67
110
  }): InstallationSecretBackend;
68
111
  declare function writeExternalAgentInstallation(installation: ExternalAgentInstallation, secret: ExternalAgentInstallationSecret, backend?: InstallationSecretBackend): Promise<void>;
69
112
  declare function readExternalAgentInstallation(environment: TopoloRuntimeEnvironment, id: string, backend?: InstallationSecretBackend): Promise<{
@@ -73,6 +116,13 @@ declare function readExternalAgentInstallation(environment: TopoloRuntimeEnviron
73
116
  declare function listExternalAgentInstallations(environment: TopoloRuntimeEnvironment): Promise<ExternalAgentInstallation[]>;
74
117
  declare function deleteExternalAgentInstallation(environment: TopoloRuntimeEnvironment, id: string, backend?: InstallationSecretBackend): Promise<void>;
75
118
 
119
+ declare function createProtectedActionSecretStore(options: {
120
+ environment: TopoloRuntimeEnvironment;
121
+ identity: () => Promise<CredentialIntrospection>;
122
+ backend?: InstallationSecretBackend;
123
+ now?: () => number;
124
+ }): TopoloActionSecretStore;
125
+
76
126
  /**
77
127
  * Node-only credential store shared by the `topolo` CLI and `@topolo/mcp`.
78
128
  *
@@ -166,4 +216,4 @@ declare function resolveServiceUrls(configUrls?: Record<string, string>, environ
166
216
  declare function assertServiceUrlForEnvironment(service: string, serviceUrl: string | undefined, environment?: TopoloEnvironment): void;
167
217
  declare function resolvedProfileConfig(config: TopoloConfig, environment?: TopoloEnvironment): TopoloEnvironmentProfile;
168
218
 
169
- export { ENVIRONMENT_DEFAULT_SERVICE_URLS, type ExternalAgentInstallation, type ExternalAgentInstallationSecret, type InstallationSecretBackend, TOPOLO_ENVIRONMENTS, type TopoloConfig, type TopoloContextCredential, type TopoloEnvironment, type TopoloEnvironmentProfile, activateContext, assertServiceUrlForEnvironment, assertTopoloServiceUrlEnvironment, clearProfileCredential, configPath, contextKey, createPlatformInstallationSecretBackend, credentialSourceFromConfig, deleteExternalAgentInstallation, envVarPrefix, installationStorePath, listExternalAgentInstallations, normalizeEnvironment, profileForEnvironment, readConfig, readExternalAgentInstallation, refreshContextCredentialIfNeeded, refreshCredentialIfNeeded, resolveAgentName, resolveCredential, resolveServiceUrls, resolvedProfileConfig, selectedEnvironment, topoloServiceOrigins, writeConfig, writeContextCredential, writeExternalAgentInstallation, writeProfileConfig };
219
+ export { ENVIRONMENT_DEFAULT_SERVICE_URLS, type ExternalAgentInstallation, type ExternalAgentInstallationSecret, type InstallationSecretBackend, TOPOLO_ENVIRONMENTS, type TopoloConfig, type TopoloContextCredential, type TopoloEnvironment, type TopoloEnvironmentProfile, activateContext, assertServiceUrlForEnvironment, assertTopoloServiceUrlEnvironment, clearProfileCredential, configPath, contextKey, createPlatformInstallationSecretBackend, createProtectedActionSecretStore, credentialSourceFromConfig, deleteExternalAgentInstallation, envVarPrefix, installationStorePath, listExternalAgentInstallations, normalizeEnvironment, profileForEnvironment, readConfig, readExternalAgentInstallation, refreshContextCredentialIfNeeded, refreshCredentialIfNeeded, resolveAgentName, resolveCredential, resolveServiceUrls, resolvedProfileConfig, selectedEnvironment, topoloServiceOrigins, writeConfig, writeContextCredential, writeExternalAgentInstallation, writeProfileConfig };