@indigoai-us/hq-cli 5.153.0 → 5.155.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/dist/commands/agent-mcp.d.ts +4 -1
- package/dist/commands/agent-mcp.js +43 -2
- package/dist/commands/files-browse.d.ts +6 -0
- package/dist/commands/files-browse.js +3 -2
- package/dist/commands/lanes.d.ts +3 -11
- package/dist/commands/lanes.js +68 -37
- package/dist/lib/agent-kit/mcp/attachments.d.ts +99 -0
- package/dist/lib/agent-kit/mcp/attachments.js +188 -0
- package/dist/lib/agent-kit/mcp/tools.d.ts +13 -2
- package/dist/lib/agent-kit/mcp/tools.js +66 -3
- package/dist/lib/agent-kit/paths.d.ts +3 -0
- package/dist/lib/agent-kit/paths.js +2 -0
- package/dist/lib/lanes/accounts.d.ts +16 -11
- package/dist/lib/lanes/accounts.js +50 -8
- package/dist/lib/lanes/admission-host.d.ts +19 -0
- package/dist/lib/lanes/admission-host.js +33 -0
- package/dist/lib/lanes/config.js +25 -0
- package/dist/lib/lanes/provider-rejection.d.ts +4 -4
- package/dist/lib/lanes/provider-rejection.js +24 -6
- package/dist/lib/lanes/reconcile.js +30 -1
- package/dist/lib/lanes/spawn.d.ts +9 -1
- package/dist/lib/lanes/spawn.js +420 -14
- package/dist/lib/lanes/types.d.ts +7 -3
- package/dist/lib/lanes/types.js +3 -2
- package/dist/lib/lanes/watcher.d.ts +8 -1
- package/dist/lib/lanes/watcher.js +39 -3
- package/dist/utils/vault-api.d.ts +7 -1
- package/dist/utils/vault-api.js +22 -12
- package/package.json +1 -1
|
@@ -166,7 +166,7 @@ export declare const TERMINAL_LANE_STATES: readonly ["done", "failed", "interrup
|
|
|
166
166
|
export type TerminalLaneState = (typeof TERMINAL_LANE_STATES)[number];
|
|
167
167
|
export declare const LANE_PROVIDERS: readonly ["claude", "codex", "grok"];
|
|
168
168
|
export type LaneProvider = (typeof LANE_PROVIDERS)[number];
|
|
169
|
-
export declare const QUEUED_REASONS: readonly ["cap", "provider_cap", "account_cap", "profile_cap", "queue_position", "throttle", "provider_binary_missing"];
|
|
169
|
+
export declare const QUEUED_REASONS: readonly ["cap", "provider_cap", "account_cap", "profile_cap", "queue_position", "throttle", "provider_binary_missing", "account_retry"];
|
|
170
170
|
export type QueuedReason = (typeof QUEUED_REASONS)[number];
|
|
171
171
|
export declare function isQueuedReason(value: unknown): value is QueuedReason;
|
|
172
172
|
export declare const HOST_KINDS: readonly ["local", "outpost"];
|
|
@@ -436,11 +436,11 @@ export interface AccountHistoryEntry {
|
|
|
436
436
|
from: string | null;
|
|
437
437
|
to: string;
|
|
438
438
|
at: string;
|
|
439
|
-
reason: "operator_move";
|
|
439
|
+
reason: "first_assignment" | "model_change" | "usage_limit" | "account_unavailable" | "operator_move";
|
|
440
440
|
}
|
|
441
441
|
export interface PendingResume {
|
|
442
442
|
session_id: string;
|
|
443
|
-
trigger: "message" | "resume";
|
|
443
|
+
trigger: "answered_questions" | "message" | "resume";
|
|
444
444
|
resumed_from?: "interrupted" | "done" | "blocked";
|
|
445
445
|
text?: string;
|
|
446
446
|
message_id?: string;
|
|
@@ -461,6 +461,8 @@ export interface LaneResumeRecord {
|
|
|
461
461
|
*/
|
|
462
462
|
resumed_from?: "interrupted" | "done" | "blocked";
|
|
463
463
|
text?: string;
|
|
464
|
+
/** False when the continued turn explicitly skipped compaction. */
|
|
465
|
+
compact?: boolean;
|
|
464
466
|
}
|
|
465
467
|
export interface LaneRunUsage {
|
|
466
468
|
cache_read_input_tokens: number | null;
|
|
@@ -575,6 +577,8 @@ export interface ProviderConfig {
|
|
|
575
577
|
* Account pick is least-loaded among enabled, logged-in accounts.
|
|
576
578
|
*/
|
|
577
579
|
default_account?: string;
|
|
580
|
+
/** Ordered account preferences used only for a lane's first assignment. */
|
|
581
|
+
preferred_accounts?: string[];
|
|
578
582
|
/** Default model for this provider when the account does not set one. */
|
|
579
583
|
model?: string;
|
|
580
584
|
/** Default effort for this provider when the account does not set one. */
|
package/dist/lib/lanes/types.js
CHANGED
|
@@ -103,6 +103,7 @@ export const QUEUED_REASONS = [
|
|
|
103
103
|
"queue_position",
|
|
104
104
|
"throttle",
|
|
105
105
|
"provider_binary_missing",
|
|
106
|
+
"account_retry",
|
|
106
107
|
];
|
|
107
108
|
export function isQueuedReason(value) {
|
|
108
109
|
return typeof value === "string" && QUEUED_REASONS.includes(value);
|
|
@@ -215,11 +216,11 @@ export const LANE_FIELD_WRITERS = {
|
|
|
215
216
|
schema_version: ["creator"], lane_id: ["creator"], company: ["creator"],
|
|
216
217
|
project_id: ["command"], story_id: ["command"], stories: ["creator", "command", "watcher", "reconcile", "spawn"], thread_id: ["creator", "command", "spawn"], mesh_company: ["creator"],
|
|
217
218
|
depth: ["command"], senior: ["command"], senior_history: ["command"],
|
|
218
|
-
state: ["creator", "command", "watcher", "reconcile", "spawn"], queued_reason: ["creator", "command", "spawn", "reconcile"], queued_at: ["creator", "command", "spawn"], admission_reserved_at: ["creator", "command", "reconcile", "spawn"],
|
|
219
|
+
state: ["creator", "command", "watcher", "reconcile", "spawn"], queued_reason: ["creator", "command", "spawn", "watcher", "reconcile"], queued_at: ["creator", "command", "spawn", "watcher", "reconcile"], admission_reserved_at: ["creator", "command", "reconcile", "spawn", "watcher"],
|
|
219
220
|
pickup_validation_pending: ["creator", "command", "spawn", "reconcile"],
|
|
220
221
|
pickup_validation_pending_since: ["creator", "command", "spawn", "reconcile"],
|
|
221
222
|
priority: ["command"], worker: ["creator"], worker_path: ["creator"], worker_pack: ["creator"], worker_profile: ["creator"], worker_profile_hash: ["creator", "spawn"], worker_profile_ignored: ["creator", "spawn"],
|
|
222
|
-
provider: ["creator"], account: ["creator", "spawn", "command", "reconcile"], admission_accounts: ["creator", "command"], account_history: ["command"], pending_resume: ["command", "spawn"], host: ["creator"], origin_host: ["creator"], origin_host_source: ["creator"],
|
|
223
|
+
provider: ["creator"], account: ["creator", "spawn", "command", "watcher", "reconcile"], admission_accounts: ["creator", "command"], account_history: ["command", "spawn", "watcher", "reconcile"], pending_resume: ["command", "spawn", "watcher", "reconcile"], host: ["creator"], origin_host: ["creator"], origin_host_source: ["creator"],
|
|
223
224
|
version: ["creator"], created_at: ["creator"], updated_at: ["creator"], attribution_history: ["command"],
|
|
224
225
|
last_envelope: ["watcher", "reconcile"], model: ["creator", "spawn", "command"], model_source: ["creator", "spawn", "command"], effort: ["creator", "spawn"], effort_source: ["creator", "spawn"], fast_mode: ["spawn"],
|
|
225
226
|
last_message_at: ["command"], last_compaction: ["spawn"], story_complete: ["watcher", "reconcile"], story_complete_reason: ["watcher", "reconcile"], story_status: ["watcher", "reconcile"],
|
|
@@ -34,8 +34,9 @@
|
|
|
34
34
|
import { type LaneEnvelope, type LaneRecord, type LaneState } from "./types.js";
|
|
35
35
|
import { type QuotaSignal } from "./quota.js";
|
|
36
36
|
import { type ProviderRejection } from "./provider-rejection.js";
|
|
37
|
+
import type { AdmissionHost } from "./admission.js";
|
|
37
38
|
import { type LaneMesh } from "./mesh-events.js";
|
|
38
|
-
import { type EnvelopeRepairSpawnInput } from "./spawn.js";
|
|
39
|
+
import { type EnvelopeRepairSpawnInput, type SpawnLaneDeps } from "./spawn.js";
|
|
39
40
|
/** Spec §5.7: no signal for 20 minutes while the process is still alive. */
|
|
40
41
|
export declare const SILENCE_MS: number;
|
|
41
42
|
export declare const WATCHER_POLL_MS = 5000;
|
|
@@ -206,6 +207,12 @@ export interface WatcherLoopDeps {
|
|
|
206
207
|
meshRequestTimeoutMs?: number;
|
|
207
208
|
/** Spec §4 repair spawn. Default: `spawnEnvelopeRepair`. */
|
|
208
209
|
spawnRepair?: (hqRoot: string, lane: LaneRecord, input: EnvelopeRepairSpawnInput) => number;
|
|
210
|
+
/** Rebind and respawn after a provider quota or authentication refusal. */
|
|
211
|
+
retryAccount?: (hqRoot: string, lane: LaneRecord, reason: "usage_limit" | "account_unavailable") => LaneRecord | Promise<LaneRecord>;
|
|
212
|
+
/** Shared process/Outpost identity used by command admission and watcher retries. */
|
|
213
|
+
resolveAdmissionHost?: () => AdmissionHost;
|
|
214
|
+
/** Spawn seams for account retry; host scope is supplied by the resolver above. */
|
|
215
|
+
retrySpawnDeps?: Omit<SpawnLaneDeps, "admissionHost">;
|
|
209
216
|
}
|
|
210
217
|
/**
|
|
211
218
|
* Wrap one mesh call. A failed emit is logged as `mesh_event_failed` with
|
|
@@ -40,12 +40,13 @@ import { laneStories, storyStatusForEnvelope } from "./stories.js";
|
|
|
40
40
|
import { readLane, retryUpdateLane } from "./store.js";
|
|
41
41
|
import { readWorkerRunResult } from "./delivery.js";
|
|
42
42
|
import { applyDefaultQuotaHold, quotaSignalFromFields, readQuotaSignal, recordAccountQuota, } from "./quota.js";
|
|
43
|
-
import { PROVIDER_REJECTED_REASON, QUOTA_STATUS, providerRejectionDetails, readProviderRejection, } from "./provider-rejection.js";
|
|
43
|
+
import { PROVIDER_REJECTED_REASON, QUOTA_STATUS, isProviderLoginFailure, providerRejectionDetails, readProviderRejection, } from "./provider-rejection.js";
|
|
44
44
|
import { readToolActivity, toolActivityPath } from "./dropbox.js";
|
|
45
45
|
import { loadLanesConfig, providerProgressEventMinIntervalMs, providerQuotaDefaultHoldMs } from "./config.js";
|
|
46
|
+
import { resolveAdmissionHost as resolveProcessAdmissionHost } from "./admission-host.js";
|
|
46
47
|
import { createLaneMesh, emitLaneDecisionEvents, emitLaneProgressEvent, progressEventDue, } from "./mesh-events.js";
|
|
47
48
|
import { envelopeQuestionId } from "./questions.js";
|
|
48
|
-
import { laneRunDir, missingPidProbe, probePidRecord, readPidRecord, readProcStarttime, readSpawnManifest, envelopeRepairMode, spawnEnvelopeRepair, } from "./spawn.js";
|
|
49
|
+
import { laneRunDir, missingPidProbe, probePidRecord, readPidRecord, readProcStarttime, readSpawnManifest, envelopeRepairMode, spawnEnvelopeRepair, retryLaneOnAlternateAccount, } from "./spawn.js";
|
|
49
50
|
/** Spec §5.7: no signal for 20 minutes while the process is still alive. */
|
|
50
51
|
export const SILENCE_MS = 20 * 60 * 1000;
|
|
51
52
|
export const WATCHER_POLL_MS = 5_000;
|
|
@@ -89,7 +90,12 @@ export function classifyLaneExit(input) {
|
|
|
89
90
|
throw new LanesError("unknown_envelope_decision", `Unknown envelope decision ${JSON.stringify(input.envelope.decision)}. Accepted: done, ask, blocked`, { accepted: ["done", "ask", "blocked"], value: input.envelope.decision });
|
|
90
91
|
}
|
|
91
92
|
if (input.providerRejection) {
|
|
92
|
-
return {
|
|
93
|
+
return {
|
|
94
|
+
state: "failed",
|
|
95
|
+
reason: isProviderLoginFailure(input.providerRejection)
|
|
96
|
+
? "account_not_logged_in"
|
|
97
|
+
: PROVIDER_REJECTED_REASON,
|
|
98
|
+
};
|
|
93
99
|
}
|
|
94
100
|
if (input.invalidEnvelope) {
|
|
95
101
|
return { state: "failed", reason: ENVELOPE_INVALID_REASON };
|
|
@@ -613,6 +619,10 @@ export async function runWatcherLoop(hqRoot, laneId, deps = {}) {
|
|
|
613
619
|
}
|
|
614
620
|
const progressIntervalMs = deps.progressEventMinIntervalMs ?? readProgressIntervalMs(hqRoot, readLaneSoft(hqRoot, laneId));
|
|
615
621
|
const spawnRepair = deps.spawnRepair ?? spawnEnvelopeRepair;
|
|
622
|
+
const retryAccount = deps.retryAccount ?? ((root, lane, reason) => retryLaneOnAlternateAccount(root, lane, reason, "watcher", {
|
|
623
|
+
...deps.retrySpawnDeps,
|
|
624
|
+
admissionHost: (deps.resolveAdmissionHost ?? resolveProcessAdmissionHost)(),
|
|
625
|
+
}));
|
|
616
626
|
let lastSignalMs = now();
|
|
617
627
|
let lastSeenMtime = 0;
|
|
618
628
|
let lastAlive = null;
|
|
@@ -836,6 +846,32 @@ export async function runWatcherLoop(hqRoot, laneId, deps = {}) {
|
|
|
836
846
|
process.stderr.write(`hq lanes watcher: quota record failed for ${laneId}: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
837
847
|
}
|
|
838
848
|
}
|
|
849
|
+
const accountRetryReason = quota
|
|
850
|
+
? "usage_limit"
|
|
851
|
+
: isProviderLoginFailure(providerRejection)
|
|
852
|
+
? "account_unavailable"
|
|
853
|
+
: null;
|
|
854
|
+
if (accountRetryReason && quotaLane?.account) {
|
|
855
|
+
try {
|
|
856
|
+
const retry = await retryAccount(hqRoot, quotaLane, accountRetryReason);
|
|
857
|
+
logDecision({
|
|
858
|
+
event: "account_failover",
|
|
859
|
+
at: tick.emit.at,
|
|
860
|
+
from: quotaLane.account,
|
|
861
|
+
to: retry.account ?? null,
|
|
862
|
+
reason: accountRetryReason,
|
|
863
|
+
state: retry.state,
|
|
864
|
+
});
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
catch (err) {
|
|
868
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
869
|
+
process.stderr.write(`hq lanes watcher: account failover failed for ${laneId}: ${message}\n`);
|
|
870
|
+
logDecision({ event: "account_failover_failed", at: tick.emit.at, reason: accountRetryReason, error: message });
|
|
871
|
+
if (readLane(hqRoot, laneId).state !== "running")
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
839
875
|
const terminalWrite = applyTerminalToLaneWithResult(hqRoot, laneId, classification, {
|
|
840
876
|
envelope,
|
|
841
877
|
envelopeRef: latest?.ref,
|
|
@@ -61,7 +61,13 @@ export declare function vaultApiFetchPublic(opts: {
|
|
|
61
61
|
}): Promise<Response>;
|
|
62
62
|
export declare function looksLikeCompanyUid(ref: string): boolean;
|
|
63
63
|
export declare function looksLikePersonVaultUid(ref: string): boolean;
|
|
64
|
-
export declare function getCompanyUid(token: string, companySlug: string | undefined
|
|
64
|
+
export declare function getCompanyUid(token: string, companySlug: string | undefined,
|
|
65
|
+
/**
|
|
66
|
+
* Control-plane origin for the lookups. Defaults to DEFAULT_VAULT_API_URL;
|
|
67
|
+
* pass it when the caller is bound to another API (an enrolled agent's
|
|
68
|
+
* `apiBaseUrl`) so its token never goes to the default host.
|
|
69
|
+
*/
|
|
70
|
+
baseUrl?: string): Promise<string>;
|
|
65
71
|
export declare function resolveCallerPersonUid(token: string, baseUrl?: string): Promise<string>;
|
|
66
72
|
export declare function getEntityUid(token: string, opts: {
|
|
67
73
|
personal?: boolean;
|
package/dist/utils/vault-api.js
CHANGED
|
@@ -314,10 +314,11 @@ function raiseIfUnauthorized(res) {
|
|
|
314
314
|
if (res.status === 401)
|
|
315
315
|
throw new AuthError();
|
|
316
316
|
}
|
|
317
|
-
async function resolveCompanyByUid(token, uid) {
|
|
317
|
+
async function resolveCompanyByUid(token, uid, baseUrl) {
|
|
318
318
|
const res = await vaultApiFetch({
|
|
319
319
|
token,
|
|
320
320
|
path: `/entity/${encodeURIComponent(uid)}`,
|
|
321
|
+
baseUrl,
|
|
321
322
|
signal: AbortSignal.timeout(identityLookupTimeoutMs()),
|
|
322
323
|
});
|
|
323
324
|
if (!res.ok) {
|
|
@@ -370,11 +371,12 @@ function isTransientSlugLookupStatus(status) {
|
|
|
370
371
|
* stranger's same-slug company is correctly invisible to resolution. This is
|
|
371
372
|
* the same primitive hq-cloud's `context.ts` already resolves slugs through.
|
|
372
373
|
*/
|
|
373
|
-
async function resolveSlugInCallerNamespace(token, slug) {
|
|
374
|
+
async function resolveSlugInCallerNamespace(token, slug, baseUrl) {
|
|
374
375
|
const attempt = async () => {
|
|
375
376
|
const res = await vaultApiFetch({
|
|
376
377
|
token,
|
|
377
378
|
path: "/entity/check-slug/me",
|
|
379
|
+
baseUrl,
|
|
378
380
|
query: { type: "company", slug },
|
|
379
381
|
signal: AbortSignal.timeout(identityLookupTimeoutMs()),
|
|
380
382
|
});
|
|
@@ -411,10 +413,11 @@ async function resolveSlugInCallerNamespace(token, slug) {
|
|
|
411
413
|
return first;
|
|
412
414
|
return run();
|
|
413
415
|
}
|
|
414
|
-
async function fetchGlobalCompanyBySlug(token, ref) {
|
|
416
|
+
async function fetchGlobalCompanyBySlug(token, ref, baseUrl) {
|
|
415
417
|
const attempt = () => vaultApiFetch({
|
|
416
418
|
token,
|
|
417
419
|
path: `/entity/by-slug/company/${encodeURIComponent(ref)}`,
|
|
420
|
+
baseUrl,
|
|
418
421
|
signal: AbortSignal.timeout(identityLookupTimeoutMs()),
|
|
419
422
|
});
|
|
420
423
|
try {
|
|
@@ -429,10 +432,10 @@ async function fetchGlobalCompanyBySlug(token, ref) {
|
|
|
429
432
|
}
|
|
430
433
|
return attempt();
|
|
431
434
|
}
|
|
432
|
-
async function resolveCompanyUid(token, ref) {
|
|
435
|
+
async function resolveCompanyUid(token, ref, baseUrl) {
|
|
433
436
|
// Silent escape hatch: a `cmp_` ref is a uid, not a slug — resolve directly.
|
|
434
437
|
if (looksLikeCompanyUid(ref)) {
|
|
435
|
-
return resolveCompanyByUid(token, ref);
|
|
438
|
+
return resolveCompanyByUid(token, ref, baseUrl);
|
|
436
439
|
}
|
|
437
440
|
// A `prs_` ref names a person's vault (DM attachments). There is no slug to
|
|
438
441
|
// resolve and no company lookup that could succeed; pass it through as the
|
|
@@ -453,7 +456,7 @@ async function resolveCompanyUid(token, ref) {
|
|
|
453
456
|
// hq-cli defect still throws, and the 401 AuthError that
|
|
454
457
|
// `raiseIfUnauthorized` throws still short-circuits before any fallback
|
|
455
458
|
// (Sentry HQ-CLI-1P / issue 7716090679).
|
|
456
|
-
const mine = await resolveSlugInCallerNamespace(token, ref);
|
|
459
|
+
const mine = await resolveSlugInCallerNamespace(token, ref, baseUrl);
|
|
457
460
|
if (mine.kind === "hit") {
|
|
458
461
|
return mine.uid;
|
|
459
462
|
}
|
|
@@ -462,7 +465,7 @@ async function resolveCompanyUid(token, ref) {
|
|
|
462
465
|
// cross-company / admin resolutions that intentionally target a company the
|
|
463
466
|
// caller isn't a member of (e.g. a group-grant target). A single global
|
|
464
467
|
// match resolves cleanly.
|
|
465
|
-
const res = await fetchGlobalCompanyBySlug(token, ref);
|
|
468
|
+
const res = await fetchGlobalCompanyBySlug(token, ref, baseUrl);
|
|
466
469
|
if (!res.ok) {
|
|
467
470
|
raiseIfUnauthorized(res);
|
|
468
471
|
const body = (await res.json().catch(() => ({})));
|
|
@@ -494,10 +497,11 @@ async function resolveCompanyUid(token, ref) {
|
|
|
494
497
|
const data = (await res.json());
|
|
495
498
|
return data.entity.uid;
|
|
496
499
|
}
|
|
497
|
-
async function resolveCompanyFromMemberships(token) {
|
|
500
|
+
async function resolveCompanyFromMemberships(token, baseUrl) {
|
|
498
501
|
const res = await vaultApiFetch({
|
|
499
502
|
token,
|
|
500
503
|
path: '/membership/me',
|
|
504
|
+
baseUrl,
|
|
501
505
|
signal: AbortSignal.timeout(identityLookupTimeoutMs()),
|
|
502
506
|
});
|
|
503
507
|
if (!res.ok) {
|
|
@@ -519,7 +523,13 @@ async function resolveCompanyFromMemberships(token) {
|
|
|
519
523
|
`pick one:\n` +
|
|
520
524
|
picks.join('\n'));
|
|
521
525
|
}
|
|
522
|
-
export async function getCompanyUid(token, companySlug
|
|
526
|
+
export async function getCompanyUid(token, companySlug,
|
|
527
|
+
/**
|
|
528
|
+
* Control-plane origin for the lookups. Defaults to DEFAULT_VAULT_API_URL;
|
|
529
|
+
* pass it when the caller is bound to another API (an enrolled agent's
|
|
530
|
+
* `apiBaseUrl`) so its token never goes to the default host.
|
|
531
|
+
*/
|
|
532
|
+
baseUrl) {
|
|
523
533
|
// Vault API keys are company-bound. Entity slug resolution has no keyed
|
|
524
534
|
// route, so under hqk_ we accept an explicit cmp_ uid (or HQ_COMPANY_UID)
|
|
525
535
|
// and otherwise fall through to keyed GET /v1/keys/membership/me.
|
|
@@ -532,12 +542,12 @@ export async function getCompanyUid(token, companySlug) {
|
|
|
532
542
|
}
|
|
533
543
|
return ref;
|
|
534
544
|
}
|
|
535
|
-
return resolveCompanyFromMemberships(token);
|
|
545
|
+
return resolveCompanyFromMemberships(token, baseUrl);
|
|
536
546
|
}
|
|
537
547
|
if (companySlug) {
|
|
538
|
-
return resolveCompanyUid(token, companySlug);
|
|
548
|
+
return resolveCompanyUid(token, companySlug, baseUrl);
|
|
539
549
|
}
|
|
540
|
-
return resolveCompanyFromMemberships(token);
|
|
550
|
+
return resolveCompanyFromMemberships(token, baseUrl);
|
|
541
551
|
}
|
|
542
552
|
// Same selection rule as the backend's `resolveCallerPersonUid`: ascending by
|
|
543
553
|
// createdAt, tie-break by uid ascending. Returns the `prs_*` UID.
|