@mindstudio-ai/remy 0.1.242 → 0.1.244
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/headless.js
CHANGED
|
@@ -557,24 +557,31 @@ function getOrgContext() {
|
|
|
557
557
|
function renderOrgContextBlock() {
|
|
558
558
|
const ctx = cached;
|
|
559
559
|
const auth = ctx?.auth;
|
|
560
|
-
|
|
560
|
+
const hasAuth = !!auth && (auth.delegatedAvailable || !!auth.requireDelegatedOnly);
|
|
561
|
+
const hasDesignSystem = !!ctx?.designSystem;
|
|
562
|
+
if (!hasAuth && !hasDesignSystem) {
|
|
561
563
|
return "";
|
|
562
564
|
}
|
|
563
|
-
const lines = ["<
|
|
565
|
+
const lines = ["<org_context>"];
|
|
564
566
|
if (ctx?.org?.name && ctx?.org?.name !== "Personal Workspace") {
|
|
565
567
|
lines.push(`This app is owned by the organization "${ctx.org.name}".`);
|
|
566
568
|
}
|
|
567
|
-
if (auth
|
|
569
|
+
if (auth?.delegatedAvailable) {
|
|
568
570
|
lines.push(
|
|
569
571
|
'"Sign in with Remy" (platform-delegated sign-in) is an available auth type for this app: organization members can sign in without a verification code.'
|
|
570
572
|
);
|
|
571
573
|
}
|
|
572
|
-
if (auth
|
|
574
|
+
if (auth?.requireDelegatedOnly) {
|
|
573
575
|
lines.push(
|
|
574
576
|
"This organization requires delegated sign-in: non-delegated human auth methods (email-code, sms-code) are blocked at the platform edge for its apps."
|
|
575
577
|
);
|
|
576
578
|
}
|
|
577
|
-
|
|
579
|
+
if (hasDesignSystem) {
|
|
580
|
+
lines.push(
|
|
581
|
+
"This organization maintains shared brand/design foundations. The design expert has access to these and applies them automatically. You don't need to gather foundational visual style or branding requirements from the user."
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
lines.push("</org_context>");
|
|
578
585
|
return lines.join("\n");
|
|
579
586
|
}
|
|
580
587
|
|
|
@@ -3314,6 +3321,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3314
3321
|
let currentToolNames = "";
|
|
3315
3322
|
let lastUsage;
|
|
3316
3323
|
let lastProviderMetadata;
|
|
3324
|
+
let lastModelId;
|
|
3317
3325
|
const statusWatcher = startStatusWatcher({
|
|
3318
3326
|
apiConfig,
|
|
3319
3327
|
getContext: () => {
|
|
@@ -3427,6 +3435,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3427
3435
|
llmCalls: 1
|
|
3428
3436
|
};
|
|
3429
3437
|
lastProviderMetadata = event.providerMetadata;
|
|
3438
|
+
lastModelId = event.modelId;
|
|
3430
3439
|
recordUsage({
|
|
3431
3440
|
ts: Date.now(),
|
|
3432
3441
|
requestId,
|
|
@@ -3465,7 +3474,8 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3465
3474
|
role: "assistant",
|
|
3466
3475
|
content: contentBlocks,
|
|
3467
3476
|
...lastUsage ? { usage: lastUsage } : {},
|
|
3468
|
-
...lastProviderMetadata ? { providerMetadata: lastProviderMetadata } : {}
|
|
3477
|
+
...lastProviderMetadata ? { providerMetadata: lastProviderMetadata } : {},
|
|
3478
|
+
model: lastModelId ?? model
|
|
3469
3479
|
});
|
|
3470
3480
|
const toolCalls = contentBlocks.filter(
|
|
3471
3481
|
(b) => b.type === "tool"
|
|
@@ -7059,6 +7069,7 @@ async function runTurn(params) {
|
|
|
7059
7069
|
apiConfig,
|
|
7060
7070
|
system,
|
|
7061
7071
|
model,
|
|
7072
|
+
buildModel,
|
|
7062
7073
|
onboardingState,
|
|
7063
7074
|
signal,
|
|
7064
7075
|
onEvent,
|
|
@@ -7070,15 +7081,24 @@ async function runTurn(params) {
|
|
|
7070
7081
|
} = params;
|
|
7071
7082
|
const tools2 = getToolDefinitions(onboardingState);
|
|
7072
7083
|
const excludeToolsFromClearing = tools2.filter((t) => !CLEARABLE_TOOLS.has(t.name)).map((t) => t.name);
|
|
7084
|
+
const buildModelOverride = buildModel ? filterModelPicks({ parent: buildModel }).parent : void 0;
|
|
7085
|
+
const baseline = resolveModel("parent", state.models, model);
|
|
7086
|
+
const parentModel = buildModelOverride ?? baseline;
|
|
7087
|
+
const modelOverride = buildModelOverride && buildModelOverride !== baseline ? { from: baseline } : void 0;
|
|
7073
7088
|
log13.info("Turn started", {
|
|
7074
7089
|
requestId,
|
|
7075
7090
|
model,
|
|
7091
|
+
buildModel: buildModelOverride,
|
|
7076
7092
|
toolCount: tools2.length,
|
|
7077
7093
|
...attachments && attachments.length > 0 && {
|
|
7078
7094
|
attachmentCount: attachments.length
|
|
7079
7095
|
}
|
|
7080
7096
|
});
|
|
7081
|
-
onEvent({
|
|
7097
|
+
onEvent({
|
|
7098
|
+
type: "turn_started",
|
|
7099
|
+
model: parentModel,
|
|
7100
|
+
...modelOverride && { modelOverride }
|
|
7101
|
+
});
|
|
7082
7102
|
const hasText = userMessage.trim().length > 0;
|
|
7083
7103
|
const hasAttachments = attachments && attachments.length > 0;
|
|
7084
7104
|
if (!hasText && !hasAttachments) {
|
|
@@ -7145,6 +7165,7 @@ async function runTurn(params) {
|
|
|
7145
7165
|
const toolInputAccumulators = /* @__PURE__ */ new Map();
|
|
7146
7166
|
let stopReason = "end_turn";
|
|
7147
7167
|
let turnProviderMetadata;
|
|
7168
|
+
let turnModelId;
|
|
7148
7169
|
let subAgentText = "";
|
|
7149
7170
|
let currentToolNames = "";
|
|
7150
7171
|
const statusWatcher = isFirstMessage ? { stop() {
|
|
@@ -7226,7 +7247,6 @@ async function runTurn(params) {
|
|
|
7226
7247
|
onEvent({ type: "tool_input_delta", id, name, result: content });
|
|
7227
7248
|
}
|
|
7228
7249
|
}
|
|
7229
|
-
const parentModel = resolveModel("parent", state.models, model);
|
|
7230
7250
|
try {
|
|
7231
7251
|
for await (const event of streamChatWithRetry(
|
|
7232
7252
|
{
|
|
@@ -7346,6 +7366,7 @@ async function runTurn(params) {
|
|
|
7346
7366
|
case "done":
|
|
7347
7367
|
stopReason = event.stopReason;
|
|
7348
7368
|
turnProviderMetadata = event.providerMetadata;
|
|
7369
|
+
turnModelId = event.modelId;
|
|
7349
7370
|
turnLlmCalls++;
|
|
7350
7371
|
lastCallInputTokens = event.usage.inputTokens;
|
|
7351
7372
|
lastCallCacheCreation = event.usage.cacheCreationTokens ?? 0;
|
|
@@ -7406,7 +7427,9 @@ async function runTurn(params) {
|
|
|
7406
7427
|
},
|
|
7407
7428
|
...turnProviderMetadata && {
|
|
7408
7429
|
providerMetadata: turnProviderMetadata
|
|
7409
|
-
}
|
|
7430
|
+
},
|
|
7431
|
+
model: turnModelId ?? parentModel,
|
|
7432
|
+
...modelOverride && { modelOverride }
|
|
7410
7433
|
});
|
|
7411
7434
|
}
|
|
7412
7435
|
onEvent({ type: "turn_cancelled" });
|
|
@@ -7424,7 +7447,9 @@ async function runTurn(params) {
|
|
|
7424
7447
|
cacheReadTokens: turnCacheRead || void 0,
|
|
7425
7448
|
llmCalls: turnLlmCalls
|
|
7426
7449
|
},
|
|
7427
|
-
...turnProviderMetadata && { providerMetadata: turnProviderMetadata }
|
|
7450
|
+
...turnProviderMetadata && { providerMetadata: turnProviderMetadata },
|
|
7451
|
+
model: turnModelId ?? parentModel,
|
|
7452
|
+
...modelOverride && { modelOverride }
|
|
7428
7453
|
});
|
|
7429
7454
|
}
|
|
7430
7455
|
const toolCalls = getToolCalls(contentBlocks);
|
|
@@ -8219,7 +8244,14 @@ var HeadlessSession = class {
|
|
|
8219
8244
|
const rid = this.currentRequestId;
|
|
8220
8245
|
switch (e.type) {
|
|
8221
8246
|
case "turn_started":
|
|
8222
|
-
this.emit(
|
|
8247
|
+
this.emit(
|
|
8248
|
+
"turn_started",
|
|
8249
|
+
{
|
|
8250
|
+
...e.model && { model: e.model },
|
|
8251
|
+
...e.modelOverride && { modelOverride: e.modelOverride }
|
|
8252
|
+
},
|
|
8253
|
+
rid
|
|
8254
|
+
);
|
|
8223
8255
|
return;
|
|
8224
8256
|
case "user_message":
|
|
8225
8257
|
this.emit(
|
|
@@ -8423,7 +8455,9 @@ var HeadlessSession = class {
|
|
|
8423
8455
|
userMessage = resolved.message;
|
|
8424
8456
|
}
|
|
8425
8457
|
const isHidden = !!parsed.hidden;
|
|
8426
|
-
|
|
8458
|
+
const rawText = parsed.text ?? "";
|
|
8459
|
+
applyPlanFileSideEffect(rawText);
|
|
8460
|
+
const buildModel = hasSentinel(rawText, "approvePlan") ? parsed.buildModel : void 0;
|
|
8427
8461
|
const onboardingState = parsed.onboardingState ?? "onboardingFinished";
|
|
8428
8462
|
this.currentOnboardingState = onboardingState;
|
|
8429
8463
|
const system = buildSystemPrompt(
|
|
@@ -8452,6 +8486,7 @@ var HeadlessSession = class {
|
|
|
8452
8486
|
apiConfig: this.config,
|
|
8453
8487
|
system,
|
|
8454
8488
|
model: this.opts.model,
|
|
8489
|
+
buildModel,
|
|
8455
8490
|
onboardingState,
|
|
8456
8491
|
requestId,
|
|
8457
8492
|
signal: this.currentAbort.signal,
|
package/dist/index.js
CHANGED
|
@@ -3884,6 +3884,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3884
3884
|
let currentToolNames = "";
|
|
3885
3885
|
let lastUsage;
|
|
3886
3886
|
let lastProviderMetadata;
|
|
3887
|
+
let lastModelId;
|
|
3887
3888
|
const statusWatcher = startStatusWatcher({
|
|
3888
3889
|
apiConfig,
|
|
3889
3890
|
getContext: () => {
|
|
@@ -3997,6 +3998,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3997
3998
|
llmCalls: 1
|
|
3998
3999
|
};
|
|
3999
4000
|
lastProviderMetadata = event.providerMetadata;
|
|
4001
|
+
lastModelId = event.modelId;
|
|
4000
4002
|
recordUsage({
|
|
4001
4003
|
ts: Date.now(),
|
|
4002
4004
|
requestId,
|
|
@@ -4035,7 +4037,8 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
4035
4037
|
role: "assistant",
|
|
4036
4038
|
content: contentBlocks,
|
|
4037
4039
|
...lastUsage ? { usage: lastUsage } : {},
|
|
4038
|
-
...lastProviderMetadata ? { providerMetadata: lastProviderMetadata } : {}
|
|
4040
|
+
...lastProviderMetadata ? { providerMetadata: lastProviderMetadata } : {},
|
|
4041
|
+
model: lastModelId ?? model
|
|
4039
4042
|
});
|
|
4040
4043
|
const toolCalls = contentBlocks.filter(
|
|
4041
4044
|
(b) => b.type === "tool"
|
|
@@ -5715,24 +5718,31 @@ function getOrgContext() {
|
|
|
5715
5718
|
function renderOrgContextBlock() {
|
|
5716
5719
|
const ctx = cached;
|
|
5717
5720
|
const auth = ctx?.auth;
|
|
5718
|
-
|
|
5721
|
+
const hasAuth = !!auth && (auth.delegatedAvailable || !!auth.requireDelegatedOnly);
|
|
5722
|
+
const hasDesignSystem = !!ctx?.designSystem;
|
|
5723
|
+
if (!hasAuth && !hasDesignSystem) {
|
|
5719
5724
|
return "";
|
|
5720
5725
|
}
|
|
5721
|
-
const lines = ["<
|
|
5726
|
+
const lines = ["<org_context>"];
|
|
5722
5727
|
if (ctx?.org?.name && ctx?.org?.name !== "Personal Workspace") {
|
|
5723
5728
|
lines.push(`This app is owned by the organization "${ctx.org.name}".`);
|
|
5724
5729
|
}
|
|
5725
|
-
if (auth
|
|
5730
|
+
if (auth?.delegatedAvailable) {
|
|
5726
5731
|
lines.push(
|
|
5727
5732
|
'"Sign in with Remy" (platform-delegated sign-in) is an available auth type for this app: organization members can sign in without a verification code.'
|
|
5728
5733
|
);
|
|
5729
5734
|
}
|
|
5730
|
-
if (auth
|
|
5735
|
+
if (auth?.requireDelegatedOnly) {
|
|
5731
5736
|
lines.push(
|
|
5732
5737
|
"This organization requires delegated sign-in: non-delegated human auth methods (email-code, sms-code) are blocked at the platform edge for its apps."
|
|
5733
5738
|
);
|
|
5734
5739
|
}
|
|
5735
|
-
|
|
5740
|
+
if (hasDesignSystem) {
|
|
5741
|
+
lines.push(
|
|
5742
|
+
"This organization maintains shared brand/design foundations. The design expert has access to these and applies them automatically. You don't need to gather foundational visual style or branding requirements from the user."
|
|
5743
|
+
);
|
|
5744
|
+
}
|
|
5745
|
+
lines.push("</org_context>");
|
|
5736
5746
|
return lines.join("\n");
|
|
5737
5747
|
}
|
|
5738
5748
|
var log9, cached;
|
|
@@ -7502,6 +7512,7 @@ async function runTurn(params) {
|
|
|
7502
7512
|
apiConfig,
|
|
7503
7513
|
system,
|
|
7504
7514
|
model,
|
|
7515
|
+
buildModel,
|
|
7505
7516
|
onboardingState,
|
|
7506
7517
|
signal,
|
|
7507
7518
|
onEvent,
|
|
@@ -7513,15 +7524,24 @@ async function runTurn(params) {
|
|
|
7513
7524
|
} = params;
|
|
7514
7525
|
const tools2 = getToolDefinitions(onboardingState);
|
|
7515
7526
|
const excludeToolsFromClearing = tools2.filter((t) => !CLEARABLE_TOOLS.has(t.name)).map((t) => t.name);
|
|
7527
|
+
const buildModelOverride = buildModel ? filterModelPicks({ parent: buildModel }).parent : void 0;
|
|
7528
|
+
const baseline = resolveModel("parent", state.models, model);
|
|
7529
|
+
const parentModel = buildModelOverride ?? baseline;
|
|
7530
|
+
const modelOverride = buildModelOverride && buildModelOverride !== baseline ? { from: baseline } : void 0;
|
|
7516
7531
|
log12.info("Turn started", {
|
|
7517
7532
|
requestId,
|
|
7518
7533
|
model,
|
|
7534
|
+
buildModel: buildModelOverride,
|
|
7519
7535
|
toolCount: tools2.length,
|
|
7520
7536
|
...attachments && attachments.length > 0 && {
|
|
7521
7537
|
attachmentCount: attachments.length
|
|
7522
7538
|
}
|
|
7523
7539
|
});
|
|
7524
|
-
onEvent({
|
|
7540
|
+
onEvent({
|
|
7541
|
+
type: "turn_started",
|
|
7542
|
+
model: parentModel,
|
|
7543
|
+
...modelOverride && { modelOverride }
|
|
7544
|
+
});
|
|
7525
7545
|
const hasText = userMessage.trim().length > 0;
|
|
7526
7546
|
const hasAttachments = attachments && attachments.length > 0;
|
|
7527
7547
|
if (!hasText && !hasAttachments) {
|
|
@@ -7588,6 +7608,7 @@ async function runTurn(params) {
|
|
|
7588
7608
|
const toolInputAccumulators = /* @__PURE__ */ new Map();
|
|
7589
7609
|
let stopReason = "end_turn";
|
|
7590
7610
|
let turnProviderMetadata;
|
|
7611
|
+
let turnModelId;
|
|
7591
7612
|
let subAgentText = "";
|
|
7592
7613
|
let currentToolNames = "";
|
|
7593
7614
|
const statusWatcher = isFirstMessage ? { stop() {
|
|
@@ -7669,7 +7690,6 @@ async function runTurn(params) {
|
|
|
7669
7690
|
onEvent({ type: "tool_input_delta", id, name, result: content });
|
|
7670
7691
|
}
|
|
7671
7692
|
}
|
|
7672
|
-
const parentModel = resolveModel("parent", state.models, model);
|
|
7673
7693
|
try {
|
|
7674
7694
|
for await (const event of streamChatWithRetry(
|
|
7675
7695
|
{
|
|
@@ -7789,6 +7809,7 @@ async function runTurn(params) {
|
|
|
7789
7809
|
case "done":
|
|
7790
7810
|
stopReason = event.stopReason;
|
|
7791
7811
|
turnProviderMetadata = event.providerMetadata;
|
|
7812
|
+
turnModelId = event.modelId;
|
|
7792
7813
|
turnLlmCalls++;
|
|
7793
7814
|
lastCallInputTokens = event.usage.inputTokens;
|
|
7794
7815
|
lastCallCacheCreation = event.usage.cacheCreationTokens ?? 0;
|
|
@@ -7849,7 +7870,9 @@ async function runTurn(params) {
|
|
|
7849
7870
|
},
|
|
7850
7871
|
...turnProviderMetadata && {
|
|
7851
7872
|
providerMetadata: turnProviderMetadata
|
|
7852
|
-
}
|
|
7873
|
+
},
|
|
7874
|
+
model: turnModelId ?? parentModel,
|
|
7875
|
+
...modelOverride && { modelOverride }
|
|
7853
7876
|
});
|
|
7854
7877
|
}
|
|
7855
7878
|
onEvent({ type: "turn_cancelled" });
|
|
@@ -7867,7 +7890,9 @@ async function runTurn(params) {
|
|
|
7867
7890
|
cacheReadTokens: turnCacheRead || void 0,
|
|
7868
7891
|
llmCalls: turnLlmCalls
|
|
7869
7892
|
},
|
|
7870
|
-
...turnProviderMetadata && { providerMetadata: turnProviderMetadata }
|
|
7893
|
+
...turnProviderMetadata && { providerMetadata: turnProviderMetadata },
|
|
7894
|
+
model: turnModelId ?? parentModel,
|
|
7895
|
+
...modelOverride && { modelOverride }
|
|
7871
7896
|
});
|
|
7872
7897
|
}
|
|
7873
7898
|
const toolCalls = getToolCalls(contentBlocks);
|
|
@@ -9108,7 +9133,14 @@ var init_headless = __esm({
|
|
|
9108
9133
|
const rid = this.currentRequestId;
|
|
9109
9134
|
switch (e.type) {
|
|
9110
9135
|
case "turn_started":
|
|
9111
|
-
this.emit(
|
|
9136
|
+
this.emit(
|
|
9137
|
+
"turn_started",
|
|
9138
|
+
{
|
|
9139
|
+
...e.model && { model: e.model },
|
|
9140
|
+
...e.modelOverride && { modelOverride: e.modelOverride }
|
|
9141
|
+
},
|
|
9142
|
+
rid
|
|
9143
|
+
);
|
|
9112
9144
|
return;
|
|
9113
9145
|
case "user_message":
|
|
9114
9146
|
this.emit(
|
|
@@ -9312,7 +9344,9 @@ var init_headless = __esm({
|
|
|
9312
9344
|
userMessage = resolved.message;
|
|
9313
9345
|
}
|
|
9314
9346
|
const isHidden = !!parsed.hidden;
|
|
9315
|
-
|
|
9347
|
+
const rawText = parsed.text ?? "";
|
|
9348
|
+
applyPlanFileSideEffect(rawText);
|
|
9349
|
+
const buildModel = hasSentinel(rawText, "approvePlan") ? parsed.buildModel : void 0;
|
|
9316
9350
|
const onboardingState = parsed.onboardingState ?? "onboardingFinished";
|
|
9317
9351
|
this.currentOnboardingState = onboardingState;
|
|
9318
9352
|
const system = buildSystemPrompt(
|
|
@@ -9341,6 +9375,7 @@ var init_headless = __esm({
|
|
|
9341
9375
|
apiConfig: this.config,
|
|
9342
9376
|
system,
|
|
9343
9377
|
model: this.opts.model,
|
|
9378
|
+
buildModel,
|
|
9344
9379
|
onboardingState,
|
|
9345
9380
|
requestId,
|
|
9346
9381
|
signal: this.currentAbort.signal,
|
|
@@ -34,7 +34,7 @@ Remy apps can have and manage their own users. Auth is opt-in: configure it in t
|
|
|
34
34
|
- `email-code` — 6-digit code sent via email
|
|
35
35
|
- `sms-code` — 6-digit code sent via SMS
|
|
36
36
|
- `api-key` — programmatic access via `Authorization: Bearer sk_...` header. Resolves to a user with full RBAC.
|
|
37
|
-
- `remy` — platform-delegated sign-in ("Sign in with Remy"). The platform resolves who the user is; roles and verification are platform-managed. Only usable when the app's owning organization has it enabled — the `<
|
|
37
|
+
- `remy` — platform-delegated sign-in ("Sign in with Remy"). The platform resolves who the user is; roles and verification are platform-managed. Only usable when the app's owning organization has it enabled — the `<org_context>` block signals availability. See *Organization-Managed Sign-In* above.
|
|
38
38
|
- **`auth.table.name`** — name of the `defineTable` table that holds user records.
|
|
39
39
|
- **`auth.table.columns`** — maps platform-managed fields to column names in the developer's table.
|
|
40
40
|
- `email` — required if `email-code` is in methods
|
|
@@ -135,12 +135,12 @@ const user = await auth.verifySmsCode(verificationId, '123456');
|
|
|
135
135
|
|
|
136
136
|
## Organization-Managed Sign-In ("Sign in with Remy")
|
|
137
137
|
|
|
138
|
-
Some apps are owned by an organization that centralizes sign-in. When that applies to the current app, the system prompt includes an `<
|
|
138
|
+
Some apps are owned by an organization that centralizes sign-in. When that applies to the current app, the system prompt includes an `<org_context>` block (near the end); for org-managed sign-in it states the organization name and whether delegated sign-in is available. Sign in with Remy is a way to make authentication seamless for internal apps - it should not be used for public-facing applications. The app owner will need to add users to their workspace's team on the Remy platform and those users will need Remy accounts for it to work.
|
|
139
139
|
|
|
140
140
|
- **When it says "Sign in with Remy" is available** — offer delegated sign-in: a **"Continue with {Org}"** button wired to the `remy` method (see *Sign in with Remy (delegated)* below). Use the exact organization name from the block for the label. For an org-owned app this is usually the primary sign-in — the members already have platform identities, so a verification-code form is redundant.
|
|
141
141
|
- **When it says the organization requires delegated sign-in** — `remy` is the *only* human method: do not add `email-code` or `sms-code`. Those are blocked at the platform edge for the org's apps, so building them yields a login that can't work.
|
|
142
142
|
|
|
143
|
-
When
|
|
143
|
+
When the `<org_context>` block is absent, or present without a delegated-sign-in line — the common case — do not build it or offer to build it. This is an auth scheme for enterprises building internal apps only, and it requires a Remy enterprise plan to use. When enabled, the platform decides who the user is (like "Sign in with Google"); the app just starts the flow and reads the result.
|
|
144
144
|
|
|
145
145
|
```typescript
|
|
146
146
|
// "Continue with {Org}" button — must be triggered by a user gesture (click).
|
|
@@ -399,7 +399,7 @@ Consult the `visualDesignExpert` to help you work through authentication at a hi
|
|
|
399
399
|
### Rules for Building Auth Screens
|
|
400
400
|
**Auth modes:** Think about which mode(s) makes the most sense for the type of app you are building. Consumer apps likely to be used on mobile should probably tend toward SMS auth as the default - business apps used on desktop make more sense to use email verification - or allow both, there's no harm in giving the user choice!
|
|
401
401
|
|
|
402
|
-
**"Continue with {Org}" (delegated):** When `<
|
|
402
|
+
**"Continue with {Org}" (delegated):** When the `<org_context>` block says delegated sign-in is available, a single "Continue with {Org}" button is the primary path — often the *only* one — and there's no verification-code step to design at all (the platform handles it). Give the button real weight in the branded login moment rather than treating it as a secondary option, and use the exact organization name. If the org also allows code methods, delegated goes first with the code form beneath. On return from the handshake (and on dashboard launch), render a brief "Completing sign-in…" state driven by `auth.authStatus === 'authenticating'` — never the login form — so a successful sign-in doesn't flash the logged-out screen.
|
|
403
403
|
|
|
404
404
|
**Verification code input:** The 6-digit code entry is the critical moment. Prefer to design it as individual digit boxes (not a single text input), with auto-advance between digits, a beautiful animation and auto-submit on paste, and clear visual feedback. The boxes should be large enough to tap easily on mobile. Show a subtle animation on successful verification. Error states should be inline and immediate, not a separate alert. Make sure there is no layout shift when loading in the success/error states - loading spinners must never pop in below the input and shift the content, for example.
|
|
405
405
|
|
|
@@ -20,7 +20,7 @@ Login and signup screens set the tone for the user's entire experience with the
|
|
|
20
20
|
|
|
21
21
|
Authentication moments must feel natural and intuitive - they should not feel jarring or surprising. Take care to integrate them into the entire experience when building. Remy apps support SMS code verification, email verification, delegated "Sign in with Remy", or a combination, depending on how the app is configured.
|
|
22
22
|
|
|
23
|
-
**"Continue with {Org}" (delegated sign-in):** Some apps are internal business tools that are owned by an organization and sign members in through the platform — a single "Continue with {Org}" button, no verification-code step at all. For these apps this button is often the primary (sometimes only) path, so give it real presence in the branded login moment rather than tucking it away, and label it with the organization's actual name. If the app also offers code methods, lead with the delegated button and place the code form beneath. This scheme should ONLY be used for internal apps AND when it is explicitly enabled in <
|
|
23
|
+
**"Continue with {Org}" (delegated sign-in):** Some apps are internal business tools that are owned by an organization and sign members in through the platform — a single "Continue with {Org}" button, no verification-code step at all. For these apps this button is often the primary (sometimes only) path, so give it real presence in the branded login moment rather than tucking it away, and label it with the organization's actual name. If the app also offers code methods, lead with the delegated button and place the code form beneath. This scheme should ONLY be used for internal apps AND when it is explicitly enabled in <org_context> - it should not be used for public-facing apps.
|
|
24
24
|
|
|
25
25
|
**Verification code input:** The 6-digit code entry is the critical moment. Prefer to design it as individual digit boxes (not a single text input), with auto-advance between digits, auto-submit on paste, and clear visual feedback. The boxes should be large enough to tap easily on mobile. Show a subtle animation on successful verification. Error states should be inline and immediate, not a separate alert.
|
|
26
26
|
|