shakers-cli 0.0.0-stage → 0.14.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/LICENSE +59 -0
- package/README.md +1233 -2
- package/bin/add-agent.js +88 -0
- package/bin/add-project.js +88 -0
- package/bin/add-role.js +61 -0
- package/bin/add-skill.js +88 -0
- package/bin/ai-usage.js +99 -0
- package/bin/alma.js +99 -0
- package/bin/applications.js +69 -0
- package/bin/availability.js +70 -0
- package/bin/certifications.js +69 -0
- package/bin/certify.js +73 -0
- package/bin/change-role.js +61 -0
- package/bin/config.js +124 -0
- package/bin/experiences.js +69 -0
- package/bin/find-projects.js +89 -0
- package/bin/invitations.js +69 -0
- package/bin/lang.js +70 -0
- package/bin/login.js +216 -0
- package/bin/mcp.js +124 -0
- package/bin/mcpb.js +5 -0
- package/bin/me.js +69 -0
- package/bin/onboarding.js +134 -0
- package/bin/portfolios.js +69 -0
- package/bin/rate.js +70 -0
- package/bin/register.js +337 -0
- package/bin/report-html.js +134 -0
- package/bin/save.js +77 -0
- package/bin/shakers.js +363 -0
- package/bin/share.js +72 -0
- package/bin/show-project.js +72 -0
- package/bin/socials.js +70 -0
- package/bin/superadmin.js +351 -0
- package/bin/update-check-worker.js +70 -0
- package/bin/update.js +111 -0
- package/package.json +40 -4
- package/src/agent-category.js +13 -0
- package/src/agent-evaluation.js +177 -0
- package/src/agent-org-chart.js +355 -0
- package/src/agent-synthesis.js +132 -0
- package/src/agent-usage.js +118 -0
- package/src/agents-client.js +165 -0
- package/src/ai-fluency-cell.js +72 -0
- package/src/ai-profile-client.js +45 -0
- package/src/ai-profile-preview.js +154 -0
- package/src/ai.js +66 -0
- package/src/alma-client.js +123 -0
- package/src/alma-flow.js +32 -0
- package/src/alma-repo-context.js +47 -0
- package/src/alma-shell.js +161 -0
- package/src/ansi.js +62 -0
- package/src/applications-client.js +79 -0
- package/src/applications-flow.js +58 -0
- package/src/auth-client.js +144 -0
- package/src/auth-device-client.js +118 -0
- package/src/auth-method-picker.js +44 -0
- package/src/auth-methods.js +35 -0
- package/src/auth-session-store.js +215 -0
- package/src/authorship-mismatch.js +62 -0
- package/src/authorship.js +204 -0
- package/src/automations-detector.js +170 -0
- package/src/availability-client.js +93 -0
- package/src/availability-flow.js +124 -0
- package/src/backend-request.js +191 -0
- package/src/blind-approval-extract.js +48 -0
- package/src/brand-ansi.js +14 -0
- package/src/brand-svg.js +9 -0
- package/src/browser-tools-detector.js +21 -0
- package/src/build-next-level.js +56 -0
- package/src/certifications-client.js +89 -0
- package/src/certifications-flow.js +75 -0
- package/src/certify-dimension-client.js +163 -0
- package/src/certify-dimension-flow.js +135 -0
- package/src/certify-disclaimer.js +51 -0
- package/src/certify-remediation-prompt.js +23 -0
- package/src/cli-args.js +83 -0
- package/src/command-audit.js +105 -0
- package/src/config-dir.js +120 -0
- package/src/config.js +877 -0
- package/src/consent-flow.js +115 -0
- package/src/consent-skip.js +27 -0
- package/src/cv-file.js +62 -0
- package/src/cv-text.js +92 -0
- package/src/decisions-extract.js +43 -0
- package/src/detect-repos.js +145 -0
- package/src/detected-tools.js +15 -0
- package/src/detection-evidence.js +105 -0
- package/src/detection-noise.js +132 -0
- package/src/detectors.js +221 -0
- package/src/device-auth-store.js +100 -0
- package/src/device-login.js +120 -0
- package/src/email-verification.js +152 -0
- package/src/env-paths.js +30 -0
- package/src/find-projects-cache.js +89 -0
- package/src/find-projects-client.js +148 -0
- package/src/find-projects-flow.js +164 -0
- package/src/git-activity.js +124 -0
- package/src/git-log-prefetch.js +53 -0
- package/src/graph-certs.js +58 -0
- package/src/graph-generator.js +230 -0
- package/src/graph-scan.js +239 -0
- package/src/i18n.js +3341 -0
- package/src/inter-fonts.js +6 -0
- package/src/interactive-select.js +183 -0
- package/src/interview-client.js +226 -0
- package/src/interview-transcript.js +56 -0
- package/src/inventory-client.js +90 -0
- package/src/invitations-client.js +66 -0
- package/src/invitations-flow.js +50 -0
- package/src/lang-client.js +147 -0
- package/src/lang-codes.js +14 -0
- package/src/lang-flow.js +85 -0
- package/src/legal-notice.js +53 -0
- package/src/locale.js +72 -0
- package/src/markdown-ansi.js +50 -0
- package/src/maturity.js +111 -0
- package/src/mcp-ai-usage-result-tool.js +111 -0
- package/src/mcp-ai-usage-tool.js +401 -0
- package/src/mcp-alma-tools.js +95 -0
- package/src/mcp-applications-tools.js +44 -0
- package/src/mcp-auth-form.js +480 -0
- package/src/mcp-availability-tools.js +45 -0
- package/src/mcp-certifications-tools.js +44 -0
- package/src/mcp-certify-dimension-tools.js +66 -0
- package/src/mcp-choice.js +95 -0
- package/src/mcp-detector.js +223 -0
- package/src/mcp-device-login-tools.js +177 -0
- package/src/mcp-find-projects-tools.js +69 -0
- package/src/mcp-install.js +275 -0
- package/src/mcp-instructions.js +27 -0
- package/src/mcp-invitations-tools.js +44 -0
- package/src/mcp-lang-tools.js +41 -0
- package/src/mcp-onboarding-tools.js +379 -0
- package/src/mcp-portfolios-tools.js +50 -0
- package/src/mcp-profile-tools.js +48 -0
- package/src/mcp-prompts.js +73 -0
- package/src/mcp-rate-tools.js +44 -0
- package/src/mcp-register-tools.js +441 -0
- package/src/mcp-report-tools.js +105 -0
- package/src/mcp-role-tools.js +372 -0
- package/src/mcp-saved-positions-tools.js +59 -0
- package/src/mcp-server.js +290 -0
- package/src/mcp-show-project-tools.js +52 -0
- package/src/mcp-signup-client.js +118 -0
- package/src/mcp-signup-tools.js +1171 -0
- package/src/mcp-socials-tools.js +41 -0
- package/src/memory-structure-detector.js +108 -0
- package/src/model-call-record.js +198 -0
- package/src/onboarding-availability.js +26 -0
- package/src/onboarding-client.js +374 -0
- package/src/onboarding-flow.js +1162 -0
- package/src/onboarding-funnel.js +272 -0
- package/src/onboarding-livekit-client.js +102 -0
- package/src/onboarding-status-client.js +57 -0
- package/src/open-file.js +32 -0
- package/src/osc-link.js +16 -0
- package/src/portfolio-client.js +164 -0
- package/src/portfolio-git-info.js +61 -0
- package/src/portfolios-client.js +85 -0
- package/src/portfolios-flow.js +79 -0
- package/src/profile-client.js +133 -0
- package/src/profile-flow.js +115 -0
- package/src/profile-switch.js +46 -0
- package/src/prompt-select.js +66 -0
- package/src/rate-client.js +81 -0
- package/src/rate-flow.js +137 -0
- package/src/register-context.js +145 -0
- package/src/render-ai-fluency.js +266 -0
- package/src/render-certification.js +177 -0
- package/src/render-html.js +140 -0
- package/src/render-report-text.js +27 -0
- package/src/render-sheet.js +261 -0
- package/src/render-terminal.js +109 -0
- package/src/render-usage-report-text.js +155 -0
- package/src/repo-context.js +215 -0
- package/src/report-gating.js +49 -0
- package/src/report-store.js +226 -0
- package/src/report-theme.js +177 -0
- package/src/report-view-client.js +50 -0
- package/src/reveal-output.js +29 -0
- package/src/roadmap-content.js +453 -0
- package/src/roadmap-personalization.js +132 -0
- package/src/roadmap-prompt.js +154 -0
- package/src/roles-client.js +112 -0
- package/src/roles-flow.js +192 -0
- package/src/sanitize-network-text.js +36 -0
- package/src/saved-positions-client.js +71 -0
- package/src/saved-positions-flow.js +50 -0
- package/src/scan-exclusions.js +50 -0
- package/src/scanner.js +624 -0
- package/src/scope-signals.js +148 -0
- package/src/secret-prompt.js +72 -0
- package/src/session-gate.js +30 -0
- package/src/session-normalizers.js +174 -0
- package/src/session-refresh.js +51 -0
- package/src/session-scan.js +475 -0
- package/src/session-signals.js +36 -0
- package/src/shakers-wordmark.js +17 -0
- package/src/share-card.js +362 -0
- package/src/share-certification.js +111 -0
- package/src/share-consent-state.js +157 -0
- package/src/share-payload.js +300 -0
- package/src/share.js +185 -0
- package/src/show-project-client.js +58 -0
- package/src/show-project-flow.js +119 -0
- package/src/signup-client.js +103 -0
- package/src/signup-copy.js +450 -0
- package/src/signup-language.js +21 -0
- package/src/skills-client.js +102 -0
- package/src/socials-client.js +91 -0
- package/src/socials-flow.js +83 -0
- package/src/specificity-extract.js +41 -0
- package/src/start-add-agents.js +306 -0
- package/src/start-add-portfolio.js +388 -0
- package/src/start-add-skills.js +260 -0
- package/src/stdin-ask.js +129 -0
- package/src/steering-extract.js +43 -0
- package/src/strip-internal-comments.js +45 -0
- package/src/tech-detector.js +295 -0
- package/src/templates/report-sheet.html +572 -0
- package/src/terminal-agents.js +207 -0
- package/src/terminal-format.js +85 -0
- package/src/terminal-progress.js +106 -0
- package/src/terminal-sections.js +219 -0
- package/src/tier-analysis.js +126 -0
- package/src/tier-engine.js +114 -0
- package/src/tier-projection.js +117 -0
- package/src/transcript-turns.js +49 -0
- package/src/translations-client.js +84 -0
- package/src/typewriter.js +154 -0
- package/src/update-flow.js +223 -0
- package/src/update-notifier.js +113 -0
- package/src/usage-model-calls.js +119 -0
- package/src/usage-oneshot-commands.js +180 -0
- package/src/usage-repo-scope.js +162 -0
- package/src/usage-run-steps.js +354 -0
- package/src/work-streams.js +79 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { getHomeDir } = require('./env-paths');
|
|
6
|
+
|
|
7
|
+
// Local Claude Code agent-usage signal (ADR-016, agent evaluation feature).
|
|
8
|
+
|
|
9
|
+
const MAX_FILES = 500; // cap total session files scanned (bounded work)
|
|
10
|
+
const MAX_BYTES_PER_FILE = 8 * 1024 * 1024; // read at most an 8MB head per file
|
|
11
|
+
const AGENT_TOOL_NAMES = new Set(['Agent', 'Task']);
|
|
12
|
+
|
|
13
|
+
function historyRoot(env = process.env) {
|
|
14
|
+
return path.join(getHomeDir(env), '.claude', 'projects');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function listSessionFiles(root) {
|
|
18
|
+
const out = [];
|
|
19
|
+
let dirs;
|
|
20
|
+
try {
|
|
21
|
+
dirs = fs.readdirSync(root);
|
|
22
|
+
} catch {
|
|
23
|
+
return out; // no history dir on this machine — graceful
|
|
24
|
+
}
|
|
25
|
+
for (const d of dirs) {
|
|
26
|
+
const dirPath = path.join(root, d);
|
|
27
|
+
let files;
|
|
28
|
+
try {
|
|
29
|
+
files = fs.readdirSync(dirPath);
|
|
30
|
+
} catch {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
for (const f of files) {
|
|
34
|
+
if (f.endsWith('.jsonl')) {
|
|
35
|
+
out.push(path.join(dirPath, f));
|
|
36
|
+
if (out.length >= MAX_FILES) return out;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Parse ONE jsonl line, tallying any Agent/Task tool_use subagent_type into `counts`.
|
|
44
|
+
function countInvocation(line, counts) {
|
|
45
|
+
if (!line || !line.includes('subagent_type')) return;
|
|
46
|
+
let o;
|
|
47
|
+
try {
|
|
48
|
+
o = JSON.parse(line);
|
|
49
|
+
} catch {
|
|
50
|
+
return; // truncated / malformed line — skip, never throw
|
|
51
|
+
}
|
|
52
|
+
const msg = o && o.message;
|
|
53
|
+
const content = msg && Array.isArray(msg.content) ? msg.content : null;
|
|
54
|
+
if (!content) return;
|
|
55
|
+
for (const c of content) {
|
|
56
|
+
if (c && c.type === 'tool_use' && AGENT_TOOL_NAMES.has(c.name)) {
|
|
57
|
+
const t = c.input && typeof c.input.subagent_type === 'string' ? c.input.subagent_type.trim() : '';
|
|
58
|
+
if (t) counts[t] = (counts[t] || 0) + 1;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Read a bounded head of a session file and tally invocations.
|
|
64
|
+
function scanFile(file, counts) {
|
|
65
|
+
let fd;
|
|
66
|
+
try {
|
|
67
|
+
fd = fs.openSync(file, 'r');
|
|
68
|
+
} catch {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const { size } = fs.fstatSync(fd);
|
|
73
|
+
const readLen = Math.min(size, MAX_BYTES_PER_FILE);
|
|
74
|
+
const buf = Buffer.allocUnsafe(readLen);
|
|
75
|
+
fs.readSync(fd, buf, 0, readLen, 0);
|
|
76
|
+
const lines = buf.toString('utf8').split('\n');
|
|
77
|
+
const complete = size > MAX_BYTES_PER_FILE ? lines.slice(0, -1) : lines;
|
|
78
|
+
for (const ln of complete) countInvocation(ln, counts);
|
|
79
|
+
} catch {
|
|
80
|
+
/* unreadable mid-stream — skip this file */
|
|
81
|
+
} finally {
|
|
82
|
+
try {
|
|
83
|
+
fs.closeSync(fd);
|
|
84
|
+
} catch {
|
|
85
|
+
/* already closed */
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Scan the whole local history.
|
|
91
|
+
function collectClaudeAgentUsage(env = process.env) {
|
|
92
|
+
const files = listSessionFiles(historyRoot(env));
|
|
93
|
+
if (!files.length) {
|
|
94
|
+
return { available: false, byAgent: {}, totalInvocations: 0, sessionsScanned: 0 };
|
|
95
|
+
}
|
|
96
|
+
const counts = {};
|
|
97
|
+
for (const f of files) scanFile(f, counts);
|
|
98
|
+
const totalInvocations = Object.values(counts).reduce((a, b) => a + b, 0);
|
|
99
|
+
return { available: true, byAgent: counts, totalInvocations, sessionsScanned: files.length };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Annotate the DETECTED local agents with their usage count (exact match of the agent `name` against the recorded `subagent_type`).
|
|
103
|
+
function collectAgentUsage(agents, env = process.env) {
|
|
104
|
+
const usage = collectClaudeAgentUsage(env);
|
|
105
|
+
const byAgent = {};
|
|
106
|
+
for (const a of agents || []) {
|
|
107
|
+
if (!a || !a.name) continue;
|
|
108
|
+
byAgent[a.name] = usage.available ? usage.byAgent[a.name] || 0 : null;
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
available: usage.available,
|
|
112
|
+
byAgent,
|
|
113
|
+
totalInvocations: usage.totalInvocations,
|
|
114
|
+
sessionsScanned: usage.sessionsScanned,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = { collectClaudeAgentUsage, collectAgentUsage, historyRoot };
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { postJsonWithTimeout } = require('./backend-request');
|
|
4
|
+
const { reasonForError, WORKS_CODE_TO_REASON } = require('./skills-client');
|
|
5
|
+
|
|
6
|
+
// Talent-agent client for `start`'s "Add agent" route (talents-ai-score Phase 2).
|
|
7
|
+
|
|
8
|
+
const DEFAULT_TIMEOUT_MS = 20000;
|
|
9
|
+
|
|
10
|
+
function parseJson(raw) {
|
|
11
|
+
try {
|
|
12
|
+
return raw ? JSON.parse(raw) : null;
|
|
13
|
+
} catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function authHeaders({ accessToken, hubAccessToken } = {}) {
|
|
19
|
+
const headers = {};
|
|
20
|
+
if (typeof accessToken === 'string' && accessToken) headers.Authorization = `Bearer ${accessToken}`;
|
|
21
|
+
if (typeof hubAccessToken === 'string' && hubAccessToken) headers['X-Hub-Token'] = hubAccessToken;
|
|
22
|
+
return headers;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// DIRECT-to-Hub headers (the relate route is not a certs relay): the Talent's own hub token IS the Authorization, same shape as portfolio-client's own `hubHeaders`.
|
|
26
|
+
function hubHeaders({ hubAccessToken } = {}) {
|
|
27
|
+
const headers = {};
|
|
28
|
+
if (typeof hubAccessToken === 'string' && hubAccessToken) headers.Authorization = `Bearer ${hubAccessToken}`;
|
|
29
|
+
return headers;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// `GET .../agents` -> { ok, agents:[{name}] } | { ok:false, reason }. Used for
|
|
33
|
+
// client-side dedup; a failed list is a named reason, never a silent skip.
|
|
34
|
+
async function requestListAgents({ accessToken, hubAccessToken } = {}, { endpoint, timeoutMs = DEFAULT_TIMEOUT_MS } = {}) {
|
|
35
|
+
if (!endpoint) return { ok: false, reason: 'no-endpoint' };
|
|
36
|
+
if (!hubAccessToken) return { ok: false, reason: 'no-hub-token' };
|
|
37
|
+
|
|
38
|
+
let res;
|
|
39
|
+
try {
|
|
40
|
+
res = await postJsonWithTimeout(endpoint, null, timeoutMs, 'GET', null, authHeaders({ accessToken, hubAccessToken }));
|
|
41
|
+
} catch (e) {
|
|
42
|
+
return { ok: false, reason: (e && e.kind) || 'network-error' };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (res.status < 200 || res.status >= 300) {
|
|
46
|
+
return { ok: false, reason: reasonForError(res.status, res.raw) };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const json = parseJson(res.raw);
|
|
50
|
+
const data = json && typeof json === 'object' && Array.isArray(json.data) ? json.data : null;
|
|
51
|
+
if (!data) return { ok: false, reason: 'bad-response' };
|
|
52
|
+
|
|
53
|
+
const agents = data
|
|
54
|
+
.map((entry) => ({
|
|
55
|
+
name: entry && typeof entry.name === 'string' ? entry.name : null,
|
|
56
|
+
id: entry && (typeof entry.id === 'string' || typeof entry.id === 'number') ? String(entry.id) : null,
|
|
57
|
+
}))
|
|
58
|
+
.filter((a) => a.name !== null);
|
|
59
|
+
return { ok: true, agents };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// `POST .../agents/declare`. `no-hub-token` fires locally before any request.
|
|
63
|
+
// Optional fields are OMITTED (never null/empty).
|
|
64
|
+
async function requestDeclareAgent(
|
|
65
|
+
{ name, catalogId, whatItDoes, humanDecides, accessToken, hubAccessToken } = {},
|
|
66
|
+
{ endpoint, timeoutMs = DEFAULT_TIMEOUT_MS } = {},
|
|
67
|
+
) {
|
|
68
|
+
if (!endpoint) return { ok: false, reason: 'no-endpoint' };
|
|
69
|
+
if (!hubAccessToken) return { ok: false, reason: 'no-hub-token' };
|
|
70
|
+
|
|
71
|
+
const body = { name };
|
|
72
|
+
// Hub's `catalogId` is an Int `num_id` (`@IsInt()` server-side).
|
|
73
|
+
if (typeof catalogId === 'number' && Number.isInteger(catalogId)) body.catalogId = catalogId;
|
|
74
|
+
if (typeof whatItDoes === 'string' && whatItDoes) body.whatItDoes = whatItDoes;
|
|
75
|
+
if (typeof humanDecides === 'string' && humanDecides) body.humanDecides = humanDecides;
|
|
76
|
+
|
|
77
|
+
let res;
|
|
78
|
+
try {
|
|
79
|
+
res = await postJsonWithTimeout(endpoint, body, timeoutMs, 'POST', null, authHeaders({ accessToken, hubAccessToken }));
|
|
80
|
+
} catch (e) {
|
|
81
|
+
return { ok: false, reason: (e && e.kind) || 'network-error' };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (res.status < 200 || res.status >= 300) {
|
|
85
|
+
if (res.status === 409) return { ok: false, reason: 'agent-exists' };
|
|
86
|
+
return { ok: false, reason: reasonForError(res.status, res.raw) };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const json = parseJson(res.raw);
|
|
90
|
+
if (!json || json.status !== 'OK') return { ok: false, reason: 'bad-response' };
|
|
91
|
+
// Thread the created agent's id back (relayed from Hub's declare response) so the relate step can PATCH `.../agents/:id/portfolios` (ADR-041).
|
|
92
|
+
const data = json.data && typeof json.data === 'object' ? json.data : null;
|
|
93
|
+
const rawId = data ? data.id : null;
|
|
94
|
+
const agentId = typeof rawId === 'string' || typeof rawId === 'number' ? String(rawId) : null;
|
|
95
|
+
return { ok: true, agentId };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// DIRECT to Hub (ADR-041), replace-set.
|
|
99
|
+
async function requestRelateAgentPortfolios(
|
|
100
|
+
{ agentId, items, hubAccessToken } = {},
|
|
101
|
+
{ endpoint, timeoutMs = DEFAULT_TIMEOUT_MS } = {},
|
|
102
|
+
) {
|
|
103
|
+
if (!endpoint) return { ok: false, reason: 'no-endpoint' };
|
|
104
|
+
if (!hubAccessToken) return { ok: false, reason: 'no-hub-token' };
|
|
105
|
+
if (!agentId) return { ok: false, reason: 'no-agent' };
|
|
106
|
+
|
|
107
|
+
const body = {
|
|
108
|
+
items: (Array.isArray(items) ? items : []).map((it) => ({ portfolioId: it && it.portfolioId })),
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
let res;
|
|
112
|
+
try {
|
|
113
|
+
res = await postJsonWithTimeout(endpoint, body, timeoutMs, 'PATCH', null, hubHeaders({ hubAccessToken }));
|
|
114
|
+
} catch (e) {
|
|
115
|
+
return { ok: false, reason: (e && e.kind) || 'network-error' };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (res.status < 200 || res.status >= 300) {
|
|
119
|
+
return { ok: false, reason: reasonForError(res.status, res.raw) };
|
|
120
|
+
}
|
|
121
|
+
const json = parseJson(res.raw);
|
|
122
|
+
if (!json || (json.success !== true && json.status !== 'OK')) return { ok: false, reason: 'bad-response' };
|
|
123
|
+
return { ok: true };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// `POST .../agents/draft-fields` -> { ok, whatItDoes, humanDecides } | { ok:false, reason }.
|
|
127
|
+
// No X-Hub-Token. The CALLER gates consent (ADR-052), so `consent:true` is always sent.
|
|
128
|
+
async function requestDraftAgentFields(
|
|
129
|
+
{ name, tools, model, parent, definition, accessToken } = {},
|
|
130
|
+
{ endpoint, timeoutMs = DEFAULT_TIMEOUT_MS } = {},
|
|
131
|
+
) {
|
|
132
|
+
if (!endpoint) return { ok: false, reason: 'no-endpoint' };
|
|
133
|
+
|
|
134
|
+
const body = { consent: true, name };
|
|
135
|
+
if (Array.isArray(tools) && tools.length > 0) body.tools = tools.filter((t) => typeof t === 'string' && t);
|
|
136
|
+
if (typeof model === 'string' && model) body.model = model;
|
|
137
|
+
if (typeof parent === 'string' && parent) body.parent = parent;
|
|
138
|
+
if (typeof definition === 'string' && definition) body.definition = definition;
|
|
139
|
+
|
|
140
|
+
let res;
|
|
141
|
+
try {
|
|
142
|
+
res = await postJsonWithTimeout(endpoint, body, timeoutMs, 'POST', null, authHeaders({ accessToken }));
|
|
143
|
+
} catch (e) {
|
|
144
|
+
return { ok: false, reason: (e && e.kind) || 'network-error' };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (res.status < 200 || res.status >= 300) {
|
|
148
|
+
return { ok: false, reason: reasonForError(res.status, res.raw) };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const json = parseJson(res.raw);
|
|
152
|
+
const data = json && json.status === 'OK' && json.data && typeof json.data === 'object' ? json.data : null;
|
|
153
|
+
const whatItDoes = data && typeof data.whatItDoes === 'string' ? data.whatItDoes : null;
|
|
154
|
+
const humanDecides = data && typeof data.humanDecides === 'string' ? data.humanDecides : null;
|
|
155
|
+
if (!whatItDoes || !humanDecides) return { ok: false, reason: 'bad-response' };
|
|
156
|
+
return { ok: true, whatItDoes, humanDecides };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
module.exports = {
|
|
160
|
+
requestListAgents,
|
|
161
|
+
requestDeclareAgent,
|
|
162
|
+
requestRelateAgentPortfolios,
|
|
163
|
+
requestDraftAgentFields,
|
|
164
|
+
WORKS_CODE_TO_REASON,
|
|
165
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Setup(tier) x Usage(interaction) -> 3x3 cell; mapping mirrors certs/hub (keep in
|
|
4
|
+
// sync). Usage level is judged server-side; this only maps already-judged bands.
|
|
5
|
+
|
|
6
|
+
const SETUP_LEVELS = ['ASSISTED', 'EXTENDED', 'ORCHESTRATED'];
|
|
7
|
+
const USAGE_LEVELS = ['REACTIVE', 'DELIBERATE', 'RIGOROUS'];
|
|
8
|
+
|
|
9
|
+
const SETUP_CODE = { ASSISTED: 'S1', EXTENDED: 'S2', ORCHESTRATED: 'S3' };
|
|
10
|
+
|
|
11
|
+
// hub CELL_NAMES, transposed to [setup][usage].
|
|
12
|
+
const CELL_NAMES = {
|
|
13
|
+
ASSISTED: { REACTIVE: 'Explorer', DELIBERATE: 'Practitioner', RIGOROUS: 'Craftsman' },
|
|
14
|
+
EXTENDED: { REACTIVE: 'Tooled-up', DELIBERATE: 'Builder', RIGOROUS: 'Architect' },
|
|
15
|
+
ORCHESTRATED: { REACTIVE: 'Automated', DELIBERATE: 'Scaler', RIGOROUS: 'AI Native' },
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function deriveSetupLevelFromTier(tier) {
|
|
19
|
+
if (tier === null || tier === undefined || tier <= 0) return null;
|
|
20
|
+
if (tier <= 2) return 'ASSISTED';
|
|
21
|
+
if (tier <= 4) return 'EXTENDED';
|
|
22
|
+
return 'ORCHESTRATED';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function deriveUsageLevelFromInteraction(interactionLevel) {
|
|
26
|
+
switch (interactionLevel) {
|
|
27
|
+
case 'reactive':
|
|
28
|
+
return 'REACTIVE';
|
|
29
|
+
case 'directive':
|
|
30
|
+
return 'DELIBERATE';
|
|
31
|
+
case 'orchestrative':
|
|
32
|
+
return 'RIGOROUS';
|
|
33
|
+
default:
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function deriveAiFluencyCell(setupLevel, usageLevel) {
|
|
39
|
+
if (!setupLevel || !usageLevel) return null;
|
|
40
|
+
const row = CELL_NAMES[setupLevel];
|
|
41
|
+
return (row && row[usageLevel]) || null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function isAiNative(setupLevel, usageLevel) {
|
|
45
|
+
return setupLevel === 'ORCHESTRATED' && usageLevel === 'RIGOROUS';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// tier (0-7) + interactionLevel ('reactive'|'directive'|'orchestrative') -> matrix.
|
|
49
|
+
// Null bands when an input is missing (setup-only / usage-pending).
|
|
50
|
+
function composeAiFluencyMatrix({ tier = null, interactionLevel = null } = {}) {
|
|
51
|
+
const setupLevel = deriveSetupLevelFromTier(tier);
|
|
52
|
+
const usageLevel = deriveUsageLevelFromInteraction(interactionLevel);
|
|
53
|
+
return {
|
|
54
|
+
setupLevel,
|
|
55
|
+
setupCode: setupLevel ? SETUP_CODE[setupLevel] : null,
|
|
56
|
+
usageLevel,
|
|
57
|
+
cell: deriveAiFluencyCell(setupLevel, usageLevel),
|
|
58
|
+
isAiNative: isAiNative(setupLevel, usageLevel),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = {
|
|
63
|
+
SETUP_LEVELS,
|
|
64
|
+
USAGE_LEVELS,
|
|
65
|
+
SETUP_CODE,
|
|
66
|
+
CELL_NAMES,
|
|
67
|
+
deriveSetupLevelFromTier,
|
|
68
|
+
deriveUsageLevelFromInteraction,
|
|
69
|
+
deriveAiFluencyCell,
|
|
70
|
+
isAiNative,
|
|
71
|
+
composeAiFluencyMatrix,
|
|
72
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// The same read model the "My work with AI" front
|
|
4
|
+
// paints: { setup, usage, cell, isAiNative, traction, vision, howIWork }.
|
|
5
|
+
|
|
6
|
+
const { postJsonWithTimeout } = require('./backend-request');
|
|
7
|
+
|
|
8
|
+
const DEFAULT_TIMEOUT_MS = 20000;
|
|
9
|
+
|
|
10
|
+
function parseJson(raw) {
|
|
11
|
+
try {
|
|
12
|
+
return raw ? JSON.parse(raw) : null;
|
|
13
|
+
} catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function authHeaders(token) {
|
|
19
|
+
const headers = {};
|
|
20
|
+
if (typeof token === 'string' && token) headers.Authorization = `Bearer ${token}`;
|
|
21
|
+
return headers;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function requestAiProfile({ accessToken, hubAccessToken } = {}, { endpoint, timeoutMs = DEFAULT_TIMEOUT_MS } = {}) {
|
|
25
|
+
if (!endpoint) return { ok: false, reason: 'no-endpoint' };
|
|
26
|
+
const token = hubAccessToken || accessToken;
|
|
27
|
+
|
|
28
|
+
let res;
|
|
29
|
+
try {
|
|
30
|
+
res = await postJsonWithTimeout(endpoint, null, timeoutMs, 'GET', null, authHeaders(token));
|
|
31
|
+
} catch (e) {
|
|
32
|
+
return { ok: false, reason: (e && e.kind) || 'network-error' };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 404 = no profile row yet (projection hasn't landed) — a retriable "not ready".
|
|
36
|
+
if (res.status === 404) return { ok: false, reason: 'not-found' };
|
|
37
|
+
if (res.status < 200 || res.status >= 300) return { ok: false, reason: `http-${res.status}` };
|
|
38
|
+
|
|
39
|
+
const json = parseJson(res.raw);
|
|
40
|
+
const data = json && typeof json === 'object' ? json.data : null;
|
|
41
|
+
if (!data || typeof data !== 'object') return { ok: false, reason: 'bad-response' };
|
|
42
|
+
return { ok: true, profile: data };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = { requestAiProfile };
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Polls the hub ai-profile (downstream of the report ingest, lands ~3-8s later) and normalizes it for the report/MCP.
|
|
4
|
+
|
|
5
|
+
const { SETUP_CODE } = require('./ai-fluency-cell');
|
|
6
|
+
|
|
7
|
+
const DEFAULT_TIMEOUT_MS = 90000;
|
|
8
|
+
const POLL_INTERVAL_MS = 4000;
|
|
9
|
+
const PROFILE_POLL_WINDOW_MS = DEFAULT_TIMEOUT_MS;
|
|
10
|
+
const PROFILE_POLL_INTERVAL_MS = POLL_INTERVAL_MS;
|
|
11
|
+
const MAX_TOKEN_REFRESHES = 2;
|
|
12
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
13
|
+
|
|
14
|
+
const strOrNull = (v) => (typeof v === 'string' && v.trim() ? v.trim() : null);
|
|
15
|
+
const numOrNull = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : null);
|
|
16
|
+
|
|
17
|
+
// Tolerant normalization of the hub TalentAiProfileDto into the render shape.
|
|
18
|
+
function normalizeProfile(profile) {
|
|
19
|
+
const p = profile && typeof profile === 'object' ? profile : {};
|
|
20
|
+
const setup = p.setup && typeof p.setup === 'object' ? p.setup : {};
|
|
21
|
+
const usage = p.usage && typeof p.usage === 'object' ? p.usage : {};
|
|
22
|
+
const setupLevel = strOrNull(setup.level);
|
|
23
|
+
const usageLevel = strOrNull(usage.level);
|
|
24
|
+
const t = p.traction && typeof p.traction === 'object' ? p.traction : null;
|
|
25
|
+
const tools = t && Array.isArray(t.tools)
|
|
26
|
+
? t.tools.map((x) => ({
|
|
27
|
+
id: strOrNull(x && (x.id ?? x.name)),
|
|
28
|
+
name: strOrNull(x && (x.name ?? x.id)),
|
|
29
|
+
detected: x ? x.detected === true : false,
|
|
30
|
+
})).filter((x) => x.id || x.name)
|
|
31
|
+
: null;
|
|
32
|
+
const vision = p.vision && typeof p.vision === 'object' ? p.vision : null;
|
|
33
|
+
const howIWork = p.howIWork && typeof p.howIWork === 'object' ? p.howIWork : null;
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
matrix: {
|
|
37
|
+
setupLevel,
|
|
38
|
+
setupCode: setupLevel ? (SETUP_CODE[setupLevel] || null) : null,
|
|
39
|
+
usageLevel,
|
|
40
|
+
cell: strOrNull(p.cell),
|
|
41
|
+
isAiNative: p.isAiNative === true,
|
|
42
|
+
},
|
|
43
|
+
setup: { tier: strOrNull(setup.tier), level: setupLevel, explanation: strOrNull(setup.explanation) },
|
|
44
|
+
usage: { level: usageLevel, explanation: strOrNull(usage.explanation) },
|
|
45
|
+
traction: t
|
|
46
|
+
? {
|
|
47
|
+
sessions90d: numOrNull(t.sessions90d),
|
|
48
|
+
activeDaysPerWeek: numOrNull(t.activeDaysPerWeek),
|
|
49
|
+
agentsDetected: numOrNull(t.agentsDetected),
|
|
50
|
+
agentsOnProfile: numOrNull(t.agentsOnProfile),
|
|
51
|
+
tools,
|
|
52
|
+
}
|
|
53
|
+
: null,
|
|
54
|
+
vision: vision ? strOrNull(vision.text) : null,
|
|
55
|
+
howIWork: howIWork ? strOrNull(howIWork.body) : null,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// The matrix band is populated once the CLI footprint tier has been projected.
|
|
60
|
+
function matrixPresent(preview) {
|
|
61
|
+
const m = preview && preview.matrix;
|
|
62
|
+
return !!(m && (m.setupLevel || m.usageLevel || m.cell));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Anything renderable: the matrix band OR the vision (E) OR how-I-work (F). The
|
|
66
|
+
// three land on independent projections, so the render must not gate on the matrix.
|
|
67
|
+
function hasRenderableProfile(preview) {
|
|
68
|
+
return !!preview && (matrixPresent(preview) || !!preview.vision || !!preview.howIWork);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// The full profile: matrix band AND both narratives. Once complete, stop polling.
|
|
72
|
+
function isProfileComplete(preview) {
|
|
73
|
+
return matrixPresent(preview) && !!preview.vision && !!preview.howIWork;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// How many of the three parts (matrix, E, F) landed — to keep the most complete partial.
|
|
77
|
+
function profileCompleteness(preview) {
|
|
78
|
+
if (!preview) return -1;
|
|
79
|
+
return (matrixPresent(preview) ? 1 : 0) + (preview.vision ? 1 : 0) + (preview.howIWork ? 1 : 0);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Back-compat alias (matrix-band meaning preserved for external callers).
|
|
83
|
+
const isProfileReady = matrixPresent;
|
|
84
|
+
|
|
85
|
+
// deps (injectable for tests): requestAiProfile, now, wait, pollIntervalMs.
|
|
86
|
+
async function resolveAiProfilePreview(args = {}, deps = {}) {
|
|
87
|
+
const { session = null, endpoint = null, timeoutMs = DEFAULT_TIMEOUT_MS } = args;
|
|
88
|
+
const {
|
|
89
|
+
requestAiProfile = require('./ai-profile-client').requestAiProfile,
|
|
90
|
+
refreshToken = null,
|
|
91
|
+
now = () => Date.now(),
|
|
92
|
+
wait = sleep,
|
|
93
|
+
pollIntervalMs = POLL_INTERVAL_MS,
|
|
94
|
+
} = deps;
|
|
95
|
+
|
|
96
|
+
let accessToken = session && typeof session.accessToken === 'string' ? session.accessToken : null;
|
|
97
|
+
let hubAccessToken = session && typeof session.hubAccessToken === 'string' ? session.hubAccessToken : null;
|
|
98
|
+
if ((!accessToken && !hubAccessToken) || !endpoint) return { status: 'unavailable', preview: null };
|
|
99
|
+
|
|
100
|
+
const deadline = now() + Math.max(0, timeoutMs);
|
|
101
|
+
// The matrix band, the vision (E) and howIWork (F) land on INDEPENDENT projections and
|
|
102
|
+
// in any order (the matrix can even lag to the hourly reconcile cron). Keep the most
|
|
103
|
+
// complete partial seen and render it — never gate the whole preview on the matrix band.
|
|
104
|
+
let best = null;
|
|
105
|
+
let refreshes = 0;
|
|
106
|
+
|
|
107
|
+
while (true) {
|
|
108
|
+
const remaining = deadline - now();
|
|
109
|
+
if (remaining <= 0) break;
|
|
110
|
+
|
|
111
|
+
let res;
|
|
112
|
+
try {
|
|
113
|
+
res = await requestAiProfile({ accessToken, hubAccessToken }, { endpoint, timeoutMs: remaining });
|
|
114
|
+
} catch {
|
|
115
|
+
res = { ok: false, reason: 'network-error' };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (res && res.ok) {
|
|
119
|
+
const preview = normalizeProfile(res.profile);
|
|
120
|
+
if (isProfileComplete(preview)) return { status: 'ready', preview };
|
|
121
|
+
if (profileCompleteness(preview) > profileCompleteness(best)) best = preview;
|
|
122
|
+
} else if (res && res.reason === 'http-401' && typeof refreshToken === 'function' && refreshes < MAX_TOKEN_REFRESHES) {
|
|
123
|
+
refreshes += 1;
|
|
124
|
+
let fresh = null;
|
|
125
|
+
try { fresh = await refreshToken(); } catch { fresh = null; }
|
|
126
|
+
if (fresh && (fresh.accessToken || fresh.hubAccessToken)) {
|
|
127
|
+
accessToken = typeof fresh.accessToken === 'string' ? fresh.accessToken : accessToken;
|
|
128
|
+
hubAccessToken = typeof fresh.hubAccessToken === 'string' ? fresh.hubAccessToken : hubAccessToken;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (deadline - now() <= pollIntervalMs) break;
|
|
134
|
+
await wait(pollIntervalMs);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Anything renderable (matrix OR E OR F) is surfaced as `ready`; the renderers degrade
|
|
138
|
+
// per-field. Only a genuinely empty profile stays `pending`.
|
|
139
|
+
if (hasRenderableProfile(best)) return { status: 'ready', preview: best };
|
|
140
|
+
return { status: 'pending', preview: null };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
module.exports = {
|
|
144
|
+
resolveAiProfilePreview,
|
|
145
|
+
normalizeProfile,
|
|
146
|
+
isProfileReady,
|
|
147
|
+
matrixPresent,
|
|
148
|
+
hasRenderableProfile,
|
|
149
|
+
isProfileComplete,
|
|
150
|
+
profileCompleteness,
|
|
151
|
+
DEFAULT_TIMEOUT_MS,
|
|
152
|
+
PROFILE_POLL_WINDOW_MS,
|
|
153
|
+
PROFILE_POLL_INTERVAL_MS,
|
|
154
|
+
};
|
package/src/ai.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// The single named surface for every model-backed (LLM) call this CLI makes (talents-ai-score, arch-cleanup #4).
|
|
4
|
+
|
|
5
|
+
const agentSynthesis = require('./agent-synthesis');
|
|
6
|
+
const agentEvaluation = require('./agent-evaluation');
|
|
7
|
+
const roadmapPersonalization = require('./roadmap-personalization');
|
|
8
|
+
const onboardingClient = require('./onboarding-client');
|
|
9
|
+
const config = require('./config');
|
|
10
|
+
|
|
11
|
+
// The model calls, one entry each, paired with the request-builder, endpoint-getter and promptVersion pointer that define them. The skill-code certify/interview trio was removed when `certify` became a dimension LiveKit interview (its turns run over the onboarding HTTP/LiveKit surface, not a dedicated model call here).
|
|
12
|
+
const AI_CALLS = Object.freeze({
|
|
13
|
+
agentSynthesis: {
|
|
14
|
+
call: agentSynthesis.requestAgentSynthesis,
|
|
15
|
+
build: agentSynthesis.buildSynthesisRequest,
|
|
16
|
+
endpoint: config.getSynthesisEndpoint,
|
|
17
|
+
promptVersion: null,
|
|
18
|
+
},
|
|
19
|
+
agentEvaluation: {
|
|
20
|
+
call: agentEvaluation.requestAgentEvaluation,
|
|
21
|
+
build: agentEvaluation.buildAgentEvaluationRequest,
|
|
22
|
+
endpoint: config.getAgentEvaluationEndpoint,
|
|
23
|
+
promptVersion: agentEvaluation.PROMPT_VERSION,
|
|
24
|
+
},
|
|
25
|
+
roadmapPersonalization: {
|
|
26
|
+
call: roadmapPersonalization.requestRoadmapPersonalization,
|
|
27
|
+
build: roadmapPersonalization.buildRoadmapPersonalizationRequest,
|
|
28
|
+
endpoint: config.getRoadmapEndpoint,
|
|
29
|
+
promptVersion: null,
|
|
30
|
+
},
|
|
31
|
+
onboardingOpen: {
|
|
32
|
+
call: onboardingClient.requestStartTextSession,
|
|
33
|
+
build: null,
|
|
34
|
+
endpoint: config.getOnboardingInterviewsEndpoint,
|
|
35
|
+
promptVersion: null,
|
|
36
|
+
},
|
|
37
|
+
onboardingTurn: {
|
|
38
|
+
call: onboardingClient.requestOnboardingTurn,
|
|
39
|
+
build: null,
|
|
40
|
+
endpoint: config.getOnboardingInterviewsEndpoint,
|
|
41
|
+
promptVersion: null,
|
|
42
|
+
},
|
|
43
|
+
onboardingComplete: {
|
|
44
|
+
call: onboardingClient.requestCompleteTextSession,
|
|
45
|
+
build: null,
|
|
46
|
+
endpoint: config.getOnboardingInterviewsEndpoint,
|
|
47
|
+
promptVersion: null,
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
module.exports = {
|
|
52
|
+
AI_CALLS,
|
|
53
|
+
|
|
54
|
+
requestAgentSynthesis: agentSynthesis.requestAgentSynthesis,
|
|
55
|
+
buildSynthesisRequest: agentSynthesis.buildSynthesisRequest,
|
|
56
|
+
|
|
57
|
+
requestAgentEvaluation: agentEvaluation.requestAgentEvaluation,
|
|
58
|
+
buildAgentEvaluationRequest: agentEvaluation.buildAgentEvaluationRequest,
|
|
59
|
+
|
|
60
|
+
requestRoadmapPersonalization: roadmapPersonalization.requestRoadmapPersonalization,
|
|
61
|
+
buildRoadmapPersonalizationRequest: roadmapPersonalization.buildRoadmapPersonalizationRequest,
|
|
62
|
+
|
|
63
|
+
requestStartTextSession: onboardingClient.requestStartTextSession,
|
|
64
|
+
requestOnboardingTurn: onboardingClient.requestOnboardingTurn,
|
|
65
|
+
requestCompleteTextSession: onboardingClient.requestCompleteTextSession,
|
|
66
|
+
};
|