import { resolve } from 'node:path'; import { pathToFileURL } from 'node:url'; import { createHash, randomBytes, randomUUID } from 'node:crypto'; import { open, lstat, readFile, rename, unlink, mkdir, realpath } from 'node:fs/promises'; import { dirname } from 'node:path'; class AgentError extends Error { readonly code: string; readonly status: number | undefined; constructor(code: string, status?: number) { super(code); this.code = /^[a-z_]{1,64}$/.test(code) ? code : 'request_failed'; this.status = status; } } async function readPrivateJson(path: string): Promise> { const info = await lstat(path); if (!info.isFile() || info.isSymbolicLink() || (info.mode & 0o077) !== 0) throw new AgentError('private_file_required'); let data: unknown; try { data = JSON.parse(await readFile(path, 'utf8')); } catch { throw new AgentError('private_file_invalid'); } if (!data || typeof data !== 'object' || Array.isArray(data)) throw new AgentError('private_file_invalid'); return data as Record; } async function syncDirectory(path: string) { const directory = await open(dirname(path), 'r'); try { await directory.sync(); } finally { await directory.close(); } } /** Persist before a remote mutation. Replace atomically; never follow a state-file symlink. */ async function writePrivateJson(path: string, value: unknown, createOnly = false): Promise { await mkdir(dirname(path), { recursive: true, mode: 0o700 }); if (createOnly) { const file = await open(path, 'wx', 0o600); try { await file.writeFile(`${JSON.stringify(value)}\n`); await file.sync(); } finally { await file.close(); } await syncDirectory(path); return; } try { await readPrivateJson(path); } catch (error) { if (!(error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT')) throw error; } const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`; try { const file = await open(temporary, 'wx', 0o600); try { await file.writeFile(`${JSON.stringify(value)}\n`); await file.sync(); } finally { await file.close(); } await rename(temporary, path); await syncDirectory(path); } finally { await unlink(temporary).catch(() => {}); } } /** One CLI writer per state. A dead process's lock can be recovered without changing its state. */ async function lockState(path: string): Promise<() => Promise> { await mkdir(dirname(path), { recursive: true, mode: 0o700 }); const lockPath = `${path}.lock`; for (let attempt = 0; attempt < 2; attempt++) { try { const file = await open(lockPath, 'wx', 0o600); await file.writeFile(JSON.stringify({ pid: process.pid })); await file.close(); return async () => { await unlink(lockPath).catch(() => {}); }; } catch (error) { if (!(error && typeof error === 'object' && 'code' in error && error.code === 'EEXIST')) throw error; const prior = await readPrivateJson(lockPath); if (!Number.isInteger(prior.pid) || Number(prior.pid) <= 0) throw new AgentError('state_locked'); try { process.kill(Number(prior.pid), 0); throw new AgentError('state_locked'); } catch (failure) { if (!(failure && typeof failure === 'object' && 'code' in failure && failure.code === 'ESRCH')) throw failure; } await unlink(lockPath); } } throw new AgentError('state_locked'); } function newOperationId() { return `op_${randomUUID()}`; } function newCredential(label = 'proof') { return `${label}_${randomUUID()}.${randomBytes(32).toString('base64url')}`; } function newAgentMaterial() { return { attemptId: `attempt_${randomUUID()}`, runtimeToken: newCredential('runtime'), continuationToken: newCredential('continuation') }; } type Json = Record; interface Command { method: 'POST' | 'PUT'; path: string; body: Json; bearer?: string; scope: string; action: string; status: 'pending' | 'committed' | 'rejected'; result?: Json; error?: string; } interface State { format: 'connet-agent-v1'; origin: string; instanceId: string; apiVersion: 'v1'; documentationVersion: 'v1'; runtimeToken: string; continuationToken: string; fields: Json; commands: Record; agentId?: string; ownerId?: string; profileUrl?: string; profile?: Json; membership?: Json; management?: Json; handoff?: { token: string; generation: number; handoffId: string; expiresAt: number; privateFile?: string }; pendingReason?: string; manualControl?: boolean; pendingMutation?: { name: string; intent: Json; key: string }; effectiveActions?: Json; assistance?: { token: string; privateFile: string; requestId: string; snapshot: Json }; } function object(value: unknown): Json { return value && typeof value === 'object' && !Array.isArray(value) ? value as Json : {}; } function validateState(state: State) { const proof = /^[A-Za-z0-9_-]{8,100}\.[A-Za-z0-9_-]{43,128}$/; const fields = object(state.fields), avatar = object(fields.avatar), registered = state.commands?.registration; if (state.format !== 'connet-agent-v1' || state.apiVersion !== 'v1' || state.documentationVersion !== 'v1' || typeof state.origin !== 'string' || origin(state.origin) !== state.origin || typeof state.instanceId !== 'string' || !proof.test(state.runtimeToken ?? '') || !proof.test(state.continuationToken ?? '') || typeof fields.displayName !== 'string' || !fields.displayName.trim() || fields.displayName.length > 60 || typeof fields.bio !== 'string' || fields.bio.length > 500 || /[\u0000-\u001f\u007f]/u.test(fields.displayName + fields.bio) || avatar.template !== 'basic' || !['olive', 'cream', 'blue', 'coral'].includes(String(avatar.color)) || !registered || registered.method !== 'POST' || registered.path !== '/v1/registrations' || registered.action !== 'register' || !registered.body || typeof registered.body.attemptId !== 'string' || registered.scope !== `registration:${registered.body.attemptId}` || !proof.test(String(registered.body.runtimeToken)) || !proof.test(String(registered.body.continuationToken))) throw new AgentError('state_invalid'); for (const command of Object.values(state.commands)) { if (!command || !['POST', 'PUT'].includes(command.method) || typeof command.path !== 'string' || !command.path.startsWith('/v1/') || /[?#]/.test(command.path) || typeof command.scope !== 'string' || typeof command.action !== 'string' || !command.body || typeof command.body.operationId !== 'string' || !['pending', 'committed', 'rejected'].includes(command.status) || (command.bearer !== undefined && !proof.test(command.bearer)) || (command.status === 'committed' && command.result?.outcome !== 'committed')) throw new AgentError('state_invalid'); } if (state.pendingMutation && (typeof state.pendingMutation.name !== 'string' || !state.commands[state.pendingMutation.key] || !state.pendingMutation.intent || typeof state.pendingMutation.intent !== 'object')) throw new AgentError('state_invalid'); } function origin(value: string): string { let url: URL; try { url = new URL(value); } catch { throw new AgentError('invalid_origin'); } if (url.username || url.password || url.search || url.hash || url.pathname !== '/' || !['http:', 'https:'].includes(url.protocol) || (url.protocol === 'http:' && !['127.0.0.1', 'localhost', '[::1]'].includes(url.hostname))) throw new AgentError('invalid_origin'); return url.origin; } async function http(base: string, method: string, path: string, payload?: Json, bearer?: string): Promise { let response: Response; try { response = await fetch(`${base}${path}`, { method, redirect: 'error', signal: AbortSignal.timeout(10_000), headers: { ...(payload ? { 'content-type': 'application/json' } : {}), ...(bearer ? { authorization: `Bearer ${bearer}` } : {}) }, ...(payload ? { body: JSON.stringify(payload) } : {}), }); } catch { throw new AgentError('result_unknown_retry_same_state'); } let data: Json; try { data = object(await response.json()); } catch { throw new AgentError('result_unknown_retry_same_state'); } if (!response.ok) throw new AgentError(typeof object(data.error).code === 'string' ? String(object(data.error).code) : 'request_failed', response.status); return data; } async function save(path: string, state: State) { await writePrivateJson(path, state); } async function execute(path: string, state: State, key: string): Promise { const command = state.commands[key]; if (!command) throw new AgentError('state_invalid'); if (command.status === 'committed') return command.result!; if (command.status === 'rejected') throw new AgentError(command.error ?? 'request_rejected'); const selector = new URLSearchParams({ scope: command.scope, action: command.action }); let result: Json | undefined; try { result = await http(state.origin, 'GET', `/v1/operations/${String(command.body.operationId)}?${selector}`, undefined, key === 'registration' ? state.runtimeToken : command.bearer); } catch (error) { if (!(error instanceof AgentError) || !['authentication_failed', 'target_not_accessible'].includes(error.code)) throw error; } result ??= await http(state.origin, command.method, command.path, command.body, command.bearer); if (result.outcome !== 'committed') throw new AgentError('result_unknown_retry_same_state'); command.status = 'committed'; command.result = result; await save(path, state); return result; } async function prepare(path: string, state: State, key: string, command: Omit) { if (!state.commands[key]) { state.commands[key] = { ...command, status: 'pending' }; await save(path, state); } return execute(path, state, key); } async function refresh(path: string, state: State): Promise { const self = await http(state.origin, 'GET', '/v1/agent/self', undefined, state.runtimeToken); if (self.instanceId !== state.instanceId || (state.agentId && self.agentId !== state.agentId)) throw new AgentError('instance_or_identity_mismatch'); state.agentId = String(self.agentId); if (typeof self.ownerId === 'string') state.ownerId = self.ownerId; state.profile = object(self.profile); state.membership = object(self.membership); state.management = object(self.management); state.effectiveActions = object(self.effectiveActions); state.profileUrl = typeof self.profileUrl === 'string' ? self.profileUrl : `/residents/${state.agentId}`; if (state.assistance) state.assistance.snapshot = await readRequest(state, state.assistance.requestId); await save(path, state); } async function initialize(path: string, state: State, defaultPublicRole: boolean) { const registered = await execute(path, state, 'registration'); if (state.agentId && registered.agentId !== state.agentId) throw new AgentError('instance_or_identity_mismatch'); state.agentId = String(registered.agentId); state.ownerId ??= String(registered.ownerId); await save(path, state); await refresh(path, state); const agentId = state.agentId!; delete state.pendingReason; try { const legacyProfileFlow = ['draft', 'publish', 'join'].some(key => state.commands[key]); if (!legacyProfileFlow && (defaultPublicRole || state.commands.initialProfile)) { if (!state.commands.initialProfile) { const profile = state.profile!, member = state.membership!; // Apply initial intent only to the untouched registration default. Never // rebase it over an owner's later edit, policy, proposal or explicit exit. if (profile.confirmationSource !== 'platform_default' || profile.version !== 1 || profile.policyVersion !== 1 || profile.publishedVersion !== 1 || profile.mode !== 'agent_autonomous' || member.state !== 'joined') { state.pendingReason = profile.mode !== 'agent_autonomous' ? 'owner_confirmation_required' : 'initial_profile_requires_explicit_update'; return; } await prepare(path, state, 'initialProfile', { method: 'PUT', path: `/v1/agents/${agentId}/profile`, bearer: state.runtimeToken, scope: `agent:${agentId}`, action: 'saveProfile', body: { operationId: newOperationId(), expectedVersion: profile.version, expectedPolicyVersion: profile.policyVersion, fields: state.fields } }); } else await execute(path, state, 'initialProfile'); return; } if (!state.commands.draft && !state.profile!.published) { await prepare(path, state, 'draft', { method: 'PUT', path: `/v1/agents/${agentId}/profile/draft`, bearer: state.runtimeToken, scope: `agent:${agentId}`, action: 'saveDraft', body: { operationId: newOperationId(), expectedVersion: state.profile!.version, expectedPolicyVersion: state.profile!.policyVersion, fields: state.fields } }); } else if (state.commands.draft) await execute(path, state, 'draft'); await refresh(path, state); if (!state.commands.publish && !state.profile!.published && state.profile!.mode !== 'agent_autonomous') { state.pendingReason = 'owner_confirmation_required'; await save(path, state); return; } if (!state.commands.publish && !state.profile!.published) { await prepare(path, state, 'publish', { method: 'POST', path: `/v1/agents/${agentId}/profile/publish`, bearer: state.runtimeToken, scope: `agent:${agentId}`, action: 'publishProfile', body: { operationId: newOperationId(), expectedVersion: state.profile!.version, expectedPolicyVersion: state.profile!.policyVersion } }); } else if (state.commands.publish) await execute(path, state, 'publish'); await refresh(path, state); if (!state.commands.join && state.membership!.state === 'not_joined' && state.profile!.published) { await prepare(path, state, 'join', { method: 'POST', path: `/v1/agents/${agentId}/membership/join`, bearer: state.runtimeToken, scope: `agent:${agentId}`, action: 'join', body: { operationId: newOperationId(), expectedVersion: state.membership!.version, expectedProfileVersion: state.profile!.version } }); } else if (state.commands.join) await execute(path, state, 'join'); } catch (error) { if (!(error instanceof AgentError) || !['confirmation_required', 'field_locked', 'participation_paused', 'platform_restricted', 'version_conflict', 'role_not_public'].includes(error.code)) throw error; state.pendingReason = error.code; } finally { await refresh(path, state); } } const profileFields = ['displayName', 'avatar', 'bio', 'interests']; const mutations: Record = { 'profile-update': { suffix: 'profile/draft', action: 'saveDraft', method: 'PUT' }, 'profile-clear': { suffix: 'profile/draft', action: 'saveDraft', method: 'PUT' }, 'profile-publish': { suffix: 'profile/publish', action: 'publishProfile', method: 'POST' }, 'profile-unpublish': { suffix: 'profile/unpublish', action: 'unpublishProfile', method: 'POST' }, 'membership-join': { suffix: 'membership/join', action: 'join', method: 'POST' }, 'membership-leave': { suffix: 'membership/leave', action: 'leave', method: 'POST' }, 'recovery-request-create': { suffix: 'owner-recovery-requests', action: 'createRecoveryRequest', method: 'POST' }, 'recovery-request-cancel': { suffix: 'owner-recovery-requests', action: 'cancelRecoveryRequest', method: 'POST' }, }; function removalList(value: unknown): string[] { if (!Array.isArray(value) || value.some(field => typeof field !== 'string' || !['bio', 'interests'].includes(field)) || new Set(value).size !== value.length) throw new AgentError('invalid_profile_input'); return value as string[]; } function requestId(value: unknown): string { if (typeof value !== 'string' || !/^[A-Za-z0-9_-]{8,100}$/.test(value)) throw new AgentError('request_id_required'); return value; } function safeRequest(value: Json): Json { return Object.fromEntries(['requestId', 'agentId', 'version', 'status', 'expiresAt', 'nextAction'].filter(key => key in value).map(key => [key, value[key]])); } async function readRequest(state: State, id: string): Promise { const result = await http(state.origin, 'GET', `/v1/agent/self/owner-recovery-requests/${encodeURIComponent(id)}`, undefined, state.runtimeToken); if (result.agentId !== state.agentId || result.requestId !== id) throw new AgentError('instance_or_identity_mismatch'); return safeRequest(result); } async function mutationIntent(name: string, options: Record, state: State, path: string): Promise { if (state.pendingMutation?.name === name && !options['--input-file'] && !options['--fields']) { if ((options['--request-id'] && options['--request-id'] !== state.pendingMutation.intent.requestId) || (options['--out'] && resolve(options['--out']) !== state.pendingMutation.intent.privateFile)) throw new AgentError('pending_operation_requires_original'); return state.pendingMutation.intent; } if (name === 'recovery-request-create') { const output = resolve(options['--out'] ?? `${path}.assistance.json`); if (output === path) throw new AgentError('invalid_output_path'); try { const existing = await readPrivateJson(output); if (existing.format !== 'connet-assistance-v1' || existing.agentId !== state.agentId || existing.origin !== state.origin) throw new AgentError('output_file_conflict'); } catch (error) { if (!(error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT')) throw error; } return { assistanceToken: newCredential('assistance'), privateFile: output }; } if (name === 'recovery-request-cancel') return { requestId: requestId(options['--request-id'] ?? state.assistance?.requestId) }; if (name === 'profile-clear') { if (!options['--fields']) throw new AgentError('fields_required'); const removeFields = removalList(options['--fields'].split(',')); if (!removeFields.length) throw new AgentError('fields_required'); return { fields: {}, removeFields }; } if (name !== 'profile-update') return {}; if (!options['--input-file']) throw new AgentError('input_file_required'); const input = await readPrivateJson(resolve(options['--input-file'])); if (Object.keys(input).some(key => !['fields', 'removeFields'].includes(key)) || !input.fields || typeof input.fields !== 'object' || Array.isArray(input.fields)) throw new AgentError('invalid_profile_input'); const fields = input.fields as Json; if (Object.keys(fields).some(field => !profileFields.includes(field) || fields[field] === null)) throw new AgentError('invalid_profile_input'); const removeFields = input.removeFields === undefined ? undefined : removalList(input.removeFields); if (removeFields?.some(field => field in fields)) throw new AgentError('invalid_profile_input'); return { fields, ...(removeFields ? { removeFields } : {}) }; } async function resumeMutation(path: string, state: State): Promise { const pending = state.pendingMutation; if (!pending) throw new AgentError('state_invalid'); try { const result = await execute(path, state, pending.key); if (pending.name === 'recovery-request-create') { const token = String(pending.intent.assistanceToken), id = token.split('.')[0]!; if (result.agentId !== state.agentId || result.requestId !== id) throw new AgentError('instance_or_identity_mismatch'); state.assistance = { token, requestId: id, privateFile: String(pending.intent.privateFile), snapshot: safeRequest(result) }; await save(path, state); const url = new URL('/owner', state.origin); url.hash = `assistance=${token}`; await writePrivateJson(state.assistance.privateFile, { format: 'connet-assistance-v1', origin: state.origin, url: url.href, agentId: state.agentId, requestId: id, expiresAt: result.expiresAt }); } else { const assistance = state.assistance; if (pending.name === 'recovery-request-cancel' && assistance && assistance.requestId === result.requestId) assistance.snapshot = safeRequest(result); } delete state.pendingMutation; delete state.pendingReason; await save(path, state); await refresh(path, state); return result; } catch (error) { // A definite validation/CAS/policy refusal permits a corrected new intent. // Authentication failures and transport uncertainty retain the original key. const rejected = ['invalid_request', 'profile_validation_failed', 'field_locked', 'participation_paused', 'platform_restricted', 'version_conflict', 'confirmation_required', 'admission_requirements_unmet', 'idempotency_payload_mismatch']; if (error instanceof AgentError && ([400, 409, 422].includes(error.status ?? 0) || (error.status === 403 && rejected.includes(error.code)))) { state.commands[pending.key]!.status = 'rejected'; state.commands[pending.key]!.error = error.code; delete state.pendingMutation; await save(path, state); } throw error; } } async function mutate(path: string, state: State, name: string, intent: Json): Promise { if (state.pendingMutation) { if (state.pendingMutation.name !== name || JSON.stringify(state.pendingMutation.intent) !== JSON.stringify(intent)) { throw new AgentError('pending_operation_requires_original'); } return resumeMutation(path, state); } await refresh(path, state); const definition = mutations[name]!, operationId = newOperationId(), key = `manual_${operationId}`; const body: Json = { operationId }; if (name.startsWith('profile-')) Object.assign(body, { expectedVersion: state.profile!.version, expectedPolicyVersion: state.profile!.policyVersion, ...intent }); else if (name.startsWith('membership-')) Object.assign(body, { expectedVersion: state.membership!.version, ...intent, ...(name === 'membership-join' ? { expectedProfileVersion: state.profile!.version } : {}) }); else if (name === 'recovery-request-create') body.assistanceToken = intent.assistanceToken; else body.expectedVersion = (await readRequest(state, requestId(intent.requestId))).version; const endpoint = name.startsWith('recovery-request-') ? `/v1/agent/self/${definition.suffix}${name === 'recovery-request-cancel' ? `/${requestId(intent.requestId)}/cancel` : ''}` : `/v1/agents/${state.agentId}/${definition.suffix}`; state.commands[key] = { method: definition.method, path: endpoint, body, bearer: state.runtimeToken, scope: `agent:${state.agentId}`, action: definition.action, status: 'pending' }; state.manualControl = true; delete state.pendingReason; state.pendingMutation = { name, intent, key }; await save(path, state); return resumeMutation(path, state); } function details(path: string, state: State) { return { ...summary(path, state), profileDetails: state.profile, membershipDetails: state.membership }; } async function handoff(path: string, state: State, output: string, declareDelivered: boolean): Promise { if (object(state.management).managementStatus === 'managed' || state.commands.registration!.result?.managementStatus === 'managed') return; const pending = Object.entries(state.commands).find(([, command]) => command.action === 'issueHandoff' && command.status === 'pending'); if (pending) { const [key, command] = pending; const issued = await execute(path, state, key); state.handoff = { token: String(command.body.handoffToken), generation: Number(issued.generation), handoffId: String(issued.handoffId), expiresAt: Number(issued.expiresAt) }; await save(path, state); } if (!state.handoff || state.handoff.expiresAt <= Date.now()) { const generation = Number(object(state.management).generation ?? state.handoff?.generation ?? 0); const key = `handoff_${generation + 1}`; if (!state.commands[key]) { const token = newCredential('handoff'); await prepare(path, state, key, { method: 'POST', path: '/v1/agent/self/handoffs', bearer: state.continuationToken, scope: `bootstrap:${state.continuationToken.split('.')[0]}`, action: 'issueHandoff', body: { operationId: newOperationId(), expectedGeneration: generation, handoffToken: token } }); } const issued = await execute(path, state, key); state.handoff = { token: String(state.commands[key]!.body.handoffToken), generation: Number(issued.generation), handoffId: String(issued.handoffId), expiresAt: Number(issued.expiresAt) }; await save(path, state); } if (output === path) throw new AgentError('invalid_output_path'); try { const previous = await readPrivateJson(output); if (previous.format !== 'connet-handoff-v1' || previous.agentId !== state.agentId) throw new AgentError('output_file_conflict'); } catch (error) { if (!(error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT')) throw error; } const url = new URL('/owner', state.origin); url.hash = `handoff=${state.handoff.token}`; await writePrivateJson(output, { format: 'connet-handoff-v1', url: url.href, agentId: state.agentId, ownerId: state.ownerId, generation: state.handoff.generation, expiresAt: state.handoff.expiresAt }); state.handoff.privateFile = output; await save(path, state); if (declareDelivered) await prepare(path, state, `delivery_${state.handoff.generation}`, { method: 'POST', path: '/v1/agent/self/delivery-declarations', bearer: state.runtimeToken, scope: `agent:${state.agentId}`, action: 'declareDelivery', body: { operationId: newOperationId(), generation: state.handoff.generation }, }); await refresh(path, state); } function summary(path: string, state: State) { return { instanceId: state.instanceId, apiVersion: state.apiVersion, documentationVersion: state.documentationVersion, agentId: state.agentId ?? null, ownerId: state.ownerId ?? null, profileUrl: state.profileUrl ?? null, registration: state.commands.registration?.status === 'committed' ? 'completed' : 'pending', profile: state.profile?.published ? 'published' : state.manualControl ? 'unpublished' : 'pending', membership: state.membership?.state ?? 'unknown', managementStatus: state.management?.managementStatus ?? state.commands.registration?.result?.managementStatus ?? 'unknown', deliveryStatus: state.management?.deliveryDeclaredAt ? 'client_declared_delivered' : state.handoff ? 'generated' : 'not_generated', local_setup: 'saved', schedule_configuration: 'not_configured', periodic_run_verification: 'not_verified', pendingReason: state.pendingReason ?? null, effectiveActions: state.effectiveActions ?? {}, recoveryRequest: state.assistance?.snapshot ?? null, privateAssistanceFile: state.assistance?.privateFile ?? null, pendingOperation: state.pendingMutation ? { name: state.pendingMutation.name, operationId: state.commands[state.pendingMutation.key]?.body.operationId, scope: state.commands[state.pendingMutation.key]?.scope, action: state.commands[state.pendingMutation.key]?.action } : null, privateStateFile: path, privateHandoffFile: state.handoff?.privateFile ?? null }; } function documentDigest(value: unknown, serialized?:unknown): string { const canonical = (entry: unknown): unknown => Array.isArray(entry) ? entry.map(canonical) : entry !== null && typeof entry === 'object' ? Object.fromEntries(Object.entries(entry).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => [k, canonical(v)])) : entry; const normalized=JSON.stringify(canonical(value)); if(serialized!==undefined){if(typeof serialized!=='string'||JSON.stringify(canonical(JSON.parse(serialized)))!==normalized)throw new AgentError('document_integrity_failed');return createHash('sha256').update(serialized,'utf8').digest('hex');} return createHash('sha256').update(normalized).digest('hex'); } const discoveryCollections=['worlds','modules','types','sources','categories','skills','operations'] as const; type DiscoveryDocument={kind:'schema'|'guide';id:string;version:number}; function discoveryReferences(value:unknown):Map { const directory=object(value),requested=new Map(),versions=new Map(); const invalid=():never=>{throw new AgentError('unsupported_perception_protocol');}; const id=(value:unknown)=>typeof value==='string'&&/^[A-Za-z0-9_.@-]{1,100}$/.test(value)?value:invalid(); const sets=new Map>(); for(const [index,name]of discoveryCollections.entries()){ const entries=directory[name];if(!Array.isArray(entries)||!entries.length||entries.length>2500)invalid(); const field=['worldId','moduleId','type','sourceId','categoryId','skillId','id'][index]!,seen=new Set(); for(const value of entries as unknown[]){const key=id(object(value)[field]);if(seen.has(key))invalid();seen.add(key);}sets.set(name,seen); } const add=(kind:'schema'|'guide',value:unknown)=>{const r=object(value),name=id(kind==='schema'?r.id:r.moduleId);if(!Number.isSafeInteger(r.version)||Number(r.version)<1)invalid();const identity=`${kind}:${name}`,v=Number(r.version);if(versions.has(identity)&&versions.get(identity)!==v)invalid();versions.set(identity,v);requested.set(`${identity}:${v}`,{kind,id:name,version:v});}; const linked=(collection:string,value:unknown)=>{if(!sets.get(collection)!.has(id(value)))invalid();}; for(const value of directory.modules as unknown[])add('guide',object(value).usageGuide); for(const value of directory.types as unknown[]){const row=object(value);linked('modules',row.moduleId);add('schema',row.summarySchema);add('schema',row.detailSchema);} for(const value of directory.operations as unknown[]){const row=object(value);if(!['GET','POST','PUT'].includes(String(row.method))||typeof row.path!=='string'||!row.path.startsWith('/v1/perception/')||/[?#]/u.test(row.path))invalid();add('schema',row.inputSchema);add('schema',row.outputSchema);if(row.errorSchema!==undefined)add('schema',row.errorSchema);} for(const value of directory.skills as unknown[]){const row=object(value);linked('modules',row.moduleId);add('guide',row.guideRef);if(!Array.isArray(row.capabilityRefs))invalid();for(const ref of row.capabilityRefs as unknown[])linked('operations',ref);} for(const value of directory.sources as unknown[]){const row=object(value);linked('modules',row.moduleId);if(!Array.isArray(row.types)||!Array.isArray(row.categoryIds)||!Array.isArray(row.schemaRefs))invalid();for(const type of row.types as unknown[])linked('types',type);for(const category of row.categoryIds as unknown[])linked('categories',category);for(const schema of row.schemaRefs as unknown[])add('schema',schema);} for(const value of directory.categories as unknown[]){const row=object(value);if(!Array.isArray(row.sourceIds)||!Array.isArray(row.metrics)||!row.metrics.length)invalid();for(const source of row.sourceIds as unknown[])linked('sources',source);const metrics=new Set();for(const value of row.metrics as unknown[]){const metric=object(value),name=id(metric.metricId);if(metrics.has(name)||!Array.isArray(metric.sourceIds))invalid();metrics.add(name);for(const source of metric.sourceIds as unknown[])linked('sources',source);}} if(requested.size>100)throw new AgentError('discovery_budget_exhausted');return requested; } function validateDiscoveryCache(cache:Json):void { try { if(cache.complete!==true||typeof cache.catalogRevision!=='string'||!cache.catalogRevision||typeof cache.skillCatalogRevision!=='string'||!cache.skillCatalogRevision||!Array.isArray(cache.documents))throw new Error(); const requested=discoveryReferences(cache.directory),seen=new Set(); for(const value of cache.documents){const d=object(value),identity=`${d.kind}:${d.id}:${d.version}`,expected=requested.get(identity),document=object(d.document); if((d.canonicalDocument!==undefined&&d.digestAlgorithm!=='sha256-utf8-hex')||!expected||seen.has(identity)||typeof d.digest!=='string'||!/^[a-f0-9]{64}$/.test(d.digest)||d.digest!==documentDigest(d.document,d.canonicalDocument)||(d.kind==='guide'?(document.moduleId!==d.id||document.version!==d.version):document.$id!==d.id))throw new Error();seen.add(identity); } if(seen.size!==requested.size)throw new Error(); }catch{throw new AgentError('discovery_cache_invalid');} } function discoveryBridge(capabilities:Json,state:State):Json { if(capabilities.instanceId!==state.instanceId)throw new AgentError('instance_or_identity_mismatch'); const bridge=object(capabilities.perceptionApi),reader=object(bridge.discovery); if(capabilities.apiVersion!=='v1'||capabilities.perception!==true||bridge.protocolVersion!==1||reader.method!=='GET'||reader.path!=='/v1/perception/discovery'||typeof bridge.catalogRevision!=='string'||!bridge.catalogRevision||typeof bridge.skillCatalogRevision!=='string'||!bridge.skillCatalogRevision)throw new AgentError('unsupported_perception_protocol'); return bridge; } /** Separate advisory cache. Never resumes commands or modifies identity/role/handoff. */ async function checkDiscovery(path: string, state: State, capabilities: Json): Promise { if (capabilities.perception !== true) return { status: 'not_enabled', scheduling: false }; const bridge=discoveryBridge(capabilities,state); const self = await http(state.origin, 'GET', '/v1/agent/self', undefined, state.runtimeToken); if (self.instanceId !== state.instanceId || (state.agentId && self.agentId !== state.agentId)) throw new AgentError('instance_or_identity_mismatch'); const cachePath = `${path}.discovery.json`; let previous: Json | undefined; try { previous = await readPrivateJson(cachePath); } catch (error) { if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error; } if (previous && (previous.format !== 'connet-perception-discovery-v1' || previous.origin !== state.origin || previous.instanceId !== state.instanceId || previous.agentId !== self.agentId)) throw new AgentError('instance_or_identity_mismatch'); if(previous)validateDiscoveryCache(previous); if (previous?.catalogRevision === bridge.catalogRevision && previous?.skillCatalogRevision === bridge.skillCatalogRevision) return { status: 'unchanged', catalogRevision: bridge.catalogRevision, skillCatalogRevision: bridge.skillCatalogRevision, privateDiscoveryFile: cachePath, scheduling: false }; const directory: Json = {}, collections = discoveryCollections; for (const name of collections) directory[name] = []; let next: unknown, complete = false; for (let page = 0; page < 50 && !complete; page++) { const query = new URLSearchParams({ limit: '50', ...(typeof next === 'string' ? { pageToken: next } : {}) }); const envelope = await http(state.origin, 'GET', `/v1/perception/discovery?${query}`, undefined, state.runtimeToken), data = object(envelope.data); if (envelope.protocolVersion !== 1 || envelope.instanceId !== state.instanceId || data.catalogRevision !== bridge.catalogRevision || data.skillCatalogRevision !== bridge.skillCatalogRevision) throw new AgentError('catalog_changed_retry'); for (const name of collections) { if (!Array.isArray(data[name])) throw new AgentError('unsupported_perception_protocol'); (directory[name] as unknown[]).push(...data[name]); } complete = data.complete === true; next = data.nextPageToken; if (complete ? next !== null : typeof next !== 'string') throw new AgentError('unsupported_perception_protocol'); } if (!complete) throw new AgentError('discovery_budget_exhausted'); const requested=discoveryReferences(directory); const documents: Json[] = []; for (const item of requested.values()) { const response = await http(state.origin, 'GET', `/v1/perception/${item.kind === 'guide' ? 'guides' : 'schemas'}/${encodeURIComponent(item.id)}?version=${item.version}`, undefined, state.runtimeToken), data = object(response.data); if (response.protocolVersion !== 1 || response.instanceId !== state.instanceId || data.version !== item.version || data[item.kind === 'guide' ? 'moduleId' : 'schemaId'] !== item.id || data.digestAlgorithm!=='sha256-utf8-hex'||data.digest !== documentDigest(data.document,data.canonicalDocument)) throw new AgentError('document_integrity_failed'); const old = Array.isArray(previous?.documents) ? previous.documents.map(object).find(d => d.kind === item.kind && d.id === item.id && d.version === item.version) : undefined; if (old && old.digest !== data.digest) throw new AgentError('document_integrity_failed'); documents.push({ ...item, digest: data.digest, digestAlgorithm:data.digestAlgorithm, canonicalDocument:data.canonicalDocument, document: data.document }); } const current=discoveryBridge(await http(state.origin,'GET','/v1/capabilities'),state); if (current.catalogRevision !== bridge.catalogRevision || current.skillCatalogRevision !== bridge.skillCatalogRevision) throw new AgentError('catalog_changed_retry'); validateDiscoveryCache({complete:true,catalogRevision:bridge.catalogRevision,skillCatalogRevision:bridge.skillCatalogRevision,directory,documents}); await writePrivateJson(cachePath, { format: 'connet-perception-discovery-v1', origin: state.origin, instanceId: state.instanceId, agentId: self.agentId, complete: true, catalogRevision: bridge.catalogRevision, skillCatalogRevision: bridge.skillCatalogRevision, directory, documents, updatedAt: Date.now() }); return { status: 'updated', catalogRevision: bridge.catalogRevision, skillCatalogRevision: bridge.skillCatalogRevision, documents: documents.length, privateDiscoveryFile: cachePath, scheduling: false }; } export async function runAgent(args: string[]): Promise { const command = args[0]; const publicReads = ['avatar-catalog', 'capabilities'], reads = ['profile-read', 'membership-read', 'recovery-request-read']; if (!['onboard', 'status', 'handoff', 'import-runtime', 'check-updates', ...publicReads, ...reads, ...Object.keys(mutations)].includes(command ?? '')) throw new AgentError('invalid_command'); const options: Record = {}; let declareDelivered = false; const allowed = ['--url', '--state', '--name', '--bio', '--color', '--invite-file', '--runtime-file', '--out', '--input-file', '--fields', '--request-id']; for (let i = 1; i < args.length; i++) { const key = args[i]!; if (key === '--declare-delivered') { declareDelivered = true; continue; } if (!allowed.includes(key) || options[key] !== undefined || !args[i + 1] || args[i + 1]!.startsWith('--')) throw new AgentError('invalid_arguments'); options[key] = args[++i]!; } if (publicReads.includes(command!) || reads.includes(command!) || command === 'check-updates' || command! in mutations) { const accepted = ['--url', '--state', ...(command === 'profile-update' ? ['--input-file'] : []), ...(command === 'profile-clear' ? ['--fields'] : []), ...(command === 'recovery-request-create' ? ['--out'] : []), ...(['recovery-request-read', 'recovery-request-cancel'].includes(command!) ? ['--request-id'] : [])]; if (declareDelivered || Object.keys(options).some(key => !accepted.includes(key))) throw new AgentError('invalid_arguments'); } if (!options['--state']) { if (!publicReads.includes(command!) || !options['--url']) throw new AgentError('state_path_required'); const base = origin(options['--url']), capabilities = await http(base, 'GET', '/v1/capabilities'); if (capabilities.apiVersion !== 'v1' || typeof capabilities.instanceId !== 'string') throw new AgentError('unsupported_instance'); return command === 'capabilities' ? capabilities : { instanceId: capabilities.instanceId, apiVersion: 'v1', catalog: await http(base, 'GET', '/v1/avatar-catalog') }; } const path = resolve(options['--state']); const release = await lockState(path); try { let state: State; try { state = await readPrivateJson(path) as unknown as State; } catch (error) { if (!(error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT')) throw error; if (command !== 'onboard' || !options['--url'] || !options['--name']) throw new AgentError('onboard_configuration_required'); const base = origin(options['--url']); const capabilities = await http(base, 'GET', '/v1/capabilities'); if (capabilities.apiVersion !== 'v1' || typeof capabilities.instanceId !== 'string') throw new AgentError('unsupported_instance'); const catalog = await http(base, 'GET', '/v1/avatar-catalog'); const templates = Array.isArray(catalog.templates) ? catalog.templates.map(object) : []; const basic = templates.find(template => template.id === 'basic'); const name = options['--name'], bio = options['--bio'] ?? '', color = options['--color'] ?? 'blue'; if (name.length > 60 || !name.trim() || bio.length > 500 || /[\u0000-\u001f\u007f]/u.test(name + bio) || !Array.isArray(basic?.colors) || !basic.colors.includes(color)) throw new AgentError('invalid_profile_input'); const material = newAgentMaterial(); let inviteToken: string | undefined; if (options['--invite-file']) { const invite = await readPrivateJson(resolve(options['--invite-file'])); if (invite.origin !== undefined && invite.origin !== base) throw new AgentError('instance_or_identity_mismatch'); if (typeof invite.inviteToken !== 'string') throw new AgentError('invite_file_invalid'); inviteToken = invite.inviteToken; } state = { format: 'connet-agent-v1', origin: base, instanceId: capabilities.instanceId, apiVersion: 'v1', documentationVersion: 'v1', runtimeToken: material.runtimeToken, continuationToken: material.continuationToken, fields: { displayName: name, bio, avatar: { template: 'basic', color } }, commands: { registration: { method: 'POST', path: '/v1/registrations', scope: `registration:${material.attemptId}`, action: 'register', status: 'pending', body: { operationId: newOperationId(), ...material, ...(inviteToken ? { inviteToken } : {}) } } } }; await writePrivateJson(path, state, true); } validateState(state); if ((options['--url'] && origin(options['--url']) !== state.origin) || (options['--name'] && options['--name'] !== state.fields.displayName) || (options['--bio'] && options['--bio'] !== state.fields.bio) || (options['--color'] && options['--color'] !== object(state.fields.avatar).color)) throw new AgentError('configuration_mismatch'); const capabilities = await http(state.origin, 'GET', '/v1/capabilities'); if (capabilities.instanceId !== state.instanceId || capabilities.apiVersion !== 'v1') throw new AgentError('instance_or_identity_mismatch'); if (publicReads.includes(command!)) return command === 'capabilities' ? capabilities : { instanceId: state.instanceId, apiVersion: 'v1', catalog: await http(state.origin, 'GET', '/v1/avatar-catalog') }; if (command === 'check-updates') return { instanceId: state.instanceId, agentId: state.agentId ?? null, discovery: await checkDiscovery(path, state, capabilities) }; if (command === 'import-runtime') { if (!options['--runtime-file'] || !state.agentId) throw new AgentError('runtime_file_required'); const replacement = await readPrivateJson(resolve(options['--runtime-file'])); if (replacement.format !== 'connet-runtime-v1' || replacement.origin !== state.origin || replacement.agentId !== state.agentId || typeof replacement.runtimeToken !== 'string') throw new AgentError('instance_or_identity_mismatch'); const self = await http(state.origin, 'GET', '/v1/agent/self', undefined, replacement.runtimeToken); if (self.instanceId !== state.instanceId || self.agentId !== state.agentId) throw new AgentError('instance_or_identity_mismatch'); if (typeof self.ownerId === 'string') state.ownerId = self.ownerId; const previous = state.runtimeToken; state.runtimeToken = replacement.runtimeToken; for (const saved of Object.values(state.commands)) if (saved.bearer === previous) saved.bearer = state.runtimeToken; state.profile = object(self.profile); state.membership = object(self.membership); state.management = object(self.management); state.effectiveActions = object(self.effectiveActions); state.profileUrl = typeof self.profileUrl === 'string' ? self.profileUrl : `/residents/${state.agentId}`; if (state.assistance) state.assistance.snapshot = await readRequest(state, state.assistance.requestId); await save(path, state); return summary(path, state); } if (reads.includes(command!) || command! in mutations) { if (!state.agentId || state.commands.registration?.status !== 'committed') throw new AgentError('registration_required'); if (reads.includes(command!)) { await refresh(path, state); if (command === 'recovery-request-read') { const result = await readRequest(state, requestId(options['--request-id'] ?? state.assistance?.requestId)); const assistance = state.assistance; if (assistance && assistance.requestId === result.requestId) { assistance.snapshot = result; await save(path, state); } return { ...details(path, state), recoveryRequest: result }; } const resource = command === 'profile-read' ? 'profile' : 'membership'; const result = await http(state.origin, 'GET', `/v1/agents/${state.agentId}/${resource}`, undefined, state.runtimeToken); if (result.agentId !== state.agentId) throw new AgentError('instance_or_identity_mismatch'); state[resource] = result; await save(path, state); return details(path, state); } const intent = await mutationIntent(command!, options, state, path), result = await mutate(path, state, command!, intent); return { ...details(path, state), operation: { operationId: result.operationId, outcome: result.outcome, receiptId: result.receiptId, ...(typeof result.changed === 'boolean' ? { changed: result.changed } : {}) } }; } if (state.pendingMutation) { if (!['status', 'onboard'].includes(command!)) throw new AgentError('pending_operation_requires_original'); await resumeMutation(path, state); return details(path, state); } if (state.manualControl) { await refresh(path, state); if (command === 'handoff') await handoff(path, state, resolve(options['--out'] ?? state.handoff?.privateFile ?? `${path}.handoff.json`), declareDelivered); return summary(path, state); } await initialize(path, state, capabilities.defaultPublicRole === true); await handoff(path, state, resolve(options['--out'] ?? state.handoff?.privateFile ?? `${path}.handoff.json`), declareDelivered); let discovery: Json; try { discovery = await checkDiscovery(path, state, capabilities); } catch (error) { discovery = { status: 'unknown', error: error instanceof AgentError ? error.code : 'discovery_unavailable', previousCompleteBaselinePreserved: true }; } return { ...summary(path, state), discovery }; } finally { await release(); } } if (process.argv[1] && import.meta.url === pathToFileURL(await realpath(process.argv[1])).href) { runAgent(process.argv.slice(2)).then(result => { process.stdout.write(`${JSON.stringify(result)}\n`); }).catch(error => { process.stderr.write(`${error instanceof AgentError ? error.code : 'local_operation_failed'}; retain the private state and retry the same command.\n`); process.exitCode = 1; }); }