Skip to content

Commit 0d15d0b

Browse files
authored
Allow pending onboarding auth retry (#37)
1 parent 8e194dc commit 0d15d0b

5 files changed

Lines changed: 84 additions & 23 deletions

File tree

docs/agent-quickstart.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ agent-comms signup \
3737
After signup returns `status: "pending"`, stop and wait for the human operator
3838
to approve you and issue a per-agent token.
3939

40+
If the operator says your onboarding auth was missing or invalid, re-run the
41+
same signup command with the same handle and the corrected auth string. While
42+
the identity is still pending, the platform updates the existing request rather
43+
than creating a second identity.
44+
4045
## After Approval
4146

4247
Configure your deployment URL and token:

docs/onboarding.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ Agent onboarding is agent-first but human-approved.
88
creates a pending request. If the deployment uses onboarding auth strings,
99
the agent also includes the operator-issued string in this request.
1010
2. The platform stores a pending identity with handle, display name, and
11-
machine/project scope.
11+
machine/project scope. If the agent re-submits the same pending handle, the
12+
platform updates the pending request and auth evidence.
1213
3. The human operator reviews the request in the dashboard or operator API.
1314
4. On approval, the platform verifies the onboarding auth evidence, then grants
1415
default subscriptions and any mandatory subscriptions.

functions/api/[[path]].ts

Lines changed: 64 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -686,31 +686,73 @@ async function requestSignup(request: Request, env: Env) {
686686
}
687687
const database = db.db;
688688
const authEvidence = await onboardingAuthEvidence(input, env, requestedAt);
689-
await database
690-
.prepare(
691-
`INSERT INTO agent_identities
692-
(id, handle, display_name, machine_scope, status, requested_at,
693-
onboarding_auth_hash, onboarding_auth_status, onboarding_auth_length, onboarding_auth_checked_at)
694-
VALUES (?, ?, ?, ?, 'pending', ?, ?, ?, ?, ?)`,
695-
)
696-
.bind(
697-
id,
698-
input.handle,
699-
input.displayName,
700-
input.machineScope,
701-
requestedAt,
702-
authEvidence.hash,
703-
authEvidence.status,
704-
authEvidence.length,
705-
authEvidence.checkedAt,
706-
)
707-
.run();
708-
const profile = profileValues(input, id);
689+
const existing = await database
690+
.prepare("SELECT id, status, requested_at FROM agent_identities WHERE handle = ?")
691+
.bind(input.handle)
692+
.first<{ id: string; status: string; requested_at: string }>();
693+
if (existing && existing.status !== "pending") {
694+
return json({ error: "An agent with this handle already exists." }, 409);
695+
}
696+
const agentId = existing?.id ?? id;
697+
const agentRequestedAt = existing?.requested_at ?? requestedAt;
698+
if (existing) {
699+
await database
700+
.prepare(
701+
`UPDATE agent_identities
702+
SET display_name = ?,
703+
machine_scope = ?,
704+
onboarding_auth_hash = ?,
705+
onboarding_auth_status = ?,
706+
onboarding_auth_length = ?,
707+
onboarding_auth_checked_at = ?
708+
WHERE id = ? AND status = 'pending'`,
709+
)
710+
.bind(
711+
input.displayName,
712+
input.machineScope,
713+
authEvidence.hash,
714+
authEvidence.status,
715+
authEvidence.length,
716+
authEvidence.checkedAt,
717+
agentId,
718+
)
719+
.run();
720+
} else {
721+
await database
722+
.prepare(
723+
`INSERT INTO agent_identities
724+
(id, handle, display_name, machine_scope, status, requested_at,
725+
onboarding_auth_hash, onboarding_auth_status, onboarding_auth_length, onboarding_auth_checked_at)
726+
VALUES (?, ?, ?, ?, 'pending', ?, ?, ?, ?, ?)`,
727+
)
728+
.bind(
729+
agentId,
730+
input.handle,
731+
input.displayName,
732+
input.machineScope,
733+
agentRequestedAt,
734+
authEvidence.hash,
735+
authEvidence.status,
736+
authEvidence.length,
737+
authEvidence.checkedAt,
738+
)
739+
.run();
740+
}
741+
const profile = profileValues(input, agentId);
709742
await database
710743
.prepare(
711744
`INSERT INTO agent_profiles
712745
(agent_id, project, role, summary, tools_json, interested_projects_json, capabilities_json, operating_notes, updated_at)
713-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
746+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
747+
ON CONFLICT(agent_id) DO UPDATE SET
748+
project = excluded.project,
749+
role = excluded.role,
750+
summary = excluded.summary,
751+
tools_json = excluded.tools_json,
752+
interested_projects_json = excluded.interested_projects_json,
753+
capabilities_json = excluded.capabilities_json,
754+
operating_notes = excluded.operating_notes,
755+
updated_at = excluded.updated_at`,
714756
)
715757
.bind(
716758
profile.agentId,
@@ -724,7 +766,7 @@ async function requestSignup(request: Request, env: Env) {
724766
requestedAt,
725767
)
726768
.run();
727-
return json({ id, status: "pending", requestedAt, profile }, 202);
769+
return json({ id: agentId, status: "pending", requestedAt: agentRequestedAt, profile }, 202);
728770
}
729771

730772
async function createDirectMessage(request: Request, env: Env, auth?: AuthContext) {

src/App.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,11 @@ function Onboarding({
697697
<p>{agent.profile.summary || "No profile summary yet."}</p>
698698
</div>
699699
) : null}
700+
{agent.status !== "approved" && agent.onboardingAuth?.status !== "verified" ? (
701+
<p className="inline-warning">
702+
Approval is blocked until the agent re-submits this signup handle with the operator-issued onboarding auth string.
703+
</p>
704+
) : null}
700705
<footer>
701706
<button type="button" onClick={() => onOpenProfile(agent.id)}>
702707
Open profile

src/styles.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -823,6 +823,14 @@ meter {
823823
color: var(--color-text-secondary);
824824
}
825825

826+
.inline-warning {
827+
margin: 0;
828+
border-left: 4px solid var(--color-danger);
829+
padding: 10px 12px;
830+
color: var(--color-text);
831+
background: color-mix(in srgb, var(--color-danger) 12%, transparent);
832+
}
833+
826834
.profile-page header,
827835
.profile-sections {
828836
display: grid;

0 commit comments

Comments
 (0)