@nestpilot/mcp-app 1.0.0
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/README.md +350 -0
- package/dist/cli/doctor.d.ts +1 -0
- package/dist/cli/doctor.js +214 -0
- package/dist/cli/export-import.d.ts +6 -0
- package/dist/cli/export-import.js +132 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +168 -0
- package/dist/cli/init.d.ts +1 -0
- package/dist/cli/init.js +171 -0
- package/dist/host-configs/cowork.json +11 -0
- package/dist/host-configs/goose.yaml +22 -0
- package/dist/host-configs/openclaw-manifest.json +16 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +128 -0
- package/dist/mcp-app.html +155 -0
- package/dist/nestpilot-client.d.ts +44 -0
- package/dist/nestpilot-client.js +160 -0
- package/dist/planner.html +222 -0
- package/dist/server.d.ts +19 -0
- package/dist/server.js +245 -0
- package/dist/skills/SKILL.md +162 -0
- package/dist/skills/manifest.json +51 -0
- package/dist/skills/tools/activate_plan.md +36 -0
- package/dist/skills/tools/coach.md +59 -0
- package/dist/skills/tools/comprehensive_plan.md +65 -0
- package/dist/skills/tools/create_plan.md +59 -0
- package/dist/skills/tools/create_saved_plan.md +49 -0
- package/dist/skills/tools/delete_plan.md +42 -0
- package/dist/skills/tools/delete_scenario.md +38 -0
- package/dist/skills/tools/generate_proposal.md +63 -0
- package/dist/skills/tools/generate_retirement_report.md +50 -0
- package/dist/skills/tools/get_active_plan.md +44 -0
- package/dist/skills/tools/get_baseline_forecast.md +47 -0
- package/dist/skills/tools/get_plan.md +44 -0
- package/dist/skills/tools/get_plan_components.md +50 -0
- package/dist/skills/tools/get_scenario.md +46 -0
- package/dist/skills/tools/list_plans.md +44 -0
- package/dist/skills/tools/list_scenarios.md +42 -0
- package/dist/skills/tools/medicare-guardian.md +59 -0
- package/dist/skills/tools/nestpilot_run_plan.md +61 -0
- package/dist/skills/tools/optimize_roth_conversion.md +107 -0
- package/dist/skills/tools/optimize_ss_claiming.md +30 -0
- package/dist/skills/tools/rename_plan.md +34 -0
- package/dist/skills/tools/retirement-planner.md +55 -0
- package/dist/skills/tools/run_forecast.md +65 -0
- package/dist/skills/tools/run_saved_forecast.md +52 -0
- package/dist/skills/tools/run_scenario.md +66 -0
- package/dist/skills/tools/save_plan.md +48 -0
- package/dist/skills/tools/save_scenario.md +50 -0
- package/dist/skills/tools/verify_forecast.md +43 -0
- package/dist/src/config.d.ts +20 -0
- package/dist/src/config.js +44 -0
- package/dist/src/contracts/provenance.d.ts +37 -0
- package/dist/src/contracts/provenance.js +71 -0
- package/dist/src/contracts/tool-contract-registry.d.ts +43 -0
- package/dist/src/contracts/tool-contract-registry.js +282 -0
- package/dist/src/local/cloud-compute-client.d.ts +55 -0
- package/dist/src/local/cloud-compute-client.js +135 -0
- package/dist/src/local/encryption.d.ts +24 -0
- package/dist/src/local/encryption.js +105 -0
- package/dist/src/local/keychain.d.ts +41 -0
- package/dist/src/local/keychain.js +236 -0
- package/dist/src/local/local-config.d.ts +34 -0
- package/dist/src/local/local-config.js +61 -0
- package/dist/src/local/local-data-layer.d.ts +20 -0
- package/dist/src/local/local-data-layer.js +15 -0
- package/dist/src/local/local-plan-store.d.ts +66 -0
- package/dist/src/local/local-plan-store.js +195 -0
- package/dist/src/local/pii-scrubber.d.ts +26 -0
- package/dist/src/local/pii-scrubber.js +219 -0
- package/dist/src/policy/policy-engine.d.ts +44 -0
- package/dist/src/policy/policy-engine.js +119 -0
- package/dist/src/rate-limit.d.ts +17 -0
- package/dist/src/rate-limit.js +41 -0
- package/dist/src/security.d.ts +19 -0
- package/dist/src/security.js +118 -0
- package/dist/src/skills/index.d.ts +12 -0
- package/dist/src/skills/index.js +16 -0
- package/dist/src/skills/retirement-pack-v1.d.ts +28 -0
- package/dist/src/skills/retirement-pack-v1.js +295 -0
- package/dist/src/skills/skill-executor.d.ts +65 -0
- package/dist/src/skills/skill-executor.js +174 -0
- package/dist/src/skills/skill-manifest-schema.d.ts +337 -0
- package/dist/src/skills/skill-manifest-schema.js +94 -0
- package/dist/src/skills/skill-registry.d.ts +71 -0
- package/dist/src/skills/skill-registry.js +116 -0
- package/dist/src/telemetry.d.ts +12 -0
- package/dist/src/telemetry.js +59 -0
- package/dist/src/types.d.ts +46 -0
- package/dist/src/types.js +4 -0
- package/dist/tools/agent-tools.d.ts +12 -0
- package/dist/tools/agent-tools.js +141 -0
- package/dist/tools/forecast-management-tools.d.ts +9 -0
- package/dist/tools/forecast-management-tools.js +133 -0
- package/dist/tools/local-plan-tools.d.ts +8 -0
- package/dist/tools/local-plan-tools.js +357 -0
- package/dist/tools/mcp-helpers.d.ts +52 -0
- package/dist/tools/mcp-helpers.js +177 -0
- package/dist/tools/medicare-tools.d.ts +3 -0
- package/dist/tools/medicare-tools.js +162 -0
- package/dist/tools/optimize-roth-tools-test.d.ts +2 -0
- package/dist/tools/optimize-roth-tools-test.js +36 -0
- package/dist/tools/optimize-roth-tools.d.ts +3 -0
- package/dist/tools/optimize-roth-tools.js +818 -0
- package/dist/tools/plan-management-tools.d.ts +3 -0
- package/dist/tools/plan-management-tools.js +196 -0
- package/dist/tools/planning-tools.d.ts +3 -0
- package/dist/tools/planning-tools.js +290 -0
- package/dist/tools/proposal-tools.d.ts +3 -0
- package/dist/tools/proposal-tools.js +428 -0
- package/dist/tools/report-tools.d.ts +3 -0
- package/dist/tools/report-tools.js +245 -0
- package/dist/tools/scenario-management-tools.d.ts +3 -0
- package/dist/tools/scenario-management-tools.js +136 -0
- package/dist/views/verification-packet.html +211 -0
- package/host-configs/cowork.json +11 -0
- package/host-configs/goose.yaml +22 -0
- package/host-configs/openclaw-manifest.json +16 -0
- package/package.json +66 -0
- package/skills/SKILL.md +162 -0
- package/skills/manifest.json +51 -0
- package/skills/tools/activate_plan.md +36 -0
- package/skills/tools/coach.md +59 -0
- package/skills/tools/comprehensive_plan.md +65 -0
- package/skills/tools/create_plan.md +59 -0
- package/skills/tools/create_saved_plan.md +49 -0
- package/skills/tools/delete_plan.md +42 -0
- package/skills/tools/delete_scenario.md +38 -0
- package/skills/tools/generate_proposal.md +63 -0
- package/skills/tools/generate_retirement_report.md +50 -0
- package/skills/tools/get_active_plan.md +44 -0
- package/skills/tools/get_baseline_forecast.md +47 -0
- package/skills/tools/get_plan.md +44 -0
- package/skills/tools/get_plan_components.md +50 -0
- package/skills/tools/get_scenario.md +46 -0
- package/skills/tools/list_plans.md +44 -0
- package/skills/tools/list_scenarios.md +42 -0
- package/skills/tools/medicare-guardian.md +59 -0
- package/skills/tools/nestpilot_run_plan.md +61 -0
- package/skills/tools/optimize_roth_conversion.md +107 -0
- package/skills/tools/optimize_ss_claiming.md +30 -0
- package/skills/tools/rename_plan.md +34 -0
- package/skills/tools/retirement-planner.md +55 -0
- package/skills/tools/run_forecast.md +65 -0
- package/skills/tools/run_saved_forecast.md +52 -0
- package/skills/tools/run_scenario.md +66 -0
- package/skills/tools/save_plan.md +48 -0
- package/skills/tools/save_scenario.md +50 -0
- package/skills/tools/verify_forecast.md +43 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory rate limiter — sliding window per key.
|
|
3
|
+
* Adapted from mcp-edge.
|
|
4
|
+
*/
|
|
5
|
+
export class InMemoryRateLimiter {
|
|
6
|
+
windowMs;
|
|
7
|
+
maxRequests;
|
|
8
|
+
buckets = new Map();
|
|
9
|
+
constructor(windowMs, maxRequests) {
|
|
10
|
+
this.windowMs = windowMs;
|
|
11
|
+
this.maxRequests = maxRequests;
|
|
12
|
+
}
|
|
13
|
+
check(key, nowMs = Date.now()) {
|
|
14
|
+
const existing = this.buckets.get(key);
|
|
15
|
+
if (!existing || nowMs - existing.windowStartMs >= this.windowMs) {
|
|
16
|
+
this.buckets.set(key, { count: 1, windowStartMs: nowMs });
|
|
17
|
+
return {
|
|
18
|
+
allowed: true,
|
|
19
|
+
remaining: this.maxRequests - 1,
|
|
20
|
+
resetAtMs: nowMs + this.windowMs,
|
|
21
|
+
retryAfterSeconds: 0,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
if (existing.count >= this.maxRequests) {
|
|
25
|
+
const resetAtMs = existing.windowStartMs + this.windowMs;
|
|
26
|
+
return {
|
|
27
|
+
allowed: false,
|
|
28
|
+
remaining: 0,
|
|
29
|
+
resetAtMs,
|
|
30
|
+
retryAfterSeconds: Math.max(1, Math.ceil((resetAtMs - nowMs) / 1000)),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
existing.count += 1;
|
|
34
|
+
return {
|
|
35
|
+
allowed: true,
|
|
36
|
+
remaining: Math.max(0, this.maxRequests - existing.count),
|
|
37
|
+
resetAtMs: existing.windowStartMs + this.windowMs,
|
|
38
|
+
retryAfterSeconds: 0,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { IncomingHttpHeaders } from "node:http";
|
|
2
|
+
import type { AppConfig } from "./config.js";
|
|
3
|
+
export type AuthResult = {
|
|
4
|
+
ok: true;
|
|
5
|
+
userId: string;
|
|
6
|
+
tokenHash?: string;
|
|
7
|
+
bearerToken?: string;
|
|
8
|
+
} | {
|
|
9
|
+
ok: false;
|
|
10
|
+
statusCode: number;
|
|
11
|
+
message: string;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Decodes the `sub` claim from a JWT payload without validating the signature.
|
|
15
|
+
* The backend is the authoritative JWT validator - this is only used for
|
|
16
|
+
* logging and rate-limiting context.
|
|
17
|
+
*/
|
|
18
|
+
export declare function decodeJwtSub(token: string): string | undefined;
|
|
19
|
+
export declare function authenticateRequest(headers: IncomingHttpHeaders, config: AppConfig): AuthResult;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request authentication - adapted from mcp-edge.
|
|
3
|
+
*
|
|
4
|
+
* Supports three modes:
|
|
5
|
+
* - "token" (default): Opaque bearer tokens validated against MCP_AUTH_TOKENS
|
|
6
|
+
* - "jwt-passthrough": Bearer JWT forwarded to backend as-is; sub claim
|
|
7
|
+
* decoded locally for logging/rate-limiting only
|
|
8
|
+
* - No auth required: Falls back to X-User-ID header or default user
|
|
9
|
+
*/
|
|
10
|
+
import { createHash } from "node:crypto";
|
|
11
|
+
function readHeader(headers, name) {
|
|
12
|
+
const raw = headers[name];
|
|
13
|
+
return Array.isArray(raw) ? raw[0] : raw;
|
|
14
|
+
}
|
|
15
|
+
function hashToken(value) {
|
|
16
|
+
return createHash("sha256").update(value).digest("hex");
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Decodes the `sub` claim from a JWT payload without validating the signature.
|
|
20
|
+
* The backend is the authoritative JWT validator - this is only used for
|
|
21
|
+
* logging and rate-limiting context.
|
|
22
|
+
*/
|
|
23
|
+
export function decodeJwtSub(token) {
|
|
24
|
+
try {
|
|
25
|
+
const parts = token.split(".");
|
|
26
|
+
if (parts.length !== 3)
|
|
27
|
+
return undefined;
|
|
28
|
+
// JWT payload is base64url-encoded; convert to standard base64
|
|
29
|
+
const base64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
30
|
+
const json = Buffer.from(base64, "base64").toString("utf-8");
|
|
31
|
+
const payload = JSON.parse(json);
|
|
32
|
+
return typeof payload.sub === "string" ? payload.sub : undefined;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export function authenticateRequest(headers, config) {
|
|
39
|
+
const userHeader = readHeader(headers, "x-user-id")?.trim();
|
|
40
|
+
const authHeader = readHeader(headers, "authorization");
|
|
41
|
+
// JWT passthrough mode
|
|
42
|
+
if (config.authMode === "jwt-passthrough") {
|
|
43
|
+
if (!authHeader || !authHeader.toLowerCase().startsWith("bearer ")) {
|
|
44
|
+
if (config.requireAuth) {
|
|
45
|
+
return {
|
|
46
|
+
ok: false,
|
|
47
|
+
statusCode: 401,
|
|
48
|
+
message: "Missing bearer token. Provide Authorization: Bearer <jwt>.",
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
ok: true,
|
|
53
|
+
userId: userHeader && userHeader.length > 0
|
|
54
|
+
? userHeader
|
|
55
|
+
: config.defaultUserId,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const token = authHeader.slice("Bearer ".length).trim();
|
|
59
|
+
if (!token) {
|
|
60
|
+
if (config.requireAuth) {
|
|
61
|
+
return { ok: false, statusCode: 401, message: "Bearer token is empty." };
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
ok: true,
|
|
65
|
+
userId: userHeader && userHeader.length > 0
|
|
66
|
+
? userHeader
|
|
67
|
+
: config.defaultUserId,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
// Decode sub claim for logging/rate-limiting; backend validates the JWT
|
|
71
|
+
const sub = decodeJwtSub(token);
|
|
72
|
+
return {
|
|
73
|
+
ok: true,
|
|
74
|
+
userId: sub ?? userHeader ?? config.defaultUserId,
|
|
75
|
+
bearerToken: token,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
// Opaque token mode
|
|
79
|
+
const authRequired = config.requireAuth || config.authTokens.size > 0;
|
|
80
|
+
if (!authRequired) {
|
|
81
|
+
return {
|
|
82
|
+
ok: true,
|
|
83
|
+
userId: userHeader && userHeader.length > 0
|
|
84
|
+
? userHeader
|
|
85
|
+
: config.defaultUserId,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
if (!authHeader || !authHeader.toLowerCase().startsWith("bearer ")) {
|
|
89
|
+
return {
|
|
90
|
+
ok: false,
|
|
91
|
+
statusCode: 401,
|
|
92
|
+
message: "Missing bearer token. Provide Authorization: Bearer <token>.",
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
const token = authHeader.slice("Bearer ".length).trim();
|
|
96
|
+
if (!token) {
|
|
97
|
+
return { ok: false, statusCode: 401, message: "Bearer token is empty." };
|
|
98
|
+
}
|
|
99
|
+
if (config.authTokens.size === 0) {
|
|
100
|
+
return {
|
|
101
|
+
ok: false,
|
|
102
|
+
statusCode: 500,
|
|
103
|
+
message: "MCP auth is enabled but MCP_AUTH_TOKENS is not configured.",
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
if (!config.authTokens.has(token)) {
|
|
107
|
+
return { ok: false, statusCode: 401, message: "Invalid bearer token." };
|
|
108
|
+
}
|
|
109
|
+
const tokenHash = hashToken(token).slice(0, 16);
|
|
110
|
+
return {
|
|
111
|
+
ok: true,
|
|
112
|
+
userId: userHeader && userHeader.length > 0
|
|
113
|
+
? userHeader
|
|
114
|
+
: `token:${tokenHash}`,
|
|
115
|
+
tokenHash,
|
|
116
|
+
bearerToken: token,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill Pack v1 — FEAT-0057
|
|
3
|
+
*
|
|
4
|
+
* Public API surface for the Retirement Skill Pack v1.
|
|
5
|
+
*
|
|
6
|
+
* @feature FEAT-0057
|
|
7
|
+
* @design DES-0055
|
|
8
|
+
*/
|
|
9
|
+
export { SkillManifestV1Schema, validateManifest, requireValidManifest, type SkillManifestV1, type SkillConstraint, type RequiredTool, type SkillIOField, type SkillIO, type SkillExample, type ManifestValidation, type ManifestValidationResult, type ManifestValidationError, } from "./skill-manifest-schema.js";
|
|
10
|
+
export { registerSkill, lookupSkill, getAllSkills, checkCompatibility, requireCompatibility, semverSatisfies, _resetSkillRegistry, type SkillLookup, type SkillLookupResult, type SkillLookupMiss, type CompatibilityCheck, type CompatibilityCheckResult, type CompatibilityCheckFailure, type CompatibilityToolFailure, } from "./skill-registry.js";
|
|
11
|
+
export { executeSkill, validateIO, _resetRunCounter, type ExecutionRecord, type ExecutionContext, type ExecutionStatus, type StepTrace, type IOValidation, type IOValidationResult, type IOValidationError, } from "./skill-executor.js";
|
|
12
|
+
export { loadPlanContext, createScenarioFromTemplate, draftProposalFromScenario, validateProposalAgainstPolicy, generateAdvisorSummary, RETIREMENT_PACK_V1, RETIREMENT_PACK_VERSION, RETIREMENT_PACK_NAME, } from "./retirement-pack-v1.js";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill Pack v1 — FEAT-0057
|
|
3
|
+
*
|
|
4
|
+
* Public API surface for the Retirement Skill Pack v1.
|
|
5
|
+
*
|
|
6
|
+
* @feature FEAT-0057
|
|
7
|
+
* @design DES-0055
|
|
8
|
+
*/
|
|
9
|
+
// Schema and validation
|
|
10
|
+
export { SkillManifestV1Schema, validateManifest, requireValidManifest, } from "./skill-manifest-schema.js";
|
|
11
|
+
// Registry
|
|
12
|
+
export { registerSkill, lookupSkill, getAllSkills, checkCompatibility, requireCompatibility, semverSatisfies, _resetSkillRegistry, } from "./skill-registry.js";
|
|
13
|
+
// Executor
|
|
14
|
+
export { executeSkill, validateIO, _resetRunCounter, } from "./skill-executor.js";
|
|
15
|
+
// Retirement Pack v1 manifests
|
|
16
|
+
export { loadPlanContext, createScenarioFromTemplate, draftProposalFromScenario, validateProposalAgainstPolicy, generateAdvisorSummary, RETIREMENT_PACK_V1, RETIREMENT_PACK_VERSION, RETIREMENT_PACK_NAME, } from "./retirement-pack-v1.js";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retirement Skill Pack v1 — FEAT-0057
|
|
3
|
+
*
|
|
4
|
+
* Five core retirement workflow skill manifests in v1 format.
|
|
5
|
+
* Each manifest declares typed I/O contracts, safety constraints,
|
|
6
|
+
* required tool dependencies with minimum contract versions,
|
|
7
|
+
* and example input/output pairs.
|
|
8
|
+
*
|
|
9
|
+
* Skills:
|
|
10
|
+
* 1. load-plan-context
|
|
11
|
+
* 2. create-scenario-from-template
|
|
12
|
+
* 3. draft-proposal-from-scenario
|
|
13
|
+
* 4. validate-proposal-against-policy
|
|
14
|
+
* 5. generate-advisor-summary
|
|
15
|
+
*
|
|
16
|
+
* @feature FEAT-0057
|
|
17
|
+
* @design DES-0055
|
|
18
|
+
*/
|
|
19
|
+
import type { SkillManifestV1 } from "./skill-manifest-schema.js";
|
|
20
|
+
export declare const loadPlanContext: SkillManifestV1;
|
|
21
|
+
export declare const createScenarioFromTemplate: SkillManifestV1;
|
|
22
|
+
export declare const draftProposalFromScenario: SkillManifestV1;
|
|
23
|
+
export declare const validateProposalAgainstPolicy: SkillManifestV1;
|
|
24
|
+
export declare const generateAdvisorSummary: SkillManifestV1;
|
|
25
|
+
/** All five core retirement skills in v1 format. */
|
|
26
|
+
export declare const RETIREMENT_PACK_V1: ReadonlyArray<SkillManifestV1>;
|
|
27
|
+
export declare const RETIREMENT_PACK_VERSION = "1.0.0";
|
|
28
|
+
export declare const RETIREMENT_PACK_NAME = "retirement-v1";
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
// ── 1. Load Plan Context ────────────────────────────────────────────────
|
|
2
|
+
export const loadPlanContext = {
|
|
3
|
+
skillId: "load-plan-context",
|
|
4
|
+
version: "1.0.0",
|
|
5
|
+
displayName: "Load Plan Context",
|
|
6
|
+
description: "Loads and validates a retirement plan context from user inputs, producing a normalized plan object for downstream skills.",
|
|
7
|
+
pack: "retirement-v1",
|
|
8
|
+
requiredTools: [
|
|
9
|
+
{ toolName: "create_plan", minContractVersion: "1.0.0" },
|
|
10
|
+
],
|
|
11
|
+
inputSchema: {
|
|
12
|
+
fields: [
|
|
13
|
+
{ name: "currentAge", type: "number", required: true, description: "Current age of the plan holder" },
|
|
14
|
+
{ name: "retireAge", type: "number", required: true, description: "Target retirement age" },
|
|
15
|
+
{ name: "currentBalance", type: "number", required: true, description: "Current portfolio balance in USD" },
|
|
16
|
+
{ name: "monthlyContribution", type: "number", required: true, description: "Monthly contribution in USD" },
|
|
17
|
+
{ name: "expectedReturnRate", type: "number", required: true, description: "Expected annual return rate (decimal)" },
|
|
18
|
+
{ name: "withdrawalRate", type: "number", required: false, description: "Annual withdrawal rate in retirement (decimal)" },
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
outputSchema: {
|
|
22
|
+
fields: [
|
|
23
|
+
{ name: "projectedBalanceAtRetirement", type: "number", required: true, description: "Projected balance at retirement age" },
|
|
24
|
+
{ name: "readinessScore", type: "number", required: true, description: "Retirement readiness score (0-100)" },
|
|
25
|
+
{ name: "yearsToRetirement", type: "number", required: true, description: "Years until target retirement age" },
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
constraints: [
|
|
29
|
+
{ description: "Must not recommend specific investment products", enforced: true },
|
|
30
|
+
{ description: "All assumptions must be surfaced in output", enforced: true },
|
|
31
|
+
{ description: "Return rate must be user-provided, not predicted", enforced: true },
|
|
32
|
+
],
|
|
33
|
+
nonPermittedActions: [
|
|
34
|
+
"Recommend specific funds or advisors",
|
|
35
|
+
"Modify user account data",
|
|
36
|
+
"Execute financial transactions",
|
|
37
|
+
],
|
|
38
|
+
sensitiveDataRequirements: [
|
|
39
|
+
"Financial balance information",
|
|
40
|
+
"Age and retirement timeline",
|
|
41
|
+
],
|
|
42
|
+
examples: [
|
|
43
|
+
{
|
|
44
|
+
description: "Basic plan context for a 40-year-old",
|
|
45
|
+
input: {
|
|
46
|
+
currentAge: 40,
|
|
47
|
+
retireAge: 65,
|
|
48
|
+
currentBalance: 250000,
|
|
49
|
+
monthlyContribution: 2000,
|
|
50
|
+
expectedReturnRate: 0.07,
|
|
51
|
+
},
|
|
52
|
+
expectedOutput: {
|
|
53
|
+
projectedBalanceAtRetirement: 1200000,
|
|
54
|
+
readinessScore: 82,
|
|
55
|
+
yearsToRetirement: 25,
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
};
|
|
60
|
+
// ── 2. Create Scenario from Template ────────────────────────────────────
|
|
61
|
+
export const createScenarioFromTemplate = {
|
|
62
|
+
skillId: "create-scenario-from-template",
|
|
63
|
+
version: "1.0.0",
|
|
64
|
+
displayName: "Create Scenario from Template",
|
|
65
|
+
description: "Creates a what-if retirement scenario from a predefined template, comparing against a base plan to surface deltas and trade-offs.",
|
|
66
|
+
pack: "retirement-v1",
|
|
67
|
+
requiredTools: [
|
|
68
|
+
{ toolName: "create_plan", minContractVersion: "1.0.0" },
|
|
69
|
+
{ toolName: "run_scenario", minContractVersion: "1.0.0" },
|
|
70
|
+
],
|
|
71
|
+
inputSchema: {
|
|
72
|
+
fields: [
|
|
73
|
+
{ name: "basePlan", type: "object", required: true, description: "Base retirement plan context" },
|
|
74
|
+
{ name: "scenarioType", type: "string", required: true, description: "Template type: early-retirement, market-downturn, delayed-ss, roth-conversion" },
|
|
75
|
+
{ name: "adjustments", type: "object", required: false, description: "Override parameters for the scenario" },
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
outputSchema: {
|
|
79
|
+
fields: [
|
|
80
|
+
{ name: "scenarioResult", type: "object", required: true, description: "Full scenario projection result" },
|
|
81
|
+
{ name: "deltaVsBase", type: "object", required: true, description: "Differences compared to the base plan" },
|
|
82
|
+
{ name: "tradeOffs", type: "array", required: true, description: "List of identified trade-offs" },
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
constraints: [
|
|
86
|
+
{ description: "Scenarios are exploration, not recommendations", enforced: true },
|
|
87
|
+
{ description: "Must always present delta vs. base case", enforced: true },
|
|
88
|
+
{ description: "Frame as 'if X then Y', never 'you should do X'", enforced: true },
|
|
89
|
+
],
|
|
90
|
+
nonPermittedActions: [
|
|
91
|
+
"Present scenarios as advice or recommendations",
|
|
92
|
+
"Hide assumptions behind scenario adjustments",
|
|
93
|
+
"Execute trades or account changes",
|
|
94
|
+
],
|
|
95
|
+
sensitiveDataRequirements: [
|
|
96
|
+
"Full retirement plan context",
|
|
97
|
+
"Portfolio balance details",
|
|
98
|
+
],
|
|
99
|
+
examples: [
|
|
100
|
+
{
|
|
101
|
+
description: "Early retirement scenario — retire at 60 instead of 65",
|
|
102
|
+
input: {
|
|
103
|
+
basePlan: { currentAge: 40, retireAge: 65, currentBalance: 250000 },
|
|
104
|
+
scenarioType: "early-retirement",
|
|
105
|
+
adjustments: { retireAge: 60 },
|
|
106
|
+
},
|
|
107
|
+
expectedOutput: {
|
|
108
|
+
scenarioResult: { projectedBalance: 850000 },
|
|
109
|
+
deltaVsBase: { balanceDelta: -350000, yearsDelta: -5 },
|
|
110
|
+
tradeOffs: ["Lower balance at retirement", "More years in retirement"],
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
};
|
|
115
|
+
// ── 3. Draft Proposal from Scenario ─────────────────────────────────────
|
|
116
|
+
export const draftProposalFromScenario = {
|
|
117
|
+
skillId: "draft-proposal-from-scenario",
|
|
118
|
+
version: "1.0.0",
|
|
119
|
+
displayName: "Draft Proposal from Scenario",
|
|
120
|
+
description: "Generates a structured retirement proposal document from a completed scenario, suitable for advisor review or client presentation.",
|
|
121
|
+
pack: "retirement-v1",
|
|
122
|
+
requiredTools: [
|
|
123
|
+
{ toolName: "run_scenario", minContractVersion: "1.0.0" },
|
|
124
|
+
{ toolName: "run_forecast", minContractVersion: "1.0.0" },
|
|
125
|
+
],
|
|
126
|
+
inputSchema: {
|
|
127
|
+
fields: [
|
|
128
|
+
{ name: "scenarioResult", type: "object", required: true, description: "Completed scenario projection result" },
|
|
129
|
+
{ name: "clientName", type: "string", required: true, description: "Client display name for the proposal" },
|
|
130
|
+
{ name: "includeCharts", type: "boolean", required: false, description: "Whether to include chart data in output" },
|
|
131
|
+
],
|
|
132
|
+
},
|
|
133
|
+
outputSchema: {
|
|
134
|
+
fields: [
|
|
135
|
+
{ name: "proposalTitle", type: "string", required: true, description: "Generated proposal title" },
|
|
136
|
+
{ name: "sections", type: "array", required: true, description: "Ordered list of proposal sections" },
|
|
137
|
+
{ name: "assumptions", type: "array", required: true, description: "All assumptions used in the proposal" },
|
|
138
|
+
{ name: "forecastSummary", type: "object", required: true, description: "Condensed forecast data for the proposal" },
|
|
139
|
+
],
|
|
140
|
+
},
|
|
141
|
+
constraints: [
|
|
142
|
+
{ description: "Proposals must disclose all assumptions", enforced: true },
|
|
143
|
+
{ description: "Must not include product recommendations", enforced: true },
|
|
144
|
+
{ description: "All numbers must be traceable to forecast output", enforced: true },
|
|
145
|
+
],
|
|
146
|
+
nonPermittedActions: [
|
|
147
|
+
"Include affiliate links or product pitches",
|
|
148
|
+
"Omit assumptions from the proposal",
|
|
149
|
+
"Present projections as guarantees",
|
|
150
|
+
],
|
|
151
|
+
sensitiveDataRequirements: [
|
|
152
|
+
"Client name",
|
|
153
|
+
"Full scenario and forecast data",
|
|
154
|
+
],
|
|
155
|
+
examples: [
|
|
156
|
+
{
|
|
157
|
+
description: "Proposal draft for a base retirement scenario",
|
|
158
|
+
input: {
|
|
159
|
+
scenarioResult: { projectedBalance: 1200000, readinessScore: 82 },
|
|
160
|
+
clientName: "Jane Doe",
|
|
161
|
+
includeCharts: true,
|
|
162
|
+
},
|
|
163
|
+
expectedOutput: {
|
|
164
|
+
proposalTitle: "Retirement Proposal — Jane Doe",
|
|
165
|
+
sections: ["Executive Summary", "Forecast Details", "Assumptions", "Next Steps"],
|
|
166
|
+
assumptions: ["7% annual return", "3% inflation", "65 retirement age"],
|
|
167
|
+
forecastSummary: { balanceAtRetirement: 1200000, monthlyIncome: 4800 },
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
};
|
|
172
|
+
// ── 4. Validate Proposal Against Policy ─────────────────────────────────
|
|
173
|
+
export const validateProposalAgainstPolicy = {
|
|
174
|
+
skillId: "validate-proposal-against-policy",
|
|
175
|
+
version: "1.0.0",
|
|
176
|
+
displayName: "Validate Proposal Against Policy",
|
|
177
|
+
description: "Validates a retirement proposal against platform policies and compliance rules, blocking non-compliant content with explicit reasons.",
|
|
178
|
+
pack: "retirement-v1",
|
|
179
|
+
requiredTools: [
|
|
180
|
+
{ toolName: "verify_forecast", minContractVersion: "1.0.0" },
|
|
181
|
+
],
|
|
182
|
+
inputSchema: {
|
|
183
|
+
fields: [
|
|
184
|
+
{ name: "proposal", type: "object", required: true, description: "The proposal to validate" },
|
|
185
|
+
{ name: "policyRules", type: "array", required: true, description: "List of policy rule IDs to check against" },
|
|
186
|
+
],
|
|
187
|
+
},
|
|
188
|
+
outputSchema: {
|
|
189
|
+
fields: [
|
|
190
|
+
{ name: "compliant", type: "boolean", required: true, description: "Whether the proposal passes all policy checks" },
|
|
191
|
+
{ name: "violations", type: "array", required: true, description: "List of policy violations found (empty if compliant)" },
|
|
192
|
+
{ name: "verificationTrace", type: "object", required: true, description: "Detailed verification trace for audit" },
|
|
193
|
+
],
|
|
194
|
+
},
|
|
195
|
+
constraints: [
|
|
196
|
+
{ description: "Policy violations must block proposal delivery", enforced: true },
|
|
197
|
+
{ description: "Each violation must include a machine-readable reason code", enforced: true },
|
|
198
|
+
{ description: "Verification trace must be included for audit trail", enforced: true },
|
|
199
|
+
],
|
|
200
|
+
nonPermittedActions: [
|
|
201
|
+
"Skip policy checks for any reason",
|
|
202
|
+
"Suppress or hide policy violations",
|
|
203
|
+
"Auto-approve proposals without verification",
|
|
204
|
+
],
|
|
205
|
+
sensitiveDataRequirements: [
|
|
206
|
+
"Full proposal content",
|
|
207
|
+
"Policy rule definitions",
|
|
208
|
+
],
|
|
209
|
+
examples: [
|
|
210
|
+
{
|
|
211
|
+
description: "Compliant proposal with no violations",
|
|
212
|
+
input: {
|
|
213
|
+
proposal: {
|
|
214
|
+
proposalTitle: "Retirement Proposal — Jane Doe",
|
|
215
|
+
assumptions: ["7% annual return"],
|
|
216
|
+
},
|
|
217
|
+
policyRules: ["no-product-recs", "assumptions-disclosed"],
|
|
218
|
+
},
|
|
219
|
+
expectedOutput: {
|
|
220
|
+
compliant: true,
|
|
221
|
+
violations: [],
|
|
222
|
+
verificationTrace: { checkedRules: 2, passedRules: 2 },
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
],
|
|
226
|
+
};
|
|
227
|
+
// ── 5. Generate Advisor Summary ─────────────────────────────────────────
|
|
228
|
+
export const generateAdvisorSummary = {
|
|
229
|
+
skillId: "generate-advisor-summary",
|
|
230
|
+
version: "1.0.0",
|
|
231
|
+
displayName: "Generate Advisor Summary",
|
|
232
|
+
description: "Generates a concise advisor-facing summary from a validated proposal and forecast, highlighting key metrics, risks, and recommended discussion points.",
|
|
233
|
+
pack: "retirement-v1",
|
|
234
|
+
requiredTools: [
|
|
235
|
+
{ toolName: "run_forecast", minContractVersion: "1.0.0" },
|
|
236
|
+
{ toolName: "coach", minContractVersion: "1.0.0" },
|
|
237
|
+
],
|
|
238
|
+
inputSchema: {
|
|
239
|
+
fields: [
|
|
240
|
+
{ name: "proposal", type: "object", required: true, description: "Validated retirement proposal" },
|
|
241
|
+
{ name: "forecastData", type: "object", required: true, description: "Full forecast projection data" },
|
|
242
|
+
{ name: "clientProfile", type: "object", required: true, description: "Client demographic and risk profile" },
|
|
243
|
+
],
|
|
244
|
+
},
|
|
245
|
+
outputSchema: {
|
|
246
|
+
fields: [
|
|
247
|
+
{ name: "summaryTitle", type: "string", required: true, description: "Summary document title" },
|
|
248
|
+
{ name: "keyMetrics", type: "object", required: true, description: "Headline metrics for the advisor" },
|
|
249
|
+
{ name: "risks", type: "array", required: true, description: "Identified risks and concerns" },
|
|
250
|
+
{ name: "discussionPoints", type: "array", required: true, description: "Suggested topics for client meeting" },
|
|
251
|
+
],
|
|
252
|
+
},
|
|
253
|
+
constraints: [
|
|
254
|
+
{ description: "Summary is for advisor use only, not client-facing", enforced: true },
|
|
255
|
+
{ description: "Must include risk factors and longevity assessment", enforced: true },
|
|
256
|
+
{ description: "Discussion points must be actionable, not generic", enforced: true },
|
|
257
|
+
],
|
|
258
|
+
nonPermittedActions: [
|
|
259
|
+
"Share advisor summary directly with clients",
|
|
260
|
+
"Include specific product recommendations",
|
|
261
|
+
"Omit identified risk factors",
|
|
262
|
+
],
|
|
263
|
+
sensitiveDataRequirements: [
|
|
264
|
+
"Client demographic profile",
|
|
265
|
+
"Full forecast and proposal data",
|
|
266
|
+
"Risk assessment details",
|
|
267
|
+
],
|
|
268
|
+
examples: [
|
|
269
|
+
{
|
|
270
|
+
description: "Advisor summary for a near-retirement client",
|
|
271
|
+
input: {
|
|
272
|
+
proposal: { proposalTitle: "Retirement Proposal — John Smith" },
|
|
273
|
+
forecastData: { balanceAtRetirement: 1500000, monthlyIncome: 6000 },
|
|
274
|
+
clientProfile: { age: 62, riskTolerance: "moderate" },
|
|
275
|
+
},
|
|
276
|
+
expectedOutput: {
|
|
277
|
+
summaryTitle: "Advisor Brief — John Smith",
|
|
278
|
+
keyMetrics: { readinessScore: 88, yearsOfCoverage: 28 },
|
|
279
|
+
risks: ["Longevity risk beyond age 90", "Healthcare cost inflation"],
|
|
280
|
+
discussionPoints: ["Roth conversion window before RMDs", "Medicare enrollment timing"],
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
],
|
|
284
|
+
};
|
|
285
|
+
// ── Pack index ──────────────────────────────────────────────────────────
|
|
286
|
+
/** All five core retirement skills in v1 format. */
|
|
287
|
+
export const RETIREMENT_PACK_V1 = [
|
|
288
|
+
loadPlanContext,
|
|
289
|
+
createScenarioFromTemplate,
|
|
290
|
+
draftProposalFromScenario,
|
|
291
|
+
validateProposalAgainstPolicy,
|
|
292
|
+
generateAdvisorSummary,
|
|
293
|
+
];
|
|
294
|
+
export const RETIREMENT_PACK_VERSION = "1.0.0";
|
|
295
|
+
export const RETIREMENT_PACK_NAME = "retirement-v1";
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill Executor Adapter — FEAT-0057
|
|
3
|
+
*
|
|
4
|
+
* Validates input/output schemas per skill and runs skill steps
|
|
5
|
+
* through the MCP tool dispatch pipeline. Emits typed output
|
|
6
|
+
* and execution trace records.
|
|
7
|
+
*
|
|
8
|
+
* @feature FEAT-0057
|
|
9
|
+
* @design DES-0055
|
|
10
|
+
*/
|
|
11
|
+
import type { SkillManifestV1, SkillIO } from "./skill-manifest-schema.js";
|
|
12
|
+
import { type PolicyDecision, type UserRole } from "../policy/policy-engine.js";
|
|
13
|
+
export type ExecutionStatus = "success" | "blocked" | "error";
|
|
14
|
+
export interface StepTrace {
|
|
15
|
+
toolName: string;
|
|
16
|
+
status: "executed" | "skipped" | "blocked";
|
|
17
|
+
durationMs: number;
|
|
18
|
+
policyDecision?: PolicyDecision;
|
|
19
|
+
}
|
|
20
|
+
export interface ExecutionRecord {
|
|
21
|
+
runId: string;
|
|
22
|
+
skillId: string;
|
|
23
|
+
version: string;
|
|
24
|
+
status: ExecutionStatus;
|
|
25
|
+
stepTrace: StepTrace[];
|
|
26
|
+
policyDecisions: PolicyDecision[];
|
|
27
|
+
startedAt: string;
|
|
28
|
+
completedAt: string;
|
|
29
|
+
output?: Record<string, unknown>;
|
|
30
|
+
error?: string;
|
|
31
|
+
}
|
|
32
|
+
export interface ExecutionContext {
|
|
33
|
+
actorId: string;
|
|
34
|
+
role: UserRole;
|
|
35
|
+
input: Record<string, unknown>;
|
|
36
|
+
/** Tool call dispatcher — injected to decouple from transport. */
|
|
37
|
+
callTool: (toolName: string, args: Record<string, unknown>) => Promise<unknown>;
|
|
38
|
+
}
|
|
39
|
+
export interface IOValidationResult {
|
|
40
|
+
valid: true;
|
|
41
|
+
}
|
|
42
|
+
export interface IOValidationError {
|
|
43
|
+
valid: false;
|
|
44
|
+
errors: string[];
|
|
45
|
+
}
|
|
46
|
+
export type IOValidation = IOValidationResult | IOValidationError;
|
|
47
|
+
/**
|
|
48
|
+
* Validates a payload against a skill I/O schema definition.
|
|
49
|
+
* Checks that all required fields are present and have the correct type.
|
|
50
|
+
*/
|
|
51
|
+
export declare function validateIO(payload: Record<string, unknown>, schema: SkillIO, direction: "input" | "output"): IOValidation;
|
|
52
|
+
/**
|
|
53
|
+
* Executes a skill by:
|
|
54
|
+
* 1. Running compatibility checks against tool contracts.
|
|
55
|
+
* 2. Validating input against the skill's input schema.
|
|
56
|
+
* 3. Evaluating policy for each required tool.
|
|
57
|
+
* 4. Calling each required tool via the injected dispatcher.
|
|
58
|
+
* 5. Validating output against the skill's output schema.
|
|
59
|
+
* 6. Returning an execution record with full trace.
|
|
60
|
+
*/
|
|
61
|
+
export declare function executeSkill(manifest: SkillManifestV1, ctx: ExecutionContext): Promise<ExecutionRecord>;
|
|
62
|
+
/**
|
|
63
|
+
* Resets the run counter (for testing only).
|
|
64
|
+
*/
|
|
65
|
+
export declare function _resetRunCounter(): void;
|