@optimuslabs/harness-map-staging 1.5.28 → 1.5.30
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.
|
@@ -62,6 +62,7 @@ export function readClaudeSignedInTenant(env = process.env) {
|
|
|
62
62
|
tenant_name: text('organizationName'),
|
|
63
63
|
email: text('emailAddress'),
|
|
64
64
|
account_uuid: text('accountUuid'),
|
|
65
|
+
plan: text('organizationType').toLowerCase().replace(/^claude_/, ''),
|
|
65
66
|
};
|
|
66
67
|
}
|
|
67
68
|
catch {
|
|
@@ -69,14 +70,74 @@ export function readClaudeSignedInTenant(env = process.env) {
|
|
|
69
70
|
return null;
|
|
70
71
|
}
|
|
71
72
|
}
|
|
72
|
-
/**
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Block messages, one shape for every agent (Optimus Labs' prompt-block wording):
|
|
75
|
+
* "This device only allows <agent> <under/on/in> the <approved> <kind>. You're signed in to
|
|
76
|
+
* <what>. <how to switch>." With nothing approved yet: "This device doesn't allow <agent> signed in
|
|
77
|
+
* to <what>." and the same last sentence.
|
|
78
|
+
*/
|
|
79
|
+
const TENANT_WORDS = {
|
|
80
|
+
claude: { name: 'Claude Code', kind: 'organization', prep: 'under' },
|
|
81
|
+
claude_desktop: { name: 'Claude Desktop', kind: 'organization', prep: 'under' },
|
|
82
|
+
cursor: { name: 'Cursor', kind: 'team', prep: 'on' },
|
|
83
|
+
codex: { name: 'Codex', kind: 'workspace', prep: 'in' },
|
|
84
|
+
};
|
|
85
|
+
/** A tenant's name as the admin approved it, or its id when it has none. */
|
|
86
|
+
function nameOf(t) {
|
|
87
|
+
return (t.tenant_name || '').trim() || t.tenant_id.trim();
|
|
88
|
+
}
|
|
89
|
+
function emailDomain(email) {
|
|
90
|
+
const address = (email ?? '').trim().toLowerCase();
|
|
91
|
+
const at = address.lastIndexOf('@');
|
|
92
|
+
return at >= 0 && at < address.length - 1 ? address.slice(at + 1) : '';
|
|
77
93
|
}
|
|
78
|
-
function
|
|
79
|
-
return
|
|
94
|
+
function capitalised(word) {
|
|
95
|
+
return word ? word[0].toUpperCase() + word.slice(1) : word;
|
|
96
|
+
}
|
|
97
|
+
/** What to pick to get back in: the one approved tenant, one of several, or the company's own. */
|
|
98
|
+
function choice(approved, kind) {
|
|
99
|
+
if (approved.length === 1)
|
|
100
|
+
return approved[0];
|
|
101
|
+
return approved.length > 1 ? 'one of them' : `your company's ${kind}`;
|
|
102
|
+
}
|
|
103
|
+
function switchStep(agent, approved) {
|
|
104
|
+
const kind = TENANT_WORDS[agent].kind;
|
|
105
|
+
if (agent === 'claude_desktop')
|
|
106
|
+
return `Switch Claude Desktop to ${choice(approved, kind)}.`;
|
|
107
|
+
if (agent === 'codex')
|
|
108
|
+
return `Run codex logout, then codex login and choose ${choice(approved, kind)}.`;
|
|
109
|
+
if (agent === 'cursor') {
|
|
110
|
+
if (approved.length === 1)
|
|
111
|
+
return `Sign out of Cursor and sign in with your ${approved[0]} team account.`;
|
|
112
|
+
return approved.length > 1
|
|
113
|
+
? 'Sign out of Cursor and sign in with an account on one of those teams.'
|
|
114
|
+
: "Sign out of Cursor and sign in with your company's team account.";
|
|
115
|
+
}
|
|
116
|
+
return `Run /login and choose ${choice(approved, kind)}.`;
|
|
117
|
+
}
|
|
118
|
+
/** The block message for `agent` signed in to `signedIn` (already worded) with these approved. */
|
|
119
|
+
export function tenantBlockMessage(agent, approved, signedIn) {
|
|
120
|
+
const w = TENANT_WORDS[agent];
|
|
121
|
+
const lead = approved.length
|
|
122
|
+
? `This device only allows ${w.name} ${w.prep} the ${approved.join(' or ')} ${w.kind}. ` +
|
|
123
|
+
`You're signed in to ${signedIn}.`
|
|
124
|
+
: `This device doesn't allow ${w.name} signed in to ${signedIn}.`;
|
|
125
|
+
return `${lead} ${switchStep(agent, approved)}`;
|
|
126
|
+
}
|
|
127
|
+
const CLAUDE_PERSONAL_PLANS = new Set(['free', 'pro', 'max']);
|
|
128
|
+
/** "a personal Max account (gmail.com)" or "the Beta Corp organization". */
|
|
129
|
+
function claudeSignedInText(signedIn) {
|
|
130
|
+
const plan = (signedIn.plan ?? '').trim().toLowerCase();
|
|
131
|
+
const domain = emailDomain(signedIn.email);
|
|
132
|
+
const at = domain ? ` (${domain})` : '';
|
|
133
|
+
// A personal claude.ai org is named after its owner: "ada@gmail.com's Organization".
|
|
134
|
+
if (CLAUDE_PERSONAL_PLANS.has(plan) || /'s organization$/i.test(signedIn.tenant_name)) {
|
|
135
|
+
const planWord = CLAUDE_PERSONAL_PLANS.has(plan) ? `${capitalised(plan)} ` : '';
|
|
136
|
+
return `a personal ${planWord}account${at}`;
|
|
137
|
+
}
|
|
138
|
+
if (signedIn.tenant_name)
|
|
139
|
+
return `the ${signedIn.tenant_name} organization`;
|
|
140
|
+
return `an organization your company has not approved${at}`;
|
|
80
141
|
}
|
|
81
142
|
function tenantIds(tenants) {
|
|
82
143
|
return (tenants ?? []).filter((t) => t && typeof t.tenant_id === 'string' && t.tenant_id.trim());
|
|
@@ -96,21 +157,11 @@ export function approvedTenantViolation(agent, approved, signedIn) {
|
|
|
96
157
|
const unapprovedTenants = tenantIds(policy?.unapproved_tenants);
|
|
97
158
|
const signedInId = signedIn.tenant_id.trim();
|
|
98
159
|
const isIn = (list) => list.some((t) => t.tenant_id.trim() === signedInId);
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
else if ((policy?.enforced || approvedTenants.length > 0) && !isIn(approvedTenants)) {
|
|
104
|
-
reason = 'which your organization has not approved';
|
|
105
|
-
}
|
|
106
|
-
else {
|
|
160
|
+
const blocked = isIn(unapprovedTenants) ||
|
|
161
|
+
((policy?.enforced || approvedTenants.length > 0) && !isIn(approvedTenants));
|
|
162
|
+
if (!blocked)
|
|
107
163
|
return null;
|
|
108
|
-
|
|
109
|
-
const allowed = approvedTenants.map((t) => label(t.tenant_name, t.tenant_id)).join(', ');
|
|
110
|
-
return (`this session is signed into ${label(signedIn.tenant_name, signedIn.tenant_id)}, ${reason}. ` +
|
|
111
|
-
(allowed
|
|
112
|
-
? `Approved: ${allowed}. ${fix(agent, 'an approved')}`
|
|
113
|
-
: fix(agent, 'a different')));
|
|
164
|
+
return tenantBlockMessage(agent, approvedTenants.map(nameOf), claudeSignedInText(signedIn));
|
|
114
165
|
}
|
|
115
166
|
/**
|
|
116
167
|
* The tenants a Cursor login is in: its teams, or, on no team, `@` and its email domain. Mirrors the
|
|
@@ -142,49 +193,72 @@ export function readCursorSignedInTeams(home = homedir()) {
|
|
|
142
193
|
* the login; failing that, one marked unapproved is always blocked, and any other -- an unknown
|
|
143
194
|
* tenant, or a personal account -- only once at least one tenant is approved for this agent.
|
|
144
195
|
*/
|
|
145
|
-
function listedTenantViolation(policy, tenants
|
|
196
|
+
function listedTenantViolation(policy, tenants) {
|
|
146
197
|
const approvedList = tenantIds(policy?.tenants);
|
|
147
198
|
if (tenants.some((id) => isListed(approvedList, id)))
|
|
148
199
|
return null;
|
|
149
|
-
const
|
|
150
|
-
if (
|
|
200
|
+
const enforced = approvedList.length > 0 || Boolean(policy?.enforced);
|
|
201
|
+
if (!isRefused(tenants, enforced, tenantIds(policy?.unapproved_tenants)))
|
|
151
202
|
return null;
|
|
152
|
-
return {
|
|
203
|
+
return { approved: approvedList.map(nameOf) };
|
|
153
204
|
}
|
|
154
205
|
function isListed(list, id) {
|
|
155
206
|
return list.some((t) => t.tenant_id.trim() === id);
|
|
156
207
|
}
|
|
157
|
-
|
|
158
|
-
|
|
208
|
+
/** Whether a login in these tenants is refused: one marked unapproved always; any other only once
|
|
209
|
+
* the agent is enforced (an older backend that sends no flag, with nothing approved, lets it pass). */
|
|
210
|
+
function isRefused(tenants, enforced, unapprovedList) {
|
|
211
|
+
if (tenants.some((id) => isListed(unapprovedList, id)))
|
|
212
|
+
return true;
|
|
213
|
+
return enforced;
|
|
159
214
|
}
|
|
160
|
-
/**
|
|
161
|
-
function
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
215
|
+
/** A tenant's name from the organization's lists, or '' when it has none there. */
|
|
216
|
+
function knownName(policy, id) {
|
|
217
|
+
const all = [...tenantIds(policy?.tenants), ...tenantIds(policy?.unapproved_tenants)];
|
|
218
|
+
const hit = all.find((t) => t.tenant_id.trim() === id);
|
|
219
|
+
return (hit?.tenant_name || '').trim();
|
|
220
|
+
}
|
|
221
|
+
/** "a personal account on no team (gmail.com)", "the Beta team" or "team 9876". */
|
|
222
|
+
function cursorSignedInText(signedIn, policy) {
|
|
223
|
+
const tenants = cursorTenants(signedIn);
|
|
224
|
+
if (!tenants.length || tenants[0].startsWith('@')) {
|
|
225
|
+
const domain = emailDomain(signedIn.email) || (tenants[0] ?? '').slice(1);
|
|
226
|
+
return `a personal account on no team${domain ? ` (${domain})` : ''}`;
|
|
227
|
+
}
|
|
228
|
+
return tenants
|
|
229
|
+
.map((id) => (knownName(policy, id) ? `the ${knownName(policy, id)} team` : `team ${id}`))
|
|
230
|
+
.join(' and ');
|
|
231
|
+
}
|
|
232
|
+
/** "a personal ChatGPT Plus account (gmail.com)", "the Beta workspace" or "workspace ws-1". */
|
|
233
|
+
function codexSignedInText(signedIn, tenants, policy) {
|
|
234
|
+
const tenant = tenants[0] ?? '';
|
|
235
|
+
if (!tenant || tenant.startsWith('@')) {
|
|
236
|
+
const plan = capitalised((signedIn.plan ?? '').trim().toLowerCase());
|
|
237
|
+
const domain = emailDomain(signedIn.email) || tenant.slice(1);
|
|
238
|
+
return `a personal ChatGPT ${plan ? `${plan} ` : ''}account${domain ? ` (${domain})` : ''}`;
|
|
239
|
+
}
|
|
240
|
+
const name = knownName(policy, tenant);
|
|
241
|
+
return name ? `the ${name} workspace` : `workspace ${tenant}`;
|
|
171
242
|
}
|
|
172
243
|
/** The block message when Cursor's login may not be used, else null. */
|
|
173
244
|
export function cursorTeamViolation(approved, signedIn) {
|
|
174
245
|
if (!signedIn)
|
|
175
246
|
return null;
|
|
176
|
-
const v = listedTenantViolation(approved?.cursor, cursorTenants(signedIn)
|
|
247
|
+
const v = listedTenantViolation(approved?.cursor, cursorTenants(signedIn));
|
|
177
248
|
if (!v)
|
|
178
249
|
return null;
|
|
179
|
-
return (
|
|
180
|
-
(v.allowed ? `Approved: ${v.allowed}. ` : '') +
|
|
181
|
-
'Sign out of Cursor and sign in with an account on an approved team.');
|
|
250
|
+
return tenantBlockMessage('cursor', v.approved, cursorSignedInText(signedIn, approved?.cursor));
|
|
182
251
|
}
|
|
183
252
|
/**
|
|
184
253
|
* Codex plans that are personal, mirroring the backend's `CodexPlans.PERSONAL`. On one of these
|
|
185
254
|
* the ChatGPT account is the person's own, so the login is keyed by email domain instead.
|
|
186
255
|
*/
|
|
187
256
|
const CODEX_PERSONAL_PLANS = new Set(['free', 'go', 'plus']);
|
|
257
|
+
/**
|
|
258
|
+
* Codex work plans, mirroring the backend's `CodexPlans.WORK`: only these are a shared workspace.
|
|
259
|
+
* Any other plan with a ChatGPT login (Pro, or one not recognised) is a personal account too.
|
|
260
|
+
*/
|
|
261
|
+
const CODEX_WORK_PLANS = new Set(['business', 'hc', 'enterprise', 'edu', 'education']);
|
|
188
262
|
/** Codex's login from ~/.codex/auth.json (claims only, never tokens), or null when there is none. */
|
|
189
263
|
export function readCodexSignedIn(home = homedir()) {
|
|
190
264
|
const identity = readCodexIdentity(home);
|
|
@@ -192,31 +266,34 @@ export function readCodexSignedIn(home = homedir()) {
|
|
|
192
266
|
}
|
|
193
267
|
/**
|
|
194
268
|
* The tenant a Codex login is in, mirroring the backend's `codex_tenants`: its ChatGPT workspace
|
|
195
|
-
* (`chatgpt_account_id`, the claim Codex's own `forced_chatgpt_workspace_id` checks),
|
|
196
|
-
* `@<email domain
|
|
269
|
+
* (`chatgpt_account_id`, the claim Codex's own `forced_chatgpt_workspace_id` checks) on a work plan,
|
|
270
|
+
* else `@<email domain>`, a personal account. Null when there is no ChatGPT login (an API key).
|
|
197
271
|
*/
|
|
198
272
|
export function codexTenants(signedIn) {
|
|
199
273
|
const plan = (signedIn.plan ?? '').trim().toLowerCase();
|
|
200
|
-
if (CODEX_PERSONAL_PLANS.has(plan))
|
|
201
|
-
|
|
202
|
-
const at = email.lastIndexOf('@');
|
|
203
|
-
return at >= 0 && at < email.length - 1 ? [`@${email.slice(at + 1).toLowerCase()}`] : [];
|
|
204
|
-
}
|
|
274
|
+
if (CODEX_PERSONAL_PLANS.has(plan))
|
|
275
|
+
return codexEmailDomainTenant(signedIn);
|
|
205
276
|
const account = (signedIn.account_id ?? '').trim();
|
|
206
277
|
// The collector falls back to the API organization ("org-...") with no ChatGPT account.
|
|
207
|
-
|
|
278
|
+
if (!account || account.startsWith('org-'))
|
|
279
|
+
return null;
|
|
280
|
+
return CODEX_WORK_PLANS.has(plan) ? [account] : codexEmailDomainTenant(signedIn);
|
|
281
|
+
}
|
|
282
|
+
/** `@<email domain>` for a Codex login, or [] when it has no email: how a personal account is keyed. */
|
|
283
|
+
function codexEmailDomainTenant(signedIn) {
|
|
284
|
+
const email = (signedIn.email ?? '').trim();
|
|
285
|
+
const at = email.lastIndexOf('@');
|
|
286
|
+
return at >= 0 && at < email.length - 1 ? [`@${email.slice(at + 1).toLowerCase()}`] : [];
|
|
208
287
|
}
|
|
209
288
|
/** The block message when Codex's login may not be used, else null. */
|
|
210
289
|
export function codexTenantViolation(approved, signedIn) {
|
|
211
290
|
const tenants = signedIn ? codexTenants(signedIn) : null;
|
|
212
291
|
if (!tenants)
|
|
213
292
|
return null;
|
|
214
|
-
const v = listedTenantViolation(approved?.codex, tenants
|
|
293
|
+
const v = listedTenantViolation(approved?.codex, tenants);
|
|
215
294
|
if (!v)
|
|
216
295
|
return null;
|
|
217
|
-
return (
|
|
218
|
-
(v.allowed ? `Approved: ${v.allowed}. ` : '') +
|
|
219
|
-
'Run `codex logout`, then sign in to an approved workspace.');
|
|
296
|
+
return tenantBlockMessage('codex', v.approved, codexSignedInText(signedIn, tenants, approved?.codex));
|
|
220
297
|
}
|
|
221
298
|
/**
|
|
222
299
|
* Whether a personal account's `@<domain>` tenant is on a team domain: an allowlisted domain that
|
|
@@ -266,6 +343,7 @@ export function readClaudeDesktopSignedInTenant(home = homedir(), env = process.
|
|
|
266
343
|
tenant_name: same ? fromClaudeCode.tenant_name : '',
|
|
267
344
|
email: same ? fromClaudeCode.email : '',
|
|
268
345
|
account_uuid: account || (same ? fromClaudeCode.account_uuid : ''),
|
|
346
|
+
plan: same ? fromClaudeCode.plan : '',
|
|
269
347
|
};
|
|
270
348
|
}
|
|
271
349
|
/**
|
|
@@ -283,8 +361,8 @@ export function unapprovedEmailDomainViolation(email, unapproved) {
|
|
|
283
361
|
const hit = unapproved.find((d) => domain === d || domain.endsWith(`.${d}`));
|
|
284
362
|
if (!hit)
|
|
285
363
|
return null;
|
|
286
|
-
return (`
|
|
287
|
-
'Sign in with
|
|
364
|
+
return (`This device doesn't allow accounts on ${hit}. You're signed in as ${address}. ` +
|
|
365
|
+
'Sign in with your work account.');
|
|
288
366
|
}
|
|
289
367
|
/**
|
|
290
368
|
* The block message when a login's email is not on the organization's email allowlist, else null.
|
|
@@ -303,8 +381,8 @@ export function emailAllowlistViolation(email, allowed) {
|
|
|
303
381
|
const domain = address.slice(at + 1);
|
|
304
382
|
if (allowed.some((d) => domain === d || domain.endsWith(`.${d}`)))
|
|
305
383
|
return null;
|
|
306
|
-
return (`
|
|
307
|
-
'Sign in with
|
|
384
|
+
return (`This device only allows accounts on your organization's email domains. You're signed in as ${address}. ` +
|
|
385
|
+
'Sign in with your work account.');
|
|
308
386
|
}
|
|
309
387
|
/** The id an agent is approved under on the Agents page: Desktop's Code tab runs Claude Code. */
|
|
310
388
|
const APPROVAL_ID = { claude_desktop: 'claude' };
|
|
@@ -87,9 +87,10 @@ function printAllowWithAdvisory(ide, advisoryMessage) {
|
|
|
87
87
|
}));
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
|
+
/** Every block reads as an Optimus Labs notice: a title line, then the message. */
|
|
91
|
+
const BLOCK_TITLE = 'Optimus Labs\n\n';
|
|
90
92
|
function blockPayload(ide, violationMessage) {
|
|
91
|
-
const
|
|
92
|
-
const text = prefix + violationMessage;
|
|
93
|
+
const text = BLOCK_TITLE + violationMessage;
|
|
93
94
|
if (ide === 'claude' || ide === 'copilot' || ide === 'opencode') {
|
|
94
95
|
return JSON.stringify({ decision: 'block', reason: text, systemMessage: text });
|
|
95
96
|
}
|
|
@@ -100,7 +101,7 @@ function blockPayload(ide, violationMessage) {
|
|
|
100
101
|
* warning, so the Claude-shaped payload (which carries both) prints the same sentence twice.
|
|
101
102
|
*/
|
|
102
103
|
function codexBlockPayload(violationMessage) {
|
|
103
|
-
return JSON.stringify({ decision: 'block', reason:
|
|
104
|
+
return JSON.stringify({ decision: 'block', reason: BLOCK_TITLE + violationMessage });
|
|
104
105
|
}
|
|
105
106
|
function getManifestStalenessMs() {
|
|
106
107
|
try {
|
|
@@ -126,11 +126,17 @@ function claudeJsonDetails(home, account, orgUuids) {
|
|
|
126
126
|
const name = text(oauth.organizationName);
|
|
127
127
|
if (name)
|
|
128
128
|
details.orgNames[org] = name;
|
|
129
|
-
|
|
129
|
+
// The org's own type first: the login token's plan describes whichever org the token is for now,
|
|
130
|
+
// which is not this one while ~/.claude.json still caches the org from before a move to a team.
|
|
131
|
+
const plan = claudeOrgPlan(oauth) || readClaudeLoginPlan(home) || claudeCodePlan(claudeJson, oauth);
|
|
130
132
|
if (plan)
|
|
131
133
|
details.orgPlans[org] = plan;
|
|
132
134
|
return details;
|
|
133
135
|
}
|
|
136
|
+
/** The plan of the org `oauthAccount` names, fetched with it: its type, else its seat tier. */
|
|
137
|
+
function claudeOrgPlan(oauth) {
|
|
138
|
+
return text(oauth.organizationType) || text(oauth.seatTier);
|
|
139
|
+
}
|
|
134
140
|
/** Raw, in the order the backend reads Claude Code's plan, so both normalise alike. */
|
|
135
141
|
function claudeCodePlan(claudeJson, oauth) {
|
|
136
142
|
return (text(claudeJson?.subscriptionType) ||
|