Skip to content
Draft
7 changes: 5 additions & 2 deletions packages/dcp-agent/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,19 @@
"author": "DCP Protocol",
"license": "Apache-2.0",
"dependencies": {
"@dcprotocol/core": "^2.0.1",
"@dcprotocol/client": "^2.0.1",
"@dcprotocol/core": "^2.0.1",
"@modelcontextprotocol/sdk": "^1.0.0",
"@noble/curves": "^1.4.0",
"@solana/web3.js": "^1.98.0",
"chalk": "^5.3.0",
"commander": "^12.1.0",
"ora": "^8.0.1"
"ora": "^8.0.1",
"qrcode-terminal": "^0.12.0"
},
"devDependencies": {
"@types/node": "^22.10.2",
"@types/qrcode-terminal": "^0.12.2",
"tsup": "^8.3.5",
"tsx": "^4.19.2",
"typescript": "^5.7.2",
Expand Down
52 changes: 51 additions & 1 deletion packages/dcp-agent/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
generateSigningKeyPair,
type SignedPairingGrant,
} from '@dcprotocol/core';
import { AgentConfig, AgentError } from './types.js';
import { AgentConfig, AgentError, type MobilePairingApprovalStatus, type MobilePendingConfig } from './types.js';

// ============================================================================
// Constants
Expand Down Expand Up @@ -151,6 +151,56 @@ export function saveConfig(config: AgentConfig): void {
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 0o600 });
}

/**
* Save pending mobile pairing material.
*
* This is not a usable AgentConfig yet. The mobile vault must approve the
* invite and return vault identity before this can be promoted to a runtime
* agent config.
*/
export function saveMobilePendingConfig(config: MobilePendingConfig): void {
ensureConfigDir();
const safeInviteId = config.invite_id.replace(/[^a-zA-Z0-9_-]/g, '_');
const configPath = path.join(CONFIG_DIR, `${safeInviteId}.mobile-pending.json`);
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 0o600 });
}

export function promoteMobilePendingConfig(
pending: MobilePendingConfig,
approval: MobilePairingApprovalStatus
): AgentConfig {
if (
approval.status !== 'approved' ||
!approval.agent_id ||
!approval.vault_id ||
!approval.vault_hpke_public_key ||
!approval.vault_signing_public_key
) {
throw new AgentError('INVALID_GRANT', 'Mobile pairing approval is incomplete');
}

const config: AgentConfig = {
agent_id: approval.agent_id,
agent_name: pending.invite.agent_name,
vault_id: approval.vault_id,
mode: 'mcp',
vault_hpke_public_key: approval.vault_hpke_public_key,
vault_signing_public_key: approval.vault_signing_public_key,
relay_url: pending.invite.relay_url,
service_keypair: pending.service_keypair,
paired_at: new Date().toISOString(),
grant_expires_at: pending.invite.expires_at,
};

saveConfig(config);
const safeInviteId = pending.invite_id.replace(/[^a-zA-Z0-9_-]/g, '_');
const pendingPath = path.join(CONFIG_DIR, `${safeInviteId}.mobile-pending.json`);
if (fs.existsSync(pendingPath)) {
fs.unlinkSync(pendingPath);
}
return config;
}

/**
* Load agent configuration
*
Expand Down
Loading
Loading