@kivimedia/kmhub 2.9.0 → 2.10.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 +170 -170
- package/bin/kmhub.mjs +896 -896
- package/coach-book-output-guard.mjs +760 -760
- package/index.mjs +57 -57
- package/package.json +56 -56
- package/prompts/briefing.md +29 -29
- package/prompts/luxury.md +70 -70
- package/prompts/play.md +49 -49
- package/prompts/run.md +36 -36
- package/prompts/setup.md +33 -33
- package/prompts/vs-booked.md +46 -46
- package/prompts/what-can-you-do.md +40 -40
- package/prompts.mjs +110 -109
- package/read-only-tools.json +143 -142
- package/remote.mjs +929 -929
- package/tools/balloon-costing.mjs +80 -80
- package/tools/booking-equipment.mjs +110 -110
- package/tools/bridges.mjs +54 -54
- package/tools/briefing.mjs +91 -91
- package/tools/calendar.mjs +170 -170
- package/tools/capabilities.mjs +155 -155
- package/tools/catalog.mjs +288 -288
- package/tools/clubs.mjs +176 -176
- package/tools/coach.mjs +771 -771
- package/tools/compare.mjs +76 -76
- package/tools/core.mjs +244 -244
- package/tools/crm.mjs +209 -209
- package/tools/dubsado.mjs +137 -137
- package/tools/exports.mjs +128 -128
- package/tools/fact-review.mjs +125 -125
- package/tools/flows.mjs +261 -261
- package/tools/forms.mjs +158 -158
- package/tools/gols.mjs +134 -134
- package/tools/hr.mjs +162 -162
- package/tools/knowledge.mjs +125 -125
- package/tools/marketing.mjs +396 -396
- package/tools/meta.mjs +245 -245
- package/tools/military.mjs +244 -244
- package/tools/money.mjs +235 -197
- package/tools/outreach.mjs +238 -238
- package/tools/pending.mjs +122 -122
- package/tools/photos.mjs +140 -140
- package/tools/plays.mjs +244 -244
- package/tools/profile.mjs +118 -118
- package/tools/radar.mjs +173 -173
- package/tools/recurring-invoices.mjs +149 -149
- package/tools/reengage.mjs +434 -434
- package/tools/schedules.mjs +55 -55
- package/tools/setup.mjs +168 -168
- package/tools/sops-bridges.mjs +86 -86
- package/tools/sops.mjs +314 -314
- package/tools/sourcing.mjs +268 -268
- package/tools/strategy.mjs +146 -146
- package/tools/studio.mjs +132 -132
- package/tools/venueradar.mjs +151 -151
- package/tools/voice.mjs +134 -134
- package/tools.mjs +407 -407
package/tools/coach.mjs
CHANGED
|
@@ -1,771 +1,771 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tool family: Fully Booked Coach operations.
|
|
3
|
-
*
|
|
4
|
-
* The coach profile contains only coach relevant tools. Draft and preview tools
|
|
5
|
-
* never send or publish. Paid scouting keeps the existing price then confirm gate.
|
|
6
|
-
*/
|
|
7
|
-
import { projectCoachBookConnectorResult } from '../coach-book-output-guard.mjs';
|
|
8
|
-
import { z } from 'zod';
|
|
9
|
-
|
|
10
|
-
export const FAMILY = 'coach';
|
|
11
|
-
|
|
12
|
-
export const TOOLS = [
|
|
13
|
-
'km_coach_context',
|
|
14
|
-
'km_coach_method',
|
|
15
|
-
'km_coach_ask_book',
|
|
16
|
-
'km_coach_book_catalog',
|
|
17
|
-
'km_coach_book_recommend',
|
|
18
|
-
'km_coach_book_tool',
|
|
19
|
-
'km_coach_niche_fit',
|
|
20
|
-
'km_coach_daily_operator',
|
|
21
|
-
'km_coach_list_clients',
|
|
22
|
-
'km_coach_create_client',
|
|
23
|
-
|
|
24
|
-
'km_coach_list_records',
|
|
25
|
-
'km_coach_create_client_map',
|
|
26
|
-
'km_coach_revise_record',
|
|
27
|
-
|
|
28
|
-
'km_coach_write_record',
|
|
29
|
-
|
|
30
|
-
'km_coach_stalled_opportunities',
|
|
31
|
-
'km_coach_preview_action',
|
|
32
|
-
'km_coach_decide_action',
|
|
33
|
-
|
|
34
|
-
'km_coach_submit_daily_cookie',
|
|
35
|
-
'km_coach_run_scout',
|
|
36
|
-
'km_coach_list_scouts',
|
|
37
|
-
'km_coach_scout_status',
|
|
38
|
-
'km_coach_stop_scout',
|
|
39
|
-
'km_coach_record_scout_finding',
|
|
40
|
-
'km_coach_promote_scout_finding',
|
|
41
|
-
];
|
|
42
|
-
|
|
43
|
-
export const PROFILES = ['coach'];
|
|
44
|
-
|
|
45
|
-
const COACH_METHOD_WORKFLOWS = [
|
|
46
|
-
'method_boundary',
|
|
47
|
-
'niche',
|
|
48
|
-
'capacity',
|
|
49
|
-
'acquisition',
|
|
50
|
-
'authority_content',
|
|
51
|
-
'strategy_session',
|
|
52
|
-
'client_map',
|
|
53
|
-
'renewal_referral_proof',
|
|
54
|
-
'scouting',
|
|
55
|
-
];
|
|
56
|
-
const COACH_OBJECT_TYPES = [
|
|
57
|
-
'CoachOperatingBrief', 'MethodologyActivation', 'CapacityPolicy', 'FullyBookedThreshold',
|
|
58
|
-
'NicheHypothesis', 'NicheScorecard', 'Offer', 'Program', 'Cohort', 'Channel', 'Integration',
|
|
59
|
-
'Relationship', 'ProspectProfile', 'Opportunity', 'PipelineStage', 'ScoutRun', 'ScoutFinding',
|
|
60
|
-
'Collaboration', 'ReferralPartner', 'AudienceSegment', 'GrowthSprint', 'Campaign', 'LeadMagnet',
|
|
61
|
-
'Tripwire', 'Engagement', 'ClientMAP', 'Session', 'SessionPreparation', 'Commitment', 'Outcome',
|
|
62
|
-
'ResultMeasure', 'ClientHealth', 'Renewal', 'ReferralRequest', 'Testimonial', 'ProofPermission',
|
|
63
|
-
'Handover', 'ContentIdea', 'ContentAsset', 'ContentCategory', 'ContentBrief',
|
|
64
|
-
'DailyCookieRequest', 'EditorialApproval', 'PublicationRequest', 'PerformanceObservation',
|
|
65
|
-
'ConfidentialNote', 'ConsentGrant', 'SecureReference', 'ActionRequest', 'Approval',
|
|
66
|
-
'ManagedTask', 'AuditEvent', 'ExportJob', 'SyncCursor',
|
|
67
|
-
];
|
|
68
|
-
const COACH_WRITABLE_OBJECT_TYPES = [
|
|
69
|
-
'CoachOperatingBrief', 'CapacityPolicy', 'FullyBookedThreshold',
|
|
70
|
-
'NicheHypothesis', 'NicheScorecard', 'Offer', 'Program', 'Cohort', 'Channel', 'Integration',
|
|
71
|
-
'Relationship', 'ProspectProfile', 'Opportunity', 'PipelineStage', 'ScoutFinding',
|
|
72
|
-
'Collaboration', 'ReferralPartner', 'AudienceSegment', 'GrowthSprint', 'Campaign', 'LeadMagnet',
|
|
73
|
-
'Tripwire', 'Engagement', 'Session', 'SessionPreparation', 'Commitment', 'Outcome',
|
|
74
|
-
'ResultMeasure', 'ClientHealth', 'Renewal', 'ReferralRequest', 'Testimonial', 'Handover',
|
|
75
|
-
'ContentIdea', 'ContentAsset', 'ContentCategory', 'ContentBrief', 'PerformanceObservation',
|
|
76
|
-
'ConfidentialNote', 'ManagedTask',
|
|
77
|
-
];
|
|
78
|
-
|
|
79
|
-
const SCOUT_MODES = [
|
|
80
|
-
'micro-niche-mapper',
|
|
81
|
-
'niche-validation-scout',
|
|
82
|
-
'collaborator-scout',
|
|
83
|
-
'ideal-client-twin-scout',
|
|
84
|
-
'past-client-opportunity-scout',
|
|
85
|
-
'referral-path-scout',
|
|
86
|
-
'ideal-client-scout',
|
|
87
|
-
'niche-and-growth-scout',
|
|
88
|
-
'ideal-organization-and-partner-scout',
|
|
89
|
-
'ideal-person-and-influencer-scout',
|
|
90
|
-
'podcast-collaboration-scout',
|
|
91
|
-
];
|
|
92
|
-
|
|
93
|
-
const PUBLIC_SCOUT_MODES = new Set([
|
|
94
|
-
'micro-niche-mapper',
|
|
95
|
-
'niche-validation-scout',
|
|
96
|
-
'collaborator-scout',
|
|
97
|
-
'ideal-client-twin-scout',
|
|
98
|
-
'ideal-client-scout',
|
|
99
|
-
'niche-and-growth-scout',
|
|
100
|
-
'ideal-organization-and-partner-scout',
|
|
101
|
-
'ideal-person-and-influencer-scout',
|
|
102
|
-
'podcast-collaboration-scout',
|
|
103
|
-
]);
|
|
104
|
-
|
|
105
|
-
const MODE_DIRECTION = {
|
|
106
|
-
'micro-niche-mapper': 'Map a broad market into specific groups that share context and an urgent problem.',
|
|
107
|
-
'niche-validation-scout': 'Find public evidence that the niche problem is urgent, reachable, and valuable.',
|
|
108
|
-
'collaborator-scout': 'Find people or organizations with a complementary offer and an aligned audience.',
|
|
109
|
-
'ideal-client-twin-scout': 'Find public prospects that match approved nonconfidential attributes of a strong client.',
|
|
110
|
-
'ideal-client-scout': 'Find public prospects that match the coach niche and offer.',
|
|
111
|
-
'niche-and-growth-scout': 'Find niche groups and practical audience growth paths.',
|
|
112
|
-
'ideal-organization-and-partner-scout': 'Find organizations and partners with a strong group and problem match.',
|
|
113
|
-
'ideal-person-and-influencer-scout': 'Find people and influencers who reach the coach niche.',
|
|
114
|
-
'podcast-collaboration-scout': 'Find podcasts where a useful collaboration fits the audience problem.',
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
const NICHE_EVIDENCE_REFERENCE = z.object({
|
|
118
|
-
id: z.string().min(1).max(80),
|
|
119
|
-
url: z.string().url().refine((value) => value.startsWith('https://'), 'Use one public HTTPS evidence URL.'),
|
|
120
|
-
observedAt: z.string().min(10).max(64),
|
|
121
|
-
summary: z.string().min(1).max(1000),
|
|
122
|
-
kind: z.enum(['direct', 'analogue', 'counterevidence']),
|
|
123
|
-
}).strict();
|
|
124
|
-
|
|
125
|
-
const NICHE_PREDICATE = z.object({
|
|
126
|
-
grade: z.union([z.literal(0), z.literal(0.5), z.literal(1)]),
|
|
127
|
-
evidenceIds: z.array(z.string().min(1).max(80)).max(40),
|
|
128
|
-
rationale: z.string().min(1).max(1000),
|
|
129
|
-
}).strict();
|
|
130
|
-
|
|
131
|
-
const NICHE_COUNTEREVIDENCE = z.object({
|
|
132
|
-
query: z.string().min(1).max(500),
|
|
133
|
-
checkedAt: z.string().min(10).max(64),
|
|
134
|
-
outcome: z.enum(['none_found', 'found', 'inconclusive']),
|
|
135
|
-
evidenceIds: z.array(z.string().min(1).max(80)).max(40),
|
|
136
|
-
}).strict();
|
|
137
|
-
|
|
138
|
-
const NICHE_FALSE_POSITIVE = z.object({
|
|
139
|
-
observed: z.boolean(),
|
|
140
|
-
evidenceIds: z.array(z.string().min(1).max(80)).max(40),
|
|
141
|
-
note: z.string().max(1000),
|
|
142
|
-
}).strict();
|
|
143
|
-
|
|
144
|
-
function nicheDimension(predicates) {
|
|
145
|
-
return z.object({
|
|
146
|
-
predicates: z.object(Object.fromEntries(
|
|
147
|
-
predicates.map((predicate) => [predicate, NICHE_PREDICATE]),
|
|
148
|
-
)).strict(),
|
|
149
|
-
counterevidenceChecks: z.array(NICHE_COUNTEREVIDENCE).min(1).max(20),
|
|
150
|
-
falsePositive: NICHE_FALSE_POSITIVE,
|
|
151
|
-
}).strict();
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const NICHE_FIT_INPUT = z.object({
|
|
155
|
-
toolSpecId: z.literal('fbc.niche-fit.v1'),
|
|
156
|
-
asOf: z.string().min(10).max(64),
|
|
157
|
-
hypothesis: z.object({
|
|
158
|
-
proposedPodcastNiche: z.string().min(1).max(500),
|
|
159
|
-
strategy: z.enum(['smaller_group', 'specific_problem', 'both']),
|
|
160
|
-
specificGroup: z.string().min(1).max(500).optional(),
|
|
161
|
-
specificProblem: z.string().min(1).max(500).optional(),
|
|
162
|
-
collaboratorProfile: z.string().min(1).max(1000),
|
|
163
|
-
podcastWorkingTitle: z.string().min(1).max(500),
|
|
164
|
-
collaborationHook: z.string().min(1).max(1000),
|
|
165
|
-
valueExchangeOffer: z.string().min(1).max(1000),
|
|
166
|
-
geography: z.string().min(1).max(200).optional(),
|
|
167
|
-
language: z.string().min(1).max(100).optional(),
|
|
168
|
-
}).strict(),
|
|
169
|
-
evidence: z.array(NICHE_EVIDENCE_REFERENCE).min(1).max(120),
|
|
170
|
-
dimensions: z.object({
|
|
171
|
-
findability: nicheDimension([
|
|
172
|
-
'discoverable_collaborators',
|
|
173
|
-
'reachable_contact_path',
|
|
174
|
-
'compelling_collaboration_offer',
|
|
175
|
-
'weekly_yes_pace',
|
|
176
|
-
]),
|
|
177
|
-
bragability: nicheDimension([
|
|
178
|
-
'show_up_likelihood',
|
|
179
|
-
'compelling_story_hook',
|
|
180
|
-
'positive_status_association',
|
|
181
|
-
'share_behavior',
|
|
182
|
-
]),
|
|
183
|
-
sellability: nicheDimension([
|
|
184
|
-
'reachable_owned_audience',
|
|
185
|
-
'specific_value_exchange',
|
|
186
|
-
'offer_through_collaboration',
|
|
187
|
-
'audience_response',
|
|
188
|
-
]),
|
|
189
|
-
}).strict(),
|
|
190
|
-
}).strict();
|
|
191
|
-
|
|
192
|
-
function routeMissing(result) {
|
|
193
|
-
return result.status === 404 || result.status === 405 || result.status === 501;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
const COACH_NOT_READY =
|
|
197
|
-
'The Fully Booked Coach managed routes are not deployed for this workspace yet. Nothing changed. Use draft mode and ask the operator to finish the coach migration and API deployment.';
|
|
198
|
-
const COACH_BOOK_UNSAFE =
|
|
199
|
-
'KMHub rejected an unsafe Book OS response. Nothing changed.';
|
|
200
|
-
|
|
201
|
-
function coachOut(result, out, text) {
|
|
202
|
-
return routeMissing(result) ? text(COACH_NOT_READY, true) : out(result);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function coachBookOut(result, kind, out, text) {
|
|
206
|
-
if (routeMissing(result)) return text(COACH_NOT_READY, true);
|
|
207
|
-
try {
|
|
208
|
-
return out(projectCoachBookConnectorResult(kind, result));
|
|
209
|
-
} catch {
|
|
210
|
-
return text(COACH_BOOK_UNSAFE, true);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
export function register(server, call, { out, text, qs }) {
|
|
215
|
-
server.tool(
|
|
216
|
-
'km_coach_context',
|
|
217
|
-
'Load the connected Fully Booked Coach workspace, method stage, capacity boundaries, privacy defaults, record counts, and tenant identity. Call this first in a coach conversation. It reads only and never changes anything.',
|
|
218
|
-
{},
|
|
219
|
-
async () => coachOut(await call('GET', '/coach/context'), out, text),
|
|
220
|
-
);
|
|
221
|
-
|
|
222
|
-
server.tool(
|
|
223
|
-
'km_coach_method',
|
|
224
|
-
'Load verified Fully Booked Coach guidance for one exact workflow. Use after workspace context and before giving method-based advice or preparing a related write. It returns approved guidance metadata only, never protected source text, and fails closed when the private tenant cassette is not ready.',
|
|
225
|
-
{
|
|
226
|
-
workflow: z.enum(COACH_METHOD_WORKFLOWS).describe('The exact Fully Booked Coach workflow that grounds this request.'),
|
|
227
|
-
},
|
|
228
|
-
async ({ workflow }) => coachOut(
|
|
229
|
-
await call('GET', `/coach/method${qs({ workflow })}`),
|
|
230
|
-
out,
|
|
231
|
-
text,
|
|
232
|
-
),
|
|
233
|
-
);
|
|
234
|
-
server.tool(
|
|
235
|
-
'km_coach_ask_book',
|
|
236
|
-
'Ask the Book is intentionally closed by product decision. This compatibility tool performs no retrieval, provider call, or charge. Use the method catalog to find an available workflow.',
|
|
237
|
-
{
|
|
238
|
-
question: z.string().min(4).max(1200).describe('Legacy compatibility field. Ask the Book remains closed and does not process this question.'),
|
|
239
|
-
idempotency_key: z.string().min(1).max(120).regex(/^[A-Za-z0-9][A-Za-z0-9._:]{0,119}$/).describe('Legacy compatibility field. No run is created.'),
|
|
240
|
-
quote_id: z.string().uuid().optional().describe('Legacy compatibility field. No quote is used.'),
|
|
241
|
-
confirm_token: z.string().min(1).max(4096).optional().describe('Legacy compatibility field. No confirmation is consumed.'),
|
|
242
|
-
},
|
|
243
|
-
async () => text(
|
|
244
|
-
'Ask the Book remains intentionally closed by product decision. No retrieval ran, no provider was called, and nothing was charged. Use km_coach_book_catalog to find a method and km_coach_book_tool to start, advance, resume, or cancel a run.',
|
|
245
|
-
true,
|
|
246
|
-
),
|
|
247
|
-
);
|
|
248
|
-
|
|
249
|
-
server.tool(
|
|
250
|
-
'km_coach_book_catalog',
|
|
251
|
-
[
|
|
252
|
-
'List, search, or describe the public Fully Booked Coach method library.',
|
|
253
|
-
'Catalog and search read the managed method catalog. Describe reads one exact method id.',
|
|
254
|
-
'This tool is read-only and returns public method metadata, source page locators, availability, and public input steps.',
|
|
255
|
-
].join(' '),
|
|
256
|
-
{
|
|
257
|
-
action: z.enum(['catalog', 'search', 'describe']).describe('Use catalog for the full library, search for a local public-metadata match, or describe for one exact method.'),
|
|
258
|
-
query: z.string().trim().min(2).max(160).optional().describe('Required only for search. Matches public ids, titles, summaries, categories, executor kinds, and statuses.'),
|
|
259
|
-
tool_spec_id: z.string().regex(/^fbc[.]method[.][0-9]{3}[.][a-z0-9]+(?:-[a-z0-9]+)*$/).optional().describe('Required only for describe. Use the exact id returned by catalog or search.'),
|
|
260
|
-
},
|
|
261
|
-
async ({ action, query, tool_spec_id }) => {
|
|
262
|
-
if (action === 'describe') {
|
|
263
|
-
if (!tool_spec_id || query) {
|
|
264
|
-
return text('Describe requires only one exact tool_spec_id. Nothing changed.', true);
|
|
265
|
-
}
|
|
266
|
-
return coachBookOut(
|
|
267
|
-
await call('GET', `/coach/book/methods/${encodeURIComponent(tool_spec_id)}`),
|
|
268
|
-
'describe',
|
|
269
|
-
out,
|
|
270
|
-
text,
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
|
-
if (tool_spec_id) {
|
|
274
|
-
return text('Catalog and search do not accept tool_spec_id. Nothing changed.', true);
|
|
275
|
-
}
|
|
276
|
-
if (action === 'catalog' && query) {
|
|
277
|
-
return text('Catalog does not accept a search query. Use the search action instead.', true);
|
|
278
|
-
}
|
|
279
|
-
if (action === 'search' && !query) {
|
|
280
|
-
return text('Search requires a query. Nothing changed.', true);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
const result = await call('GET', '/coach/book/methods');
|
|
284
|
-
if (routeMissing(result)) return text(COACH_NOT_READY, true);
|
|
285
|
-
let safeResult;
|
|
286
|
-
try {
|
|
287
|
-
safeResult = projectCoachBookConnectorResult('catalog', result);
|
|
288
|
-
} catch {
|
|
289
|
-
return text(COACH_BOOK_UNSAFE, true);
|
|
290
|
-
}
|
|
291
|
-
if (!safeResult.ok || action === 'catalog') return out(safeResult);
|
|
292
|
-
|
|
293
|
-
const data = safeResult.data && typeof safeResult.data === 'object' && !Array.isArray(safeResult.data)
|
|
294
|
-
? safeResult.data
|
|
295
|
-
: {};
|
|
296
|
-
const methods = Array.isArray(data.methods) ? data.methods : [];
|
|
297
|
-
const needle = query.trim().toLowerCase();
|
|
298
|
-
const matches = methods.filter((method) => {
|
|
299
|
-
if (!method || typeof method !== 'object' || Array.isArray(method)) return false;
|
|
300
|
-
const publicSpec = method.public_spec && typeof method.public_spec === 'object'
|
|
301
|
-
&& !Array.isArray(method.public_spec)
|
|
302
|
-
? method.public_spec
|
|
303
|
-
: {};
|
|
304
|
-
return [
|
|
305
|
-
method.tool_spec_id,
|
|
306
|
-
method.title,
|
|
307
|
-
method.summary,
|
|
308
|
-
method.category,
|
|
309
|
-
method.implementation_status,
|
|
310
|
-
method.executor_kind,
|
|
311
|
-
method.effect_class,
|
|
312
|
-
method.availability_class,
|
|
313
|
-
publicSpec.title,
|
|
314
|
-
publicSpec.summary,
|
|
315
|
-
publicSpec.category,
|
|
316
|
-
publicSpec.executorKind,
|
|
317
|
-
publicSpec.effectClass,
|
|
318
|
-
].filter((value) => typeof value === 'string').join(' ').toLowerCase().includes(needle);
|
|
319
|
-
});
|
|
320
|
-
return out({
|
|
321
|
-
...safeResult,
|
|
322
|
-
data: {
|
|
323
|
-
...data,
|
|
324
|
-
methods: matches,
|
|
325
|
-
search: {
|
|
326
|
-
query: query.trim(),
|
|
327
|
-
matched: matches.length,
|
|
328
|
-
total: methods.length,
|
|
329
|
-
},
|
|
330
|
-
},
|
|
331
|
-
});
|
|
332
|
-
},
|
|
333
|
-
);
|
|
334
|
-
|
|
335
|
-
server.tool(
|
|
336
|
-
'km_coach_book_recommend',
|
|
337
|
-
[
|
|
338
|
-
'Recommend up to three public Fully Booked Coach methods for one coaching goal.',
|
|
339
|
-
'This read-only tool uses the deterministic KMHub ranker and performs no source retrieval, no provider call, no write, no charge, and no external effect.',
|
|
340
|
-
'It never exposes protected source text, source code, or skill files. Ask the Book remains closed.',
|
|
341
|
-
].join(' '),
|
|
342
|
-
{
|
|
343
|
-
goal: z.string().trim().min(4).max(600).describe('Describe the coaching outcome or problem in plain language.'),
|
|
344
|
-
availability_scope: z.enum(['runnable_only', 'include_pending']).optional().describe('Use runnable_only by default, or include pending methods for planning visibility.'),
|
|
345
|
-
max_minutes: z.number().int().min(5).max(240).optional().describe('Optional maximum estimated method duration.'),
|
|
346
|
-
executor_kind: z.enum(['deterministic_single_step', 'guided_multi_step']).optional().describe('Optional execution-style filter.'),
|
|
347
|
-
effect_class: z.enum(['none', 'action_preview']).optional().describe('Optional effect boundary filter.'),
|
|
348
|
-
},
|
|
349
|
-
async ({
|
|
350
|
-
goal,
|
|
351
|
-
availability_scope,
|
|
352
|
-
max_minutes,
|
|
353
|
-
executor_kind,
|
|
354
|
-
effect_class,
|
|
355
|
-
}) => coachBookOut(
|
|
356
|
-
await call('GET', `/coach/book/methods/recommend${qs({
|
|
357
|
-
goal,
|
|
358
|
-
availability_scope,
|
|
359
|
-
max_minutes,
|
|
360
|
-
executor_kind,
|
|
361
|
-
effect_class,
|
|
362
|
-
})}`),
|
|
363
|
-
'recommendation',
|
|
364
|
-
out,
|
|
365
|
-
text,
|
|
366
|
-
),
|
|
367
|
-
);
|
|
368
|
-
|
|
369
|
-
server.tool(
|
|
370
|
-
'km_coach_book_tool',
|
|
371
|
-
[
|
|
372
|
-
'Start, advance, resume, or cancel one generic Fully Booked Coach method run.',
|
|
373
|
-
'Start, advance, and cancel are confirmed zero-cost writes. Show any 409 confirmation sentence and wait for an explicit yes before retrying once with the exact returned confirm_token.',
|
|
374
|
-
'Never invent or self-confirm a token. Resume is a read-only run lookup and accepts only action plus run_id.',
|
|
375
|
-
'This tool creates only bounded method artifacts and action previews. It cannot contact, publish, spend, or perform an external connector action.',
|
|
376
|
-
].join(' '),
|
|
377
|
-
{
|
|
378
|
-
action: z.enum(['start', 'advance', 'resume', 'cancel']),
|
|
379
|
-
tool_spec_id: z.string().regex(/^fbc[.]method[.][0-9]{3}[.][a-z0-9]+(?:-[a-z0-9]+)*$/).optional().describe('Required only for start. Use the exact method id from the catalog.'),
|
|
380
|
-
run_id: z.string().uuid().optional().describe('Required for advance, resume, and cancel.'),
|
|
381
|
-
step_id: z.string().min(1).max(80).regex(/^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$/).optional().describe('Required for start and advance. Use the exact current public step id.'),
|
|
382
|
-
values: z.record(z.unknown()).optional().describe('Required for start and advance. Send one bounded object containing only the current public step fields.'),
|
|
383
|
-
idempotency_key: z.string().min(1).max(96).regex(/^[A-Za-z0-9][A-Za-z0-9:._-]{0,95}$/).optional().describe('Required for start, advance, and cancel. Reuse it for an exact confirmation retry.'),
|
|
384
|
-
confirm_token: z.string().min(1).max(4096).optional().describe('Use only the exact token returned in a 409 response after the coach explicitly approves the displayed confirmation sentence.'),
|
|
385
|
-
},
|
|
386
|
-
async ({
|
|
387
|
-
action,
|
|
388
|
-
tool_spec_id,
|
|
389
|
-
run_id,
|
|
390
|
-
step_id,
|
|
391
|
-
values,
|
|
392
|
-
idempotency_key,
|
|
393
|
-
confirm_token,
|
|
394
|
-
}) => {
|
|
395
|
-
if (action === 'resume') {
|
|
396
|
-
if (!run_id || tool_spec_id || step_id || values || idempotency_key || confirm_token) {
|
|
397
|
-
return text('Resume accepts only action and run_id. Nothing changed.', true);
|
|
398
|
-
}
|
|
399
|
-
return coachBookOut(
|
|
400
|
-
await call('GET', `/coach/book/runs/${encodeURIComponent(run_id)}`),
|
|
401
|
-
'run',
|
|
402
|
-
out,
|
|
403
|
-
text,
|
|
404
|
-
);
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
if (action === 'start') {
|
|
408
|
-
if (!tool_spec_id || run_id || !step_id || !values || !idempotency_key) {
|
|
409
|
-
return text('Start requires tool_spec_id, step_id, values, and idempotency_key, with no run_id. Nothing changed.', true);
|
|
410
|
-
}
|
|
411
|
-
return coachBookOut(await call(
|
|
412
|
-
'POST',
|
|
413
|
-
`/coach/book/methods/${encodeURIComponent(tool_spec_id)}/runs`,
|
|
414
|
-
{
|
|
415
|
-
tool_spec_id,
|
|
416
|
-
step_id,
|
|
417
|
-
values,
|
|
418
|
-
idempotency_key,
|
|
419
|
-
...(confirm_token ? { confirm_token } : {}),
|
|
420
|
-
},
|
|
421
|
-
), 'run', out, text);
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
if (action === 'advance') {
|
|
425
|
-
if (!run_id || tool_spec_id || !step_id || !values || !idempotency_key) {
|
|
426
|
-
return text('Advance requires run_id, step_id, values, and idempotency_key, with no tool_spec_id. Nothing changed.', true);
|
|
427
|
-
}
|
|
428
|
-
return coachBookOut(await call(
|
|
429
|
-
'POST',
|
|
430
|
-
`/coach/book/runs/${encodeURIComponent(run_id)}/steps`,
|
|
431
|
-
{
|
|
432
|
-
step_id,
|
|
433
|
-
values,
|
|
434
|
-
idempotency_key,
|
|
435
|
-
...(confirm_token ? { confirm_token } : {}),
|
|
436
|
-
},
|
|
437
|
-
), 'run', out, text);
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
if (!run_id || tool_spec_id || step_id || values || !idempotency_key) {
|
|
441
|
-
return text('Cancel requires only run_id and idempotency_key, plus an explicit confirm_token retry when KMHub requests it. Nothing changed.', true);
|
|
442
|
-
}
|
|
443
|
-
return coachBookOut(await call(
|
|
444
|
-
'POST',
|
|
445
|
-
`/coach/book/runs/${encodeURIComponent(run_id)}/cancel`,
|
|
446
|
-
{
|
|
447
|
-
idempotency_key,
|
|
448
|
-
...(confirm_token ? { confirm_token } : {}),
|
|
449
|
-
},
|
|
450
|
-
), 'run', out, text);
|
|
451
|
-
},
|
|
452
|
-
);
|
|
453
|
-
|
|
454
|
-
server.tool(
|
|
455
|
-
'km_coach_daily_operator',
|
|
456
|
-
'Build today\'s operating view from client sessions, commitments, stalled opportunities, approvals, Daily Cookie work, and capacity. Use for what needs me today or a morning review. It reads only and never sends, schedules, or publishes.',
|
|
457
|
-
{},
|
|
458
|
-
async () => coachOut(await call('GET', '/coach/daily-operator'), out, text),
|
|
459
|
-
);
|
|
460
|
-
|
|
461
|
-
server.tool(
|
|
462
|
-
'km_coach_list_clients',
|
|
463
|
-
'List recent clients in this coach workspace so a named client can be resolved before reading or writing a Client MAP, session, commitment, outcome, or confidential note. It never searches another tenant.',
|
|
464
|
-
{
|
|
465
|
-
limit: z.number().int().min(1).max(100).optional().describe('How many clients to return. Default 25.'),
|
|
466
|
-
},
|
|
467
|
-
async ({ limit }) => coachOut(await call('GET', `/coach/clients${qs({ limit: limit ?? 25 })}`), out, text),
|
|
468
|
-
);
|
|
469
|
-
|
|
470
|
-
server.tool(
|
|
471
|
-
'km_coach_create_client',
|
|
472
|
-
'Create one client in the connected coach workspace after confirming the person or company details. Use a stable idempotency key for this exact request. This creates only the client record. It does not create a Client MAP, engagement, invoice, message, or external action.',
|
|
473
|
-
{
|
|
474
|
-
client_type: z.enum(['individual', 'company']).optional().describe('Default individual.'),
|
|
475
|
-
first_name: z.string().min(1).max(200).describe('Required display or contact first name.'),
|
|
476
|
-
last_name: z.string().max(200).optional(),
|
|
477
|
-
company_name: z.string().max(300).optional(),
|
|
478
|
-
email: z.string().email().max(320).optional(),
|
|
479
|
-
phone: z.string().max(100).optional(),
|
|
480
|
-
notes: z.string().max(4000).optional(),
|
|
481
|
-
idempotency_key: z.string().min(1).max(200).describe('Stable key for this exact client create request.'),
|
|
482
|
-
},
|
|
483
|
-
async (args) => coachOut(await call('POST', '/coach/clients', args), out, text),
|
|
484
|
-
);
|
|
485
|
-
server.tool(
|
|
486
|
-
'km_coach_list_records',
|
|
487
|
-
'Read shared coach records from KMHub, optionally filtered by object type, exact client, or lifecycle state. Use for Client MAPs, sessions, commitments, outcomes, opportunities, consent, content, or operations. Local only Booked records are never available here.',
|
|
488
|
-
{
|
|
489
|
-
object_type: z.enum(COACH_OBJECT_TYPES).optional().describe('One fully_booked_coach.v1 object type.'),
|
|
490
|
-
client_id: z.string().uuid().optional().describe('Exact KMHub client UUID.'),
|
|
491
|
-
state: z.string().regex(/^[a-z][a-z0-9_]{0,63}$/).optional().describe('Lifecycle state such as active, planned, or submitted.'),
|
|
492
|
-
limit: z.number().int().min(1).max(200).optional().describe('How many records to return. Default 50.'),
|
|
493
|
-
},
|
|
494
|
-
async ({ object_type, client_id, state, limit }) =>
|
|
495
|
-
coachOut(await call('GET', `/coach/records${qs({ object_type, client_id, state, limit: limit ?? 50 })}`), out, text),
|
|
496
|
-
);
|
|
497
|
-
|
|
498
|
-
server.tool(
|
|
499
|
-
'km_coach_create_client_map',
|
|
500
|
-
'Create the first Client MAP for one exact existing coach client. Resolve the client, call km_coach_method for client_map, confirm the complete MAP with the coach, and use this only when no current Client MAP exists. A later change must use km_coach_revise_record.',
|
|
501
|
-
{
|
|
502
|
-
client_id: z.string().uuid().describe('Exact KMHub client UUID.'),
|
|
503
|
-
destination: z.string().min(1).max(4000),
|
|
504
|
-
current_state: z.string().min(1).max(4000),
|
|
505
|
-
obstacles: z.array(z.string().min(1).max(1000)).max(50).optional(),
|
|
506
|
-
measures: z.array(z.object({
|
|
507
|
-
id: z.string().min(1).max(200),
|
|
508
|
-
name: z.string().min(1).max(500),
|
|
509
|
-
baseline: z.union([z.string().min(1).max(1000), z.number(), z.null()]).optional(),
|
|
510
|
-
target: z.union([z.string().min(1).max(1000), z.number(), z.null()]).optional(),
|
|
511
|
-
actual: z.union([z.string().min(1).max(1000), z.number(), z.null()]).optional(),
|
|
512
|
-
evidence_ref: z.union([z.string().min(1).max(1000), z.number(), z.null()]).optional(),
|
|
513
|
-
}).strict()).max(50).optional(),
|
|
514
|
-
route: z.array(z.string().min(1).max(1000)).max(50).optional(),
|
|
515
|
-
permissions: z.array(z.string().min(1).max(500)).max(50).optional(),
|
|
516
|
-
change_source: z.string().max(200).optional(),
|
|
517
|
-
idempotency_key: z.string().min(1).max(200).describe('Stable key for this exact first MAP request.'),
|
|
518
|
-
},
|
|
519
|
-
async (args) => coachOut(await call('POST', '/coach/client-maps', args), out, text),
|
|
520
|
-
);
|
|
521
|
-
|
|
522
|
-
server.tool(
|
|
523
|
-
'km_coach_revise_record',
|
|
524
|
-
'Create one immutable successor for an exact current coach record. Read the current record first, keep the complete replacement body inside the same client and privacy boundary, and use one stable idempotency key. The server refuses protected object types and records that already have a successor.',
|
|
525
|
-
{
|
|
526
|
-
record_id: z.string().uuid().describe('Exact current coach record UUID.'),
|
|
527
|
-
body: z.record(z.unknown()).describe('Complete replacement body. Never include a credential, token, API key, or password.'),
|
|
528
|
-
lifecycle_state: z.string().regex(/^[a-z][a-z0-9_]{0,63}$/).optional(),
|
|
529
|
-
idempotency_key: z.string().min(1).max(200).describe('Stable key for this exact revision.'),
|
|
530
|
-
},
|
|
531
|
-
async ({ record_id, ...body }) => coachOut(
|
|
532
|
-
await call('POST', `/coach/records/${encodeURIComponent(record_id)}/revisions`, body),
|
|
533
|
-
out,
|
|
534
|
-
text,
|
|
535
|
-
),
|
|
536
|
-
);
|
|
537
|
-
server.tool(
|
|
538
|
-
'km_coach_write_record',
|
|
539
|
-
'Create one idempotent shared Fully Booked Coach record in KMHub. Use for new operational facts, sessions, commitments, outcomes, opportunities, or content drafts. Client MAP creation and record revisions require KMHub dedicated reviewed workflows. Consent, approval, and publication records require dedicated human-reviewed KMHub workflows. Do not use for local only notes or credential values. Reusing the same idempotency key returns the original record.',
|
|
540
|
-
{
|
|
541
|
-
object_type: z.enum(COACH_WRITABLE_OBJECT_TYPES).describe('A safe draft or operational fully_booked_coach.v1 object type. Consent, approval, publication, audit, and execution records require dedicated human-reviewed routes.'),
|
|
542
|
-
idempotency_key: z.string().min(1).max(200).describe('A stable key for this exact create request.'),
|
|
543
|
-
client_id: z.string().uuid().optional().describe('Required for client scoped delivery records.'),
|
|
544
|
-
privacy_scope: z.enum(['business', 'client_shared', 'client_confidential']).optional().describe('Privacy boundary. Default business.'),
|
|
545
|
-
lifecycle_state: z.string().regex(/^[a-z][a-z0-9_]{0,63}$/).optional().describe('Validated lifecycle state when this object has one.'),
|
|
546
|
-
body: z.record(z.unknown()).optional().describe('Object fields. Never include a credential, token, API key, or password.'),
|
|
547
|
-
|
|
548
|
-
},
|
|
549
|
-
async (args) => coachOut(await call('POST', '/coach/records', args), out, text),
|
|
550
|
-
);
|
|
551
|
-
|
|
552
|
-
server.tool(
|
|
553
|
-
'km_coach_stalled_opportunities',
|
|
554
|
-
'Find open coach opportunities with no current next action or an overdue next action. Use before drafting a stalled prospect message. It reads only and does not contact anyone.',
|
|
555
|
-
{},
|
|
556
|
-
async () => coachOut(await call('GET', '/coach/stalled-opportunities'), out, text),
|
|
557
|
-
);
|
|
558
|
-
|
|
559
|
-
server.tool(
|
|
560
|
-
'km_coach_preview_action',
|
|
561
|
-
'Record the complete preview for one outbound or sensitive coach action. The preview includes target, content, channel, and expected effect. This has no external side effect and always returns approval required. There is deliberately no send tool in the pilot coach profile.',
|
|
562
|
-
{
|
|
563
|
-
target: z.string().min(1).max(500).describe('Exact recipient or destination.'),
|
|
564
|
-
content: z.string().min(1).max(10000).describe('The complete content that would be used.'),
|
|
565
|
-
channel: z.string().min(1).max(80).describe('Channel such as email, LinkedIn, or Daily Cookie review.'),
|
|
566
|
-
expected_effect: z.string().min(1).max(1000).describe('What the action is intended to change.'),
|
|
567
|
-
risk: z.enum(['internal', 'external', 'money', 'sensitive']).optional().describe('Default external.'),
|
|
568
|
-
action_class: z.string().max(120).optional().describe('Stable action class such as prospect_follow_up.'),
|
|
569
|
-
target_scope: z.string().max(200).optional().describe('The precise action boundary.'),
|
|
570
|
-
client_id: z.string().uuid().optional().describe('Exact client when the action is client scoped.'),
|
|
571
|
-
privacy_scope: z.enum(['business', 'client_shared', 'client_confidential']).optional(),
|
|
572
|
-
idempotency_key: z.string().min(1).max(200).describe('Stable key for this exact preview.'),
|
|
573
|
-
},
|
|
574
|
-
async (args) => coachOut(await call('POST', '/coach/action-previews', args), out, text),
|
|
575
|
-
);
|
|
576
|
-
|
|
577
|
-
server.tool(
|
|
578
|
-
'km_coach_decide_action',
|
|
579
|
-
'Record one human approve or reject decision for an exact current ActionRequest. This creates immutable decision evidence only. It never sends, schedules, charges, publishes, or executes the approved action. First show the stored preview and the KMHub confirmation sentence, then retry unchanged with the returned confirm_token after the coach explicitly agrees.',
|
|
580
|
-
{
|
|
581
|
-
action_request_id: z.string().uuid().describe('Exact current ActionRequest UUID.'),
|
|
582
|
-
decision: z.enum(['approve', 'reject']),
|
|
583
|
-
idempotency_key: z.string().min(1).max(200).describe('Stable key for this exact decision.'),
|
|
584
|
-
note: z.string().max(2000).optional(),
|
|
585
|
-
confirm_token: z.string().optional().describe('Use only after the coach explicitly approves the exact decision confirmation sentence returned by KMHub.'),
|
|
586
|
-
},
|
|
587
|
-
async ({ action_request_id, ...body }) => coachOut(
|
|
588
|
-
await call('POST', `/coach/action-requests/${encodeURIComponent(action_request_id)}/decision`, body),
|
|
589
|
-
out,
|
|
590
|
-
text,
|
|
591
|
-
),
|
|
592
|
-
);
|
|
593
|
-
server.tool(
|
|
594
|
-
'km_coach_submit_daily_cookie',
|
|
595
|
-
'Record a Daily Cookie brief using one book category: problem, dream, process, or proof. This connector step does not start paid production. The coach reviews scope and starts the managed production queue in KMHub Growth. Direct publishing stays disabled. Every client-linked request requires an active content reuse consent grant.',
|
|
596
|
-
{
|
|
597
|
-
category: z.enum(['problem', 'dream', 'process', 'proof']),
|
|
598
|
-
source_scope: z.enum(['business_owned', 'client_linked']).describe('Use business_owned only when no client material is involved. Use client_linked with client_id and consent_grant_id.'),
|
|
599
|
-
audience: z.string().min(1).max(500),
|
|
600
|
-
useful_idea: z.string().min(1).max(4000),
|
|
601
|
-
channel: z.string().min(1).max(80),
|
|
602
|
-
objective: z.string().max(1000).optional(),
|
|
603
|
-
due_at: z.string().max(80).optional(),
|
|
604
|
-
notes: z.string().max(4000).optional(),
|
|
605
|
-
client_id: z.string().uuid().optional(),
|
|
606
|
-
consent_grant_id: z.string().uuid().optional(),
|
|
607
|
-
idempotency_key: z.string().min(1).max(200),
|
|
608
|
-
},
|
|
609
|
-
async (args) => coachOut(await call('POST', '/coach/daily-cookie', args), out, text),
|
|
610
|
-
);
|
|
611
|
-
|
|
612
|
-
server.tool(
|
|
613
|
-
'km_coach_run_scout',
|
|
614
|
-
[
|
|
615
|
-
'Start one public or coach connected acquisition scout using only coach modes.',
|
|
616
|
-
'Past client opportunity and referral path are owned record reviews, so this tool refuses those two and directs the model to KMHub records.',
|
|
617
|
-
'Public provider work can cost money. Call first without confirm_spend to use the read-only price route. Run only after the coach explicitly agrees and call again with confirm_spend true.',
|
|
618
|
-
'KMHub may also return a confirmation sentence and confirm_token. Show the sentence, get an explicit yes, and retry the same arguments with that token.',
|
|
619
|
-
'Every successful coach run stores normalized ScoutFinding records with public source evidence. It creates no generic leads, deals, or outbound drafts. Read those findings after the run and do not record them a second time.',
|
|
620
|
-
].join(' '),
|
|
621
|
-
{
|
|
622
|
-
mode: z.enum(SCOUT_MODES),
|
|
623
|
-
who_to_find: z.string().min(1).max(2000).describe('The group and problem criteria in the coach\'s words.'),
|
|
624
|
-
area: z.string().max(200).optional(),
|
|
625
|
-
target_count: z.number().int().min(1).max(100).optional(),
|
|
626
|
-
outreach_context: z.string().max(1000).optional().describe('The real coach offer and useful reason to connect.'),
|
|
627
|
-
idempotency_key: z.string().min(1).max(170).regex(/^[A-Za-z0-9][A-Za-z0-9:._-]{0,169}$/).describe('Stable key for this exact Scout intent. Reuse it for the price, confirmation, and any retry.'),
|
|
628
|
-
confirm_spend: z.boolean().optional().describe('Leave out for the free price preview. Set true only after explicit agreement.'),
|
|
629
|
-
confirm_token: z.string().optional().describe('Use only the token KMHub returned for these exact arguments after the coach explicitly approved its confirmation sentence.'),
|
|
630
|
-
},
|
|
631
|
-
async ({ mode, who_to_find, area, target_count, outreach_context, idempotency_key, confirm_spend, confirm_token }) => {
|
|
632
|
-
if (!PUBLIC_SCOUT_MODES.has(mode)) {
|
|
633
|
-
return text(
|
|
634
|
-
`${mode} uses owned KMHub relationships and client history. Use km_coach_list_records instead. Nothing ran and nothing was charged.`,
|
|
635
|
-
);
|
|
636
|
-
}
|
|
637
|
-
const direction = MODE_DIRECTION[mode] || 'Find coach acquisition opportunities with public evidence.';
|
|
638
|
-
const endpoint = confirm_spend === true ? '/sourcing/missions' : '/sourcing/missions/estimate';
|
|
639
|
-
return out(await call('POST', endpoint, {
|
|
640
|
-
describe: `Fully Booked Coach scout: ${direction} Coach criteria: ${who_to_find}`,
|
|
641
|
-
area,
|
|
642
|
-
target_count: target_count ?? 25,
|
|
643
|
-
outreach_context,
|
|
644
|
-
idempotency_key,
|
|
645
|
-
vertical_flavor: 'business_coach',
|
|
646
|
-
coach_scout_mode: mode,
|
|
647
|
-
deep_research: true,
|
|
648
|
-
ai_copywriting: false,
|
|
649
|
-
confirm_spend,
|
|
650
|
-
confirm_token,
|
|
651
|
-
}));
|
|
652
|
-
},
|
|
653
|
-
);
|
|
654
|
-
|
|
655
|
-
server.tool(
|
|
656
|
-
'km_coach_list_scouts',
|
|
657
|
-
'List recent provider scout runs for this workspace. This reads status only, costs nothing, and starts nothing.',
|
|
658
|
-
{
|
|
659
|
-
limit: z.number().int().min(1).max(50).optional(),
|
|
660
|
-
status: z.enum(['queued', 'running', 'succeeded', 'failed', 'canceled']).optional(),
|
|
661
|
-
},
|
|
662
|
-
async ({ limit, status }) => out(await call('GET', `/sourcing/missions${qs({ limit: limit ?? 10, status })}`)),
|
|
663
|
-
);
|
|
664
|
-
|
|
665
|
-
server.tool(
|
|
666
|
-
'km_coach_scout_status',
|
|
667
|
-
'Check one provider scout run and return current progress or results. This reads only and costs nothing.',
|
|
668
|
-
{
|
|
669
|
-
mission_id: z.string().uuid(),
|
|
670
|
-
},
|
|
671
|
-
async ({ mission_id }) => out(await call('GET', `/sourcing/missions/${encodeURIComponent(mission_id)}`)),
|
|
672
|
-
);
|
|
673
|
-
|
|
674
|
-
server.tool(
|
|
675
|
-
'km_coach_stop_scout',
|
|
676
|
-
'Stop a queued or running provider scout. Use immediately when the coach says stop or the criteria are wrong. Work already completed remains, and no future provider work should continue.',
|
|
677
|
-
{
|
|
678
|
-
mission_id: z.string().uuid(),
|
|
679
|
-
confirm_token: z.string().optional().describe('Use only the token KMHub returned after the coach explicitly approved stopping this exact scout.'),
|
|
680
|
-
},
|
|
681
|
-
async ({ mission_id, confirm_token }) => out(await call('POST', `/sourcing/missions/${encodeURIComponent(mission_id)}/cancel`, { confirm_token })),
|
|
682
|
-
);
|
|
683
|
-
|
|
684
|
-
server.tool(
|
|
685
|
-
'km_coach_record_scout_finding',
|
|
686
|
-
'Record one verified coach scout finding after deduplication. Every finding requires source evidence, group match, problem evidence, rationale, uncertainty, a duplicate key, readiness, and a safe next step. It does not contact anyone.',
|
|
687
|
-
{
|
|
688
|
-
mode: z.enum([...PUBLIC_SCOUT_MODES]),
|
|
689
|
-
source_evidence: z.array(z.object({
|
|
690
|
-
url: z.string().url(),
|
|
691
|
-
observed_at: z.string().datetime(),
|
|
692
|
-
summary: z.string().min(1).max(2000),
|
|
693
|
-
})).min(1),
|
|
694
|
-
group_match: z.string().min(1).max(2000),
|
|
695
|
-
problem_evidence: z.string().min(1).max(4000),
|
|
696
|
-
rationale: z.string().min(1).max(4000),
|
|
697
|
-
uncertainty: z.string().min(1).max(2000),
|
|
698
|
-
duplicate_key: z.string().min(1).max(300),
|
|
699
|
-
safe_next_step: z.string().min(1).max(2000),
|
|
700
|
-
readiness: z.enum(['research_only', 'relationship_opportunity', 'outreach_ready']),
|
|
701
|
-
idempotency_key: z.string().min(1).max(200),
|
|
702
|
-
},
|
|
703
|
-
async ({ idempotency_key, ...finding }) =>
|
|
704
|
-
coachOut(await call('POST', '/coach/records', {
|
|
705
|
-
object_type: 'ScoutFinding',
|
|
706
|
-
idempotency_key,
|
|
707
|
-
privacy_scope: 'business',
|
|
708
|
-
lifecycle_state: finding.readiness,
|
|
709
|
-
body: finding,
|
|
710
|
-
}), out, text),
|
|
711
|
-
);
|
|
712
|
-
server.tool(
|
|
713
|
-
'km_coach_promote_scout_finding',
|
|
714
|
-
'Promote one exact current reviewed public ScoutFinding into one business relationship and one opportunity. Use only after the coach selects the automatically stored finding and confirms the editable prospect fields. This creates no client, message, outbound request, charge, or external action. Exact retries return the original promotion.',
|
|
715
|
-
{
|
|
716
|
-
scout_finding_id: z.string().uuid().describe('Exact current ScoutFinding UUID.'),
|
|
717
|
-
name: z.string().min(1).max(300).describe('Reviewed relationship and opportunity name.'),
|
|
718
|
-
context: z.string().max(2000).optional(),
|
|
719
|
-
source_label: z.string().max(300).optional(),
|
|
720
|
-
next_action: z.string().max(1000).optional(),
|
|
721
|
-
next_action_due_at: z.string().datetime().max(80).optional(),
|
|
722
|
-
state: z.enum(['identified', 'qualified', 'conversation', 'strategy_session', 'proposed', 'nurture']).optional(),
|
|
723
|
-
idempotency_key: z.string().min(1).max(170).regex(/^[A-Za-z0-9][A-Za-z0-9:._-]{0,169}$/).describe('Stable key for this exact finding promotion.'),
|
|
724
|
-
},
|
|
725
|
-
async ({ scout_finding_id, ...body }) => coachOut(
|
|
726
|
-
await call('POST', `/coach/scout-findings/${encodeURIComponent(scout_finding_id)}/promote`, body),
|
|
727
|
-
out,
|
|
728
|
-
text,
|
|
729
|
-
),
|
|
730
|
-
);
|
|
731
|
-
server.tool(
|
|
732
|
-
'km_coach_niche_fit',
|
|
733
|
-
[
|
|
734
|
-
'Run or resume the source-locked Podcast Niche Selector from Fully Booked Coach PDF pages 115 to 125.',
|
|
735
|
-
'A new run is deterministic, uses no provider, costs $0, sends nothing, publishes nothing, and requires a confirmation before saving the artifact and human decision.',
|
|
736
|
-
'Use run_id alone to resume the identical run created in native web or Claude. Never recreate a run from memory when a run id is available.',
|
|
737
|
-
].join(' '),
|
|
738
|
-
{
|
|
739
|
-
input: NICHE_FIT_INPUT.optional().describe('The complete evidence-backed scorecard for a new run.'),
|
|
740
|
-
run_id: z.string().uuid().optional().describe('An existing Book OS run to resume. Use this without input or idempotency_key.'),
|
|
741
|
-
idempotency_key: z.string().min(1).max(96).regex(/^[A-Za-z0-9][A-Za-z0-9:._-]{0,95}$/).optional().describe('Stable key for one new scorecard run and its confirmation retry.'),
|
|
742
|
-
confirm_token: z.string().min(1).max(4096).optional().describe('Use only after the coach approves saving the displayed zero-cost run.'),
|
|
743
|
-
},
|
|
744
|
-
async ({ input, run_id, idempotency_key, confirm_token }) => {
|
|
745
|
-
if (run_id) {
|
|
746
|
-
if (input || idempotency_key || confirm_token) {
|
|
747
|
-
return text('Resume accepts only run_id. Nothing changed.', true);
|
|
748
|
-
}
|
|
749
|
-
return coachBookOut(
|
|
750
|
-
await call('GET', `/coach/book/runs/${encodeURIComponent(run_id)}`),
|
|
751
|
-
'run',
|
|
752
|
-
out,
|
|
753
|
-
text,
|
|
754
|
-
);
|
|
755
|
-
}
|
|
756
|
-
if (!input || !idempotency_key) {
|
|
757
|
-
return text('A new niche run requires the complete scorecard input and one stable idempotency_key. Nothing changed.', true);
|
|
758
|
-
}
|
|
759
|
-
return coachBookOut(await call(
|
|
760
|
-
'POST',
|
|
761
|
-
'/coach/book/tools/niche-fit/runs',
|
|
762
|
-
{
|
|
763
|
-
tool_spec_id: 'fbc.niche-fit.v1',
|
|
764
|
-
input,
|
|
765
|
-
idempotency_key,
|
|
766
|
-
confirm_token,
|
|
767
|
-
},
|
|
768
|
-
), 'run', out, text);
|
|
769
|
-
},
|
|
770
|
-
);
|
|
771
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Tool family: Fully Booked Coach operations.
|
|
3
|
+
*
|
|
4
|
+
* The coach profile contains only coach relevant tools. Draft and preview tools
|
|
5
|
+
* never send or publish. Paid scouting keeps the existing price then confirm gate.
|
|
6
|
+
*/
|
|
7
|
+
import { projectCoachBookConnectorResult } from '../coach-book-output-guard.mjs';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
|
|
10
|
+
export const FAMILY = 'coach';
|
|
11
|
+
|
|
12
|
+
export const TOOLS = [
|
|
13
|
+
'km_coach_context',
|
|
14
|
+
'km_coach_method',
|
|
15
|
+
'km_coach_ask_book',
|
|
16
|
+
'km_coach_book_catalog',
|
|
17
|
+
'km_coach_book_recommend',
|
|
18
|
+
'km_coach_book_tool',
|
|
19
|
+
'km_coach_niche_fit',
|
|
20
|
+
'km_coach_daily_operator',
|
|
21
|
+
'km_coach_list_clients',
|
|
22
|
+
'km_coach_create_client',
|
|
23
|
+
|
|
24
|
+
'km_coach_list_records',
|
|
25
|
+
'km_coach_create_client_map',
|
|
26
|
+
'km_coach_revise_record',
|
|
27
|
+
|
|
28
|
+
'km_coach_write_record',
|
|
29
|
+
|
|
30
|
+
'km_coach_stalled_opportunities',
|
|
31
|
+
'km_coach_preview_action',
|
|
32
|
+
'km_coach_decide_action',
|
|
33
|
+
|
|
34
|
+
'km_coach_submit_daily_cookie',
|
|
35
|
+
'km_coach_run_scout',
|
|
36
|
+
'km_coach_list_scouts',
|
|
37
|
+
'km_coach_scout_status',
|
|
38
|
+
'km_coach_stop_scout',
|
|
39
|
+
'km_coach_record_scout_finding',
|
|
40
|
+
'km_coach_promote_scout_finding',
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
export const PROFILES = ['coach'];
|
|
44
|
+
|
|
45
|
+
const COACH_METHOD_WORKFLOWS = [
|
|
46
|
+
'method_boundary',
|
|
47
|
+
'niche',
|
|
48
|
+
'capacity',
|
|
49
|
+
'acquisition',
|
|
50
|
+
'authority_content',
|
|
51
|
+
'strategy_session',
|
|
52
|
+
'client_map',
|
|
53
|
+
'renewal_referral_proof',
|
|
54
|
+
'scouting',
|
|
55
|
+
];
|
|
56
|
+
const COACH_OBJECT_TYPES = [
|
|
57
|
+
'CoachOperatingBrief', 'MethodologyActivation', 'CapacityPolicy', 'FullyBookedThreshold',
|
|
58
|
+
'NicheHypothesis', 'NicheScorecard', 'Offer', 'Program', 'Cohort', 'Channel', 'Integration',
|
|
59
|
+
'Relationship', 'ProspectProfile', 'Opportunity', 'PipelineStage', 'ScoutRun', 'ScoutFinding',
|
|
60
|
+
'Collaboration', 'ReferralPartner', 'AudienceSegment', 'GrowthSprint', 'Campaign', 'LeadMagnet',
|
|
61
|
+
'Tripwire', 'Engagement', 'ClientMAP', 'Session', 'SessionPreparation', 'Commitment', 'Outcome',
|
|
62
|
+
'ResultMeasure', 'ClientHealth', 'Renewal', 'ReferralRequest', 'Testimonial', 'ProofPermission',
|
|
63
|
+
'Handover', 'ContentIdea', 'ContentAsset', 'ContentCategory', 'ContentBrief',
|
|
64
|
+
'DailyCookieRequest', 'EditorialApproval', 'PublicationRequest', 'PerformanceObservation',
|
|
65
|
+
'ConfidentialNote', 'ConsentGrant', 'SecureReference', 'ActionRequest', 'Approval',
|
|
66
|
+
'ManagedTask', 'AuditEvent', 'ExportJob', 'SyncCursor',
|
|
67
|
+
];
|
|
68
|
+
const COACH_WRITABLE_OBJECT_TYPES = [
|
|
69
|
+
'CoachOperatingBrief', 'CapacityPolicy', 'FullyBookedThreshold',
|
|
70
|
+
'NicheHypothesis', 'NicheScorecard', 'Offer', 'Program', 'Cohort', 'Channel', 'Integration',
|
|
71
|
+
'Relationship', 'ProspectProfile', 'Opportunity', 'PipelineStage', 'ScoutFinding',
|
|
72
|
+
'Collaboration', 'ReferralPartner', 'AudienceSegment', 'GrowthSprint', 'Campaign', 'LeadMagnet',
|
|
73
|
+
'Tripwire', 'Engagement', 'Session', 'SessionPreparation', 'Commitment', 'Outcome',
|
|
74
|
+
'ResultMeasure', 'ClientHealth', 'Renewal', 'ReferralRequest', 'Testimonial', 'Handover',
|
|
75
|
+
'ContentIdea', 'ContentAsset', 'ContentCategory', 'ContentBrief', 'PerformanceObservation',
|
|
76
|
+
'ConfidentialNote', 'ManagedTask',
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
const SCOUT_MODES = [
|
|
80
|
+
'micro-niche-mapper',
|
|
81
|
+
'niche-validation-scout',
|
|
82
|
+
'collaborator-scout',
|
|
83
|
+
'ideal-client-twin-scout',
|
|
84
|
+
'past-client-opportunity-scout',
|
|
85
|
+
'referral-path-scout',
|
|
86
|
+
'ideal-client-scout',
|
|
87
|
+
'niche-and-growth-scout',
|
|
88
|
+
'ideal-organization-and-partner-scout',
|
|
89
|
+
'ideal-person-and-influencer-scout',
|
|
90
|
+
'podcast-collaboration-scout',
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
const PUBLIC_SCOUT_MODES = new Set([
|
|
94
|
+
'micro-niche-mapper',
|
|
95
|
+
'niche-validation-scout',
|
|
96
|
+
'collaborator-scout',
|
|
97
|
+
'ideal-client-twin-scout',
|
|
98
|
+
'ideal-client-scout',
|
|
99
|
+
'niche-and-growth-scout',
|
|
100
|
+
'ideal-organization-and-partner-scout',
|
|
101
|
+
'ideal-person-and-influencer-scout',
|
|
102
|
+
'podcast-collaboration-scout',
|
|
103
|
+
]);
|
|
104
|
+
|
|
105
|
+
const MODE_DIRECTION = {
|
|
106
|
+
'micro-niche-mapper': 'Map a broad market into specific groups that share context and an urgent problem.',
|
|
107
|
+
'niche-validation-scout': 'Find public evidence that the niche problem is urgent, reachable, and valuable.',
|
|
108
|
+
'collaborator-scout': 'Find people or organizations with a complementary offer and an aligned audience.',
|
|
109
|
+
'ideal-client-twin-scout': 'Find public prospects that match approved nonconfidential attributes of a strong client.',
|
|
110
|
+
'ideal-client-scout': 'Find public prospects that match the coach niche and offer.',
|
|
111
|
+
'niche-and-growth-scout': 'Find niche groups and practical audience growth paths.',
|
|
112
|
+
'ideal-organization-and-partner-scout': 'Find organizations and partners with a strong group and problem match.',
|
|
113
|
+
'ideal-person-and-influencer-scout': 'Find people and influencers who reach the coach niche.',
|
|
114
|
+
'podcast-collaboration-scout': 'Find podcasts where a useful collaboration fits the audience problem.',
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const NICHE_EVIDENCE_REFERENCE = z.object({
|
|
118
|
+
id: z.string().min(1).max(80),
|
|
119
|
+
url: z.string().url().refine((value) => value.startsWith('https://'), 'Use one public HTTPS evidence URL.'),
|
|
120
|
+
observedAt: z.string().min(10).max(64),
|
|
121
|
+
summary: z.string().min(1).max(1000),
|
|
122
|
+
kind: z.enum(['direct', 'analogue', 'counterevidence']),
|
|
123
|
+
}).strict();
|
|
124
|
+
|
|
125
|
+
const NICHE_PREDICATE = z.object({
|
|
126
|
+
grade: z.union([z.literal(0), z.literal(0.5), z.literal(1)]),
|
|
127
|
+
evidenceIds: z.array(z.string().min(1).max(80)).max(40),
|
|
128
|
+
rationale: z.string().min(1).max(1000),
|
|
129
|
+
}).strict();
|
|
130
|
+
|
|
131
|
+
const NICHE_COUNTEREVIDENCE = z.object({
|
|
132
|
+
query: z.string().min(1).max(500),
|
|
133
|
+
checkedAt: z.string().min(10).max(64),
|
|
134
|
+
outcome: z.enum(['none_found', 'found', 'inconclusive']),
|
|
135
|
+
evidenceIds: z.array(z.string().min(1).max(80)).max(40),
|
|
136
|
+
}).strict();
|
|
137
|
+
|
|
138
|
+
const NICHE_FALSE_POSITIVE = z.object({
|
|
139
|
+
observed: z.boolean(),
|
|
140
|
+
evidenceIds: z.array(z.string().min(1).max(80)).max(40),
|
|
141
|
+
note: z.string().max(1000),
|
|
142
|
+
}).strict();
|
|
143
|
+
|
|
144
|
+
function nicheDimension(predicates) {
|
|
145
|
+
return z.object({
|
|
146
|
+
predicates: z.object(Object.fromEntries(
|
|
147
|
+
predicates.map((predicate) => [predicate, NICHE_PREDICATE]),
|
|
148
|
+
)).strict(),
|
|
149
|
+
counterevidenceChecks: z.array(NICHE_COUNTEREVIDENCE).min(1).max(20),
|
|
150
|
+
falsePositive: NICHE_FALSE_POSITIVE,
|
|
151
|
+
}).strict();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const NICHE_FIT_INPUT = z.object({
|
|
155
|
+
toolSpecId: z.literal('fbc.niche-fit.v1'),
|
|
156
|
+
asOf: z.string().min(10).max(64),
|
|
157
|
+
hypothesis: z.object({
|
|
158
|
+
proposedPodcastNiche: z.string().min(1).max(500),
|
|
159
|
+
strategy: z.enum(['smaller_group', 'specific_problem', 'both']),
|
|
160
|
+
specificGroup: z.string().min(1).max(500).optional(),
|
|
161
|
+
specificProblem: z.string().min(1).max(500).optional(),
|
|
162
|
+
collaboratorProfile: z.string().min(1).max(1000),
|
|
163
|
+
podcastWorkingTitle: z.string().min(1).max(500),
|
|
164
|
+
collaborationHook: z.string().min(1).max(1000),
|
|
165
|
+
valueExchangeOffer: z.string().min(1).max(1000),
|
|
166
|
+
geography: z.string().min(1).max(200).optional(),
|
|
167
|
+
language: z.string().min(1).max(100).optional(),
|
|
168
|
+
}).strict(),
|
|
169
|
+
evidence: z.array(NICHE_EVIDENCE_REFERENCE).min(1).max(120),
|
|
170
|
+
dimensions: z.object({
|
|
171
|
+
findability: nicheDimension([
|
|
172
|
+
'discoverable_collaborators',
|
|
173
|
+
'reachable_contact_path',
|
|
174
|
+
'compelling_collaboration_offer',
|
|
175
|
+
'weekly_yes_pace',
|
|
176
|
+
]),
|
|
177
|
+
bragability: nicheDimension([
|
|
178
|
+
'show_up_likelihood',
|
|
179
|
+
'compelling_story_hook',
|
|
180
|
+
'positive_status_association',
|
|
181
|
+
'share_behavior',
|
|
182
|
+
]),
|
|
183
|
+
sellability: nicheDimension([
|
|
184
|
+
'reachable_owned_audience',
|
|
185
|
+
'specific_value_exchange',
|
|
186
|
+
'offer_through_collaboration',
|
|
187
|
+
'audience_response',
|
|
188
|
+
]),
|
|
189
|
+
}).strict(),
|
|
190
|
+
}).strict();
|
|
191
|
+
|
|
192
|
+
function routeMissing(result) {
|
|
193
|
+
return result.status === 404 || result.status === 405 || result.status === 501;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const COACH_NOT_READY =
|
|
197
|
+
'The Fully Booked Coach managed routes are not deployed for this workspace yet. Nothing changed. Use draft mode and ask the operator to finish the coach migration and API deployment.';
|
|
198
|
+
const COACH_BOOK_UNSAFE =
|
|
199
|
+
'KMHub rejected an unsafe Book OS response. Nothing changed.';
|
|
200
|
+
|
|
201
|
+
function coachOut(result, out, text) {
|
|
202
|
+
return routeMissing(result) ? text(COACH_NOT_READY, true) : out(result);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function coachBookOut(result, kind, out, text) {
|
|
206
|
+
if (routeMissing(result)) return text(COACH_NOT_READY, true);
|
|
207
|
+
try {
|
|
208
|
+
return out(projectCoachBookConnectorResult(kind, result));
|
|
209
|
+
} catch {
|
|
210
|
+
return text(COACH_BOOK_UNSAFE, true);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function register(server, call, { out, text, qs }) {
|
|
215
|
+
server.tool(
|
|
216
|
+
'km_coach_context',
|
|
217
|
+
'Load the connected Fully Booked Coach workspace, method stage, capacity boundaries, privacy defaults, record counts, and tenant identity. Call this first in a coach conversation. It reads only and never changes anything.',
|
|
218
|
+
{},
|
|
219
|
+
async () => coachOut(await call('GET', '/coach/context'), out, text),
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
server.tool(
|
|
223
|
+
'km_coach_method',
|
|
224
|
+
'Load verified Fully Booked Coach guidance for one exact workflow. Use after workspace context and before giving method-based advice or preparing a related write. It returns approved guidance metadata only, never protected source text, and fails closed when the private tenant cassette is not ready.',
|
|
225
|
+
{
|
|
226
|
+
workflow: z.enum(COACH_METHOD_WORKFLOWS).describe('The exact Fully Booked Coach workflow that grounds this request.'),
|
|
227
|
+
},
|
|
228
|
+
async ({ workflow }) => coachOut(
|
|
229
|
+
await call('GET', `/coach/method${qs({ workflow })}`),
|
|
230
|
+
out,
|
|
231
|
+
text,
|
|
232
|
+
),
|
|
233
|
+
);
|
|
234
|
+
server.tool(
|
|
235
|
+
'km_coach_ask_book',
|
|
236
|
+
'Ask the Book is intentionally closed by product decision. This compatibility tool performs no retrieval, provider call, or charge. Use the method catalog to find an available workflow.',
|
|
237
|
+
{
|
|
238
|
+
question: z.string().min(4).max(1200).describe('Legacy compatibility field. Ask the Book remains closed and does not process this question.'),
|
|
239
|
+
idempotency_key: z.string().min(1).max(120).regex(/^[A-Za-z0-9][A-Za-z0-9._:]{0,119}$/).describe('Legacy compatibility field. No run is created.'),
|
|
240
|
+
quote_id: z.string().uuid().optional().describe('Legacy compatibility field. No quote is used.'),
|
|
241
|
+
confirm_token: z.string().min(1).max(4096).optional().describe('Legacy compatibility field. No confirmation is consumed.'),
|
|
242
|
+
},
|
|
243
|
+
async () => text(
|
|
244
|
+
'Ask the Book remains intentionally closed by product decision. No retrieval ran, no provider was called, and nothing was charged. Use km_coach_book_catalog to find a method and km_coach_book_tool to start, advance, resume, or cancel a run.',
|
|
245
|
+
true,
|
|
246
|
+
),
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
server.tool(
|
|
250
|
+
'km_coach_book_catalog',
|
|
251
|
+
[
|
|
252
|
+
'List, search, or describe the public Fully Booked Coach method library.',
|
|
253
|
+
'Catalog and search read the managed method catalog. Describe reads one exact method id.',
|
|
254
|
+
'This tool is read-only and returns public method metadata, source page locators, availability, and public input steps.',
|
|
255
|
+
].join(' '),
|
|
256
|
+
{
|
|
257
|
+
action: z.enum(['catalog', 'search', 'describe']).describe('Use catalog for the full library, search for a local public-metadata match, or describe for one exact method.'),
|
|
258
|
+
query: z.string().trim().min(2).max(160).optional().describe('Required only for search. Matches public ids, titles, summaries, categories, executor kinds, and statuses.'),
|
|
259
|
+
tool_spec_id: z.string().regex(/^fbc[.]method[.][0-9]{3}[.][a-z0-9]+(?:-[a-z0-9]+)*$/).optional().describe('Required only for describe. Use the exact id returned by catalog or search.'),
|
|
260
|
+
},
|
|
261
|
+
async ({ action, query, tool_spec_id }) => {
|
|
262
|
+
if (action === 'describe') {
|
|
263
|
+
if (!tool_spec_id || query) {
|
|
264
|
+
return text('Describe requires only one exact tool_spec_id. Nothing changed.', true);
|
|
265
|
+
}
|
|
266
|
+
return coachBookOut(
|
|
267
|
+
await call('GET', `/coach/book/methods/${encodeURIComponent(tool_spec_id)}`),
|
|
268
|
+
'describe',
|
|
269
|
+
out,
|
|
270
|
+
text,
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
if (tool_spec_id) {
|
|
274
|
+
return text('Catalog and search do not accept tool_spec_id. Nothing changed.', true);
|
|
275
|
+
}
|
|
276
|
+
if (action === 'catalog' && query) {
|
|
277
|
+
return text('Catalog does not accept a search query. Use the search action instead.', true);
|
|
278
|
+
}
|
|
279
|
+
if (action === 'search' && !query) {
|
|
280
|
+
return text('Search requires a query. Nothing changed.', true);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const result = await call('GET', '/coach/book/methods');
|
|
284
|
+
if (routeMissing(result)) return text(COACH_NOT_READY, true);
|
|
285
|
+
let safeResult;
|
|
286
|
+
try {
|
|
287
|
+
safeResult = projectCoachBookConnectorResult('catalog', result);
|
|
288
|
+
} catch {
|
|
289
|
+
return text(COACH_BOOK_UNSAFE, true);
|
|
290
|
+
}
|
|
291
|
+
if (!safeResult.ok || action === 'catalog') return out(safeResult);
|
|
292
|
+
|
|
293
|
+
const data = safeResult.data && typeof safeResult.data === 'object' && !Array.isArray(safeResult.data)
|
|
294
|
+
? safeResult.data
|
|
295
|
+
: {};
|
|
296
|
+
const methods = Array.isArray(data.methods) ? data.methods : [];
|
|
297
|
+
const needle = query.trim().toLowerCase();
|
|
298
|
+
const matches = methods.filter((method) => {
|
|
299
|
+
if (!method || typeof method !== 'object' || Array.isArray(method)) return false;
|
|
300
|
+
const publicSpec = method.public_spec && typeof method.public_spec === 'object'
|
|
301
|
+
&& !Array.isArray(method.public_spec)
|
|
302
|
+
? method.public_spec
|
|
303
|
+
: {};
|
|
304
|
+
return [
|
|
305
|
+
method.tool_spec_id,
|
|
306
|
+
method.title,
|
|
307
|
+
method.summary,
|
|
308
|
+
method.category,
|
|
309
|
+
method.implementation_status,
|
|
310
|
+
method.executor_kind,
|
|
311
|
+
method.effect_class,
|
|
312
|
+
method.availability_class,
|
|
313
|
+
publicSpec.title,
|
|
314
|
+
publicSpec.summary,
|
|
315
|
+
publicSpec.category,
|
|
316
|
+
publicSpec.executorKind,
|
|
317
|
+
publicSpec.effectClass,
|
|
318
|
+
].filter((value) => typeof value === 'string').join(' ').toLowerCase().includes(needle);
|
|
319
|
+
});
|
|
320
|
+
return out({
|
|
321
|
+
...safeResult,
|
|
322
|
+
data: {
|
|
323
|
+
...data,
|
|
324
|
+
methods: matches,
|
|
325
|
+
search: {
|
|
326
|
+
query: query.trim(),
|
|
327
|
+
matched: matches.length,
|
|
328
|
+
total: methods.length,
|
|
329
|
+
},
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
},
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
server.tool(
|
|
336
|
+
'km_coach_book_recommend',
|
|
337
|
+
[
|
|
338
|
+
'Recommend up to three public Fully Booked Coach methods for one coaching goal.',
|
|
339
|
+
'This read-only tool uses the deterministic KMHub ranker and performs no source retrieval, no provider call, no write, no charge, and no external effect.',
|
|
340
|
+
'It never exposes protected source text, source code, or skill files. Ask the Book remains closed.',
|
|
341
|
+
].join(' '),
|
|
342
|
+
{
|
|
343
|
+
goal: z.string().trim().min(4).max(600).describe('Describe the coaching outcome or problem in plain language.'),
|
|
344
|
+
availability_scope: z.enum(['runnable_only', 'include_pending']).optional().describe('Use runnable_only by default, or include pending methods for planning visibility.'),
|
|
345
|
+
max_minutes: z.number().int().min(5).max(240).optional().describe('Optional maximum estimated method duration.'),
|
|
346
|
+
executor_kind: z.enum(['deterministic_single_step', 'guided_multi_step']).optional().describe('Optional execution-style filter.'),
|
|
347
|
+
effect_class: z.enum(['none', 'action_preview']).optional().describe('Optional effect boundary filter.'),
|
|
348
|
+
},
|
|
349
|
+
async ({
|
|
350
|
+
goal,
|
|
351
|
+
availability_scope,
|
|
352
|
+
max_minutes,
|
|
353
|
+
executor_kind,
|
|
354
|
+
effect_class,
|
|
355
|
+
}) => coachBookOut(
|
|
356
|
+
await call('GET', `/coach/book/methods/recommend${qs({
|
|
357
|
+
goal,
|
|
358
|
+
availability_scope,
|
|
359
|
+
max_minutes,
|
|
360
|
+
executor_kind,
|
|
361
|
+
effect_class,
|
|
362
|
+
})}`),
|
|
363
|
+
'recommendation',
|
|
364
|
+
out,
|
|
365
|
+
text,
|
|
366
|
+
),
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
server.tool(
|
|
370
|
+
'km_coach_book_tool',
|
|
371
|
+
[
|
|
372
|
+
'Start, advance, resume, or cancel one generic Fully Booked Coach method run.',
|
|
373
|
+
'Start, advance, and cancel are confirmed zero-cost writes. Show any 409 confirmation sentence and wait for an explicit yes before retrying once with the exact returned confirm_token.',
|
|
374
|
+
'Never invent or self-confirm a token. Resume is a read-only run lookup and accepts only action plus run_id.',
|
|
375
|
+
'This tool creates only bounded method artifacts and action previews. It cannot contact, publish, spend, or perform an external connector action.',
|
|
376
|
+
].join(' '),
|
|
377
|
+
{
|
|
378
|
+
action: z.enum(['start', 'advance', 'resume', 'cancel']),
|
|
379
|
+
tool_spec_id: z.string().regex(/^fbc[.]method[.][0-9]{3}[.][a-z0-9]+(?:-[a-z0-9]+)*$/).optional().describe('Required only for start. Use the exact method id from the catalog.'),
|
|
380
|
+
run_id: z.string().uuid().optional().describe('Required for advance, resume, and cancel.'),
|
|
381
|
+
step_id: z.string().min(1).max(80).regex(/^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$/).optional().describe('Required for start and advance. Use the exact current public step id.'),
|
|
382
|
+
values: z.record(z.unknown()).optional().describe('Required for start and advance. Send one bounded object containing only the current public step fields.'),
|
|
383
|
+
idempotency_key: z.string().min(1).max(96).regex(/^[A-Za-z0-9][A-Za-z0-9:._-]{0,95}$/).optional().describe('Required for start, advance, and cancel. Reuse it for an exact confirmation retry.'),
|
|
384
|
+
confirm_token: z.string().min(1).max(4096).optional().describe('Use only the exact token returned in a 409 response after the coach explicitly approves the displayed confirmation sentence.'),
|
|
385
|
+
},
|
|
386
|
+
async ({
|
|
387
|
+
action,
|
|
388
|
+
tool_spec_id,
|
|
389
|
+
run_id,
|
|
390
|
+
step_id,
|
|
391
|
+
values,
|
|
392
|
+
idempotency_key,
|
|
393
|
+
confirm_token,
|
|
394
|
+
}) => {
|
|
395
|
+
if (action === 'resume') {
|
|
396
|
+
if (!run_id || tool_spec_id || step_id || values || idempotency_key || confirm_token) {
|
|
397
|
+
return text('Resume accepts only action and run_id. Nothing changed.', true);
|
|
398
|
+
}
|
|
399
|
+
return coachBookOut(
|
|
400
|
+
await call('GET', `/coach/book/runs/${encodeURIComponent(run_id)}`),
|
|
401
|
+
'run',
|
|
402
|
+
out,
|
|
403
|
+
text,
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (action === 'start') {
|
|
408
|
+
if (!tool_spec_id || run_id || !step_id || !values || !idempotency_key) {
|
|
409
|
+
return text('Start requires tool_spec_id, step_id, values, and idempotency_key, with no run_id. Nothing changed.', true);
|
|
410
|
+
}
|
|
411
|
+
return coachBookOut(await call(
|
|
412
|
+
'POST',
|
|
413
|
+
`/coach/book/methods/${encodeURIComponent(tool_spec_id)}/runs`,
|
|
414
|
+
{
|
|
415
|
+
tool_spec_id,
|
|
416
|
+
step_id,
|
|
417
|
+
values,
|
|
418
|
+
idempotency_key,
|
|
419
|
+
...(confirm_token ? { confirm_token } : {}),
|
|
420
|
+
},
|
|
421
|
+
), 'run', out, text);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (action === 'advance') {
|
|
425
|
+
if (!run_id || tool_spec_id || !step_id || !values || !idempotency_key) {
|
|
426
|
+
return text('Advance requires run_id, step_id, values, and idempotency_key, with no tool_spec_id. Nothing changed.', true);
|
|
427
|
+
}
|
|
428
|
+
return coachBookOut(await call(
|
|
429
|
+
'POST',
|
|
430
|
+
`/coach/book/runs/${encodeURIComponent(run_id)}/steps`,
|
|
431
|
+
{
|
|
432
|
+
step_id,
|
|
433
|
+
values,
|
|
434
|
+
idempotency_key,
|
|
435
|
+
...(confirm_token ? { confirm_token } : {}),
|
|
436
|
+
},
|
|
437
|
+
), 'run', out, text);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (!run_id || tool_spec_id || step_id || values || !idempotency_key) {
|
|
441
|
+
return text('Cancel requires only run_id and idempotency_key, plus an explicit confirm_token retry when KMHub requests it. Nothing changed.', true);
|
|
442
|
+
}
|
|
443
|
+
return coachBookOut(await call(
|
|
444
|
+
'POST',
|
|
445
|
+
`/coach/book/runs/${encodeURIComponent(run_id)}/cancel`,
|
|
446
|
+
{
|
|
447
|
+
idempotency_key,
|
|
448
|
+
...(confirm_token ? { confirm_token } : {}),
|
|
449
|
+
},
|
|
450
|
+
), 'run', out, text);
|
|
451
|
+
},
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
server.tool(
|
|
455
|
+
'km_coach_daily_operator',
|
|
456
|
+
'Build today\'s operating view from client sessions, commitments, stalled opportunities, approvals, Daily Cookie work, and capacity. Use for what needs me today or a morning review. It reads only and never sends, schedules, or publishes.',
|
|
457
|
+
{},
|
|
458
|
+
async () => coachOut(await call('GET', '/coach/daily-operator'), out, text),
|
|
459
|
+
);
|
|
460
|
+
|
|
461
|
+
server.tool(
|
|
462
|
+
'km_coach_list_clients',
|
|
463
|
+
'List recent clients in this coach workspace so a named client can be resolved before reading or writing a Client MAP, session, commitment, outcome, or confidential note. It never searches another tenant.',
|
|
464
|
+
{
|
|
465
|
+
limit: z.number().int().min(1).max(100).optional().describe('How many clients to return. Default 25.'),
|
|
466
|
+
},
|
|
467
|
+
async ({ limit }) => coachOut(await call('GET', `/coach/clients${qs({ limit: limit ?? 25 })}`), out, text),
|
|
468
|
+
);
|
|
469
|
+
|
|
470
|
+
server.tool(
|
|
471
|
+
'km_coach_create_client',
|
|
472
|
+
'Create one client in the connected coach workspace after confirming the person or company details. Use a stable idempotency key for this exact request. This creates only the client record. It does not create a Client MAP, engagement, invoice, message, or external action.',
|
|
473
|
+
{
|
|
474
|
+
client_type: z.enum(['individual', 'company']).optional().describe('Default individual.'),
|
|
475
|
+
first_name: z.string().min(1).max(200).describe('Required display or contact first name.'),
|
|
476
|
+
last_name: z.string().max(200).optional(),
|
|
477
|
+
company_name: z.string().max(300).optional(),
|
|
478
|
+
email: z.string().email().max(320).optional(),
|
|
479
|
+
phone: z.string().max(100).optional(),
|
|
480
|
+
notes: z.string().max(4000).optional(),
|
|
481
|
+
idempotency_key: z.string().min(1).max(200).describe('Stable key for this exact client create request.'),
|
|
482
|
+
},
|
|
483
|
+
async (args) => coachOut(await call('POST', '/coach/clients', args), out, text),
|
|
484
|
+
);
|
|
485
|
+
server.tool(
|
|
486
|
+
'km_coach_list_records',
|
|
487
|
+
'Read shared coach records from KMHub, optionally filtered by object type, exact client, or lifecycle state. Use for Client MAPs, sessions, commitments, outcomes, opportunities, consent, content, or operations. Local only Booked records are never available here.',
|
|
488
|
+
{
|
|
489
|
+
object_type: z.enum(COACH_OBJECT_TYPES).optional().describe('One fully_booked_coach.v1 object type.'),
|
|
490
|
+
client_id: z.string().uuid().optional().describe('Exact KMHub client UUID.'),
|
|
491
|
+
state: z.string().regex(/^[a-z][a-z0-9_]{0,63}$/).optional().describe('Lifecycle state such as active, planned, or submitted.'),
|
|
492
|
+
limit: z.number().int().min(1).max(200).optional().describe('How many records to return. Default 50.'),
|
|
493
|
+
},
|
|
494
|
+
async ({ object_type, client_id, state, limit }) =>
|
|
495
|
+
coachOut(await call('GET', `/coach/records${qs({ object_type, client_id, state, limit: limit ?? 50 })}`), out, text),
|
|
496
|
+
);
|
|
497
|
+
|
|
498
|
+
server.tool(
|
|
499
|
+
'km_coach_create_client_map',
|
|
500
|
+
'Create the first Client MAP for one exact existing coach client. Resolve the client, call km_coach_method for client_map, confirm the complete MAP with the coach, and use this only when no current Client MAP exists. A later change must use km_coach_revise_record.',
|
|
501
|
+
{
|
|
502
|
+
client_id: z.string().uuid().describe('Exact KMHub client UUID.'),
|
|
503
|
+
destination: z.string().min(1).max(4000),
|
|
504
|
+
current_state: z.string().min(1).max(4000),
|
|
505
|
+
obstacles: z.array(z.string().min(1).max(1000)).max(50).optional(),
|
|
506
|
+
measures: z.array(z.object({
|
|
507
|
+
id: z.string().min(1).max(200),
|
|
508
|
+
name: z.string().min(1).max(500),
|
|
509
|
+
baseline: z.union([z.string().min(1).max(1000), z.number(), z.null()]).optional(),
|
|
510
|
+
target: z.union([z.string().min(1).max(1000), z.number(), z.null()]).optional(),
|
|
511
|
+
actual: z.union([z.string().min(1).max(1000), z.number(), z.null()]).optional(),
|
|
512
|
+
evidence_ref: z.union([z.string().min(1).max(1000), z.number(), z.null()]).optional(),
|
|
513
|
+
}).strict()).max(50).optional(),
|
|
514
|
+
route: z.array(z.string().min(1).max(1000)).max(50).optional(),
|
|
515
|
+
permissions: z.array(z.string().min(1).max(500)).max(50).optional(),
|
|
516
|
+
change_source: z.string().max(200).optional(),
|
|
517
|
+
idempotency_key: z.string().min(1).max(200).describe('Stable key for this exact first MAP request.'),
|
|
518
|
+
},
|
|
519
|
+
async (args) => coachOut(await call('POST', '/coach/client-maps', args), out, text),
|
|
520
|
+
);
|
|
521
|
+
|
|
522
|
+
server.tool(
|
|
523
|
+
'km_coach_revise_record',
|
|
524
|
+
'Create one immutable successor for an exact current coach record. Read the current record first, keep the complete replacement body inside the same client and privacy boundary, and use one stable idempotency key. The server refuses protected object types and records that already have a successor.',
|
|
525
|
+
{
|
|
526
|
+
record_id: z.string().uuid().describe('Exact current coach record UUID.'),
|
|
527
|
+
body: z.record(z.unknown()).describe('Complete replacement body. Never include a credential, token, API key, or password.'),
|
|
528
|
+
lifecycle_state: z.string().regex(/^[a-z][a-z0-9_]{0,63}$/).optional(),
|
|
529
|
+
idempotency_key: z.string().min(1).max(200).describe('Stable key for this exact revision.'),
|
|
530
|
+
},
|
|
531
|
+
async ({ record_id, ...body }) => coachOut(
|
|
532
|
+
await call('POST', `/coach/records/${encodeURIComponent(record_id)}/revisions`, body),
|
|
533
|
+
out,
|
|
534
|
+
text,
|
|
535
|
+
),
|
|
536
|
+
);
|
|
537
|
+
server.tool(
|
|
538
|
+
'km_coach_write_record',
|
|
539
|
+
'Create one idempotent shared Fully Booked Coach record in KMHub. Use for new operational facts, sessions, commitments, outcomes, opportunities, or content drafts. Client MAP creation and record revisions require KMHub dedicated reviewed workflows. Consent, approval, and publication records require dedicated human-reviewed KMHub workflows. Do not use for local only notes or credential values. Reusing the same idempotency key returns the original record.',
|
|
540
|
+
{
|
|
541
|
+
object_type: z.enum(COACH_WRITABLE_OBJECT_TYPES).describe('A safe draft or operational fully_booked_coach.v1 object type. Consent, approval, publication, audit, and execution records require dedicated human-reviewed routes.'),
|
|
542
|
+
idempotency_key: z.string().min(1).max(200).describe('A stable key for this exact create request.'),
|
|
543
|
+
client_id: z.string().uuid().optional().describe('Required for client scoped delivery records.'),
|
|
544
|
+
privacy_scope: z.enum(['business', 'client_shared', 'client_confidential']).optional().describe('Privacy boundary. Default business.'),
|
|
545
|
+
lifecycle_state: z.string().regex(/^[a-z][a-z0-9_]{0,63}$/).optional().describe('Validated lifecycle state when this object has one.'),
|
|
546
|
+
body: z.record(z.unknown()).optional().describe('Object fields. Never include a credential, token, API key, or password.'),
|
|
547
|
+
|
|
548
|
+
},
|
|
549
|
+
async (args) => coachOut(await call('POST', '/coach/records', args), out, text),
|
|
550
|
+
);
|
|
551
|
+
|
|
552
|
+
server.tool(
|
|
553
|
+
'km_coach_stalled_opportunities',
|
|
554
|
+
'Find open coach opportunities with no current next action or an overdue next action. Use before drafting a stalled prospect message. It reads only and does not contact anyone.',
|
|
555
|
+
{},
|
|
556
|
+
async () => coachOut(await call('GET', '/coach/stalled-opportunities'), out, text),
|
|
557
|
+
);
|
|
558
|
+
|
|
559
|
+
server.tool(
|
|
560
|
+
'km_coach_preview_action',
|
|
561
|
+
'Record the complete preview for one outbound or sensitive coach action. The preview includes target, content, channel, and expected effect. This has no external side effect and always returns approval required. There is deliberately no send tool in the pilot coach profile.',
|
|
562
|
+
{
|
|
563
|
+
target: z.string().min(1).max(500).describe('Exact recipient or destination.'),
|
|
564
|
+
content: z.string().min(1).max(10000).describe('The complete content that would be used.'),
|
|
565
|
+
channel: z.string().min(1).max(80).describe('Channel such as email, LinkedIn, or Daily Cookie review.'),
|
|
566
|
+
expected_effect: z.string().min(1).max(1000).describe('What the action is intended to change.'),
|
|
567
|
+
risk: z.enum(['internal', 'external', 'money', 'sensitive']).optional().describe('Default external.'),
|
|
568
|
+
action_class: z.string().max(120).optional().describe('Stable action class such as prospect_follow_up.'),
|
|
569
|
+
target_scope: z.string().max(200).optional().describe('The precise action boundary.'),
|
|
570
|
+
client_id: z.string().uuid().optional().describe('Exact client when the action is client scoped.'),
|
|
571
|
+
privacy_scope: z.enum(['business', 'client_shared', 'client_confidential']).optional(),
|
|
572
|
+
idempotency_key: z.string().min(1).max(200).describe('Stable key for this exact preview.'),
|
|
573
|
+
},
|
|
574
|
+
async (args) => coachOut(await call('POST', '/coach/action-previews', args), out, text),
|
|
575
|
+
);
|
|
576
|
+
|
|
577
|
+
server.tool(
|
|
578
|
+
'km_coach_decide_action',
|
|
579
|
+
'Record one human approve or reject decision for an exact current ActionRequest. This creates immutable decision evidence only. It never sends, schedules, charges, publishes, or executes the approved action. First show the stored preview and the KMHub confirmation sentence, then retry unchanged with the returned confirm_token after the coach explicitly agrees.',
|
|
580
|
+
{
|
|
581
|
+
action_request_id: z.string().uuid().describe('Exact current ActionRequest UUID.'),
|
|
582
|
+
decision: z.enum(['approve', 'reject']),
|
|
583
|
+
idempotency_key: z.string().min(1).max(200).describe('Stable key for this exact decision.'),
|
|
584
|
+
note: z.string().max(2000).optional(),
|
|
585
|
+
confirm_token: z.string().optional().describe('Use only after the coach explicitly approves the exact decision confirmation sentence returned by KMHub.'),
|
|
586
|
+
},
|
|
587
|
+
async ({ action_request_id, ...body }) => coachOut(
|
|
588
|
+
await call('POST', `/coach/action-requests/${encodeURIComponent(action_request_id)}/decision`, body),
|
|
589
|
+
out,
|
|
590
|
+
text,
|
|
591
|
+
),
|
|
592
|
+
);
|
|
593
|
+
server.tool(
|
|
594
|
+
'km_coach_submit_daily_cookie',
|
|
595
|
+
'Record a Daily Cookie brief using one book category: problem, dream, process, or proof. This connector step does not start paid production. The coach reviews scope and starts the managed production queue in KMHub Growth. Direct publishing stays disabled. Every client-linked request requires an active content reuse consent grant.',
|
|
596
|
+
{
|
|
597
|
+
category: z.enum(['problem', 'dream', 'process', 'proof']),
|
|
598
|
+
source_scope: z.enum(['business_owned', 'client_linked']).describe('Use business_owned only when no client material is involved. Use client_linked with client_id and consent_grant_id.'),
|
|
599
|
+
audience: z.string().min(1).max(500),
|
|
600
|
+
useful_idea: z.string().min(1).max(4000),
|
|
601
|
+
channel: z.string().min(1).max(80),
|
|
602
|
+
objective: z.string().max(1000).optional(),
|
|
603
|
+
due_at: z.string().max(80).optional(),
|
|
604
|
+
notes: z.string().max(4000).optional(),
|
|
605
|
+
client_id: z.string().uuid().optional(),
|
|
606
|
+
consent_grant_id: z.string().uuid().optional(),
|
|
607
|
+
idempotency_key: z.string().min(1).max(200),
|
|
608
|
+
},
|
|
609
|
+
async (args) => coachOut(await call('POST', '/coach/daily-cookie', args), out, text),
|
|
610
|
+
);
|
|
611
|
+
|
|
612
|
+
server.tool(
|
|
613
|
+
'km_coach_run_scout',
|
|
614
|
+
[
|
|
615
|
+
'Start one public or coach connected acquisition scout using only coach modes.',
|
|
616
|
+
'Past client opportunity and referral path are owned record reviews, so this tool refuses those two and directs the model to KMHub records.',
|
|
617
|
+
'Public provider work can cost money. Call first without confirm_spend to use the read-only price route. Run only after the coach explicitly agrees and call again with confirm_spend true.',
|
|
618
|
+
'KMHub may also return a confirmation sentence and confirm_token. Show the sentence, get an explicit yes, and retry the same arguments with that token.',
|
|
619
|
+
'Every successful coach run stores normalized ScoutFinding records with public source evidence. It creates no generic leads, deals, or outbound drafts. Read those findings after the run and do not record them a second time.',
|
|
620
|
+
].join(' '),
|
|
621
|
+
{
|
|
622
|
+
mode: z.enum(SCOUT_MODES),
|
|
623
|
+
who_to_find: z.string().min(1).max(2000).describe('The group and problem criteria in the coach\'s words.'),
|
|
624
|
+
area: z.string().max(200).optional(),
|
|
625
|
+
target_count: z.number().int().min(1).max(100).optional(),
|
|
626
|
+
outreach_context: z.string().max(1000).optional().describe('The real coach offer and useful reason to connect.'),
|
|
627
|
+
idempotency_key: z.string().min(1).max(170).regex(/^[A-Za-z0-9][A-Za-z0-9:._-]{0,169}$/).describe('Stable key for this exact Scout intent. Reuse it for the price, confirmation, and any retry.'),
|
|
628
|
+
confirm_spend: z.boolean().optional().describe('Leave out for the free price preview. Set true only after explicit agreement.'),
|
|
629
|
+
confirm_token: z.string().optional().describe('Use only the token KMHub returned for these exact arguments after the coach explicitly approved its confirmation sentence.'),
|
|
630
|
+
},
|
|
631
|
+
async ({ mode, who_to_find, area, target_count, outreach_context, idempotency_key, confirm_spend, confirm_token }) => {
|
|
632
|
+
if (!PUBLIC_SCOUT_MODES.has(mode)) {
|
|
633
|
+
return text(
|
|
634
|
+
`${mode} uses owned KMHub relationships and client history. Use km_coach_list_records instead. Nothing ran and nothing was charged.`,
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
const direction = MODE_DIRECTION[mode] || 'Find coach acquisition opportunities with public evidence.';
|
|
638
|
+
const endpoint = confirm_spend === true ? '/sourcing/missions' : '/sourcing/missions/estimate';
|
|
639
|
+
return out(await call('POST', endpoint, {
|
|
640
|
+
describe: `Fully Booked Coach scout: ${direction} Coach criteria: ${who_to_find}`,
|
|
641
|
+
area,
|
|
642
|
+
target_count: target_count ?? 25,
|
|
643
|
+
outreach_context,
|
|
644
|
+
idempotency_key,
|
|
645
|
+
vertical_flavor: 'business_coach',
|
|
646
|
+
coach_scout_mode: mode,
|
|
647
|
+
deep_research: true,
|
|
648
|
+
ai_copywriting: false,
|
|
649
|
+
confirm_spend,
|
|
650
|
+
confirm_token,
|
|
651
|
+
}));
|
|
652
|
+
},
|
|
653
|
+
);
|
|
654
|
+
|
|
655
|
+
server.tool(
|
|
656
|
+
'km_coach_list_scouts',
|
|
657
|
+
'List recent provider scout runs for this workspace. This reads status only, costs nothing, and starts nothing.',
|
|
658
|
+
{
|
|
659
|
+
limit: z.number().int().min(1).max(50).optional(),
|
|
660
|
+
status: z.enum(['queued', 'running', 'succeeded', 'failed', 'canceled']).optional(),
|
|
661
|
+
},
|
|
662
|
+
async ({ limit, status }) => out(await call('GET', `/sourcing/missions${qs({ limit: limit ?? 10, status })}`)),
|
|
663
|
+
);
|
|
664
|
+
|
|
665
|
+
server.tool(
|
|
666
|
+
'km_coach_scout_status',
|
|
667
|
+
'Check one provider scout run and return current progress or results. This reads only and costs nothing.',
|
|
668
|
+
{
|
|
669
|
+
mission_id: z.string().uuid(),
|
|
670
|
+
},
|
|
671
|
+
async ({ mission_id }) => out(await call('GET', `/sourcing/missions/${encodeURIComponent(mission_id)}`)),
|
|
672
|
+
);
|
|
673
|
+
|
|
674
|
+
server.tool(
|
|
675
|
+
'km_coach_stop_scout',
|
|
676
|
+
'Stop a queued or running provider scout. Use immediately when the coach says stop or the criteria are wrong. Work already completed remains, and no future provider work should continue.',
|
|
677
|
+
{
|
|
678
|
+
mission_id: z.string().uuid(),
|
|
679
|
+
confirm_token: z.string().optional().describe('Use only the token KMHub returned after the coach explicitly approved stopping this exact scout.'),
|
|
680
|
+
},
|
|
681
|
+
async ({ mission_id, confirm_token }) => out(await call('POST', `/sourcing/missions/${encodeURIComponent(mission_id)}/cancel`, { confirm_token })),
|
|
682
|
+
);
|
|
683
|
+
|
|
684
|
+
server.tool(
|
|
685
|
+
'km_coach_record_scout_finding',
|
|
686
|
+
'Record one verified coach scout finding after deduplication. Every finding requires source evidence, group match, problem evidence, rationale, uncertainty, a duplicate key, readiness, and a safe next step. It does not contact anyone.',
|
|
687
|
+
{
|
|
688
|
+
mode: z.enum([...PUBLIC_SCOUT_MODES]),
|
|
689
|
+
source_evidence: z.array(z.object({
|
|
690
|
+
url: z.string().url(),
|
|
691
|
+
observed_at: z.string().datetime(),
|
|
692
|
+
summary: z.string().min(1).max(2000),
|
|
693
|
+
})).min(1),
|
|
694
|
+
group_match: z.string().min(1).max(2000),
|
|
695
|
+
problem_evidence: z.string().min(1).max(4000),
|
|
696
|
+
rationale: z.string().min(1).max(4000),
|
|
697
|
+
uncertainty: z.string().min(1).max(2000),
|
|
698
|
+
duplicate_key: z.string().min(1).max(300),
|
|
699
|
+
safe_next_step: z.string().min(1).max(2000),
|
|
700
|
+
readiness: z.enum(['research_only', 'relationship_opportunity', 'outreach_ready']),
|
|
701
|
+
idempotency_key: z.string().min(1).max(200),
|
|
702
|
+
},
|
|
703
|
+
async ({ idempotency_key, ...finding }) =>
|
|
704
|
+
coachOut(await call('POST', '/coach/records', {
|
|
705
|
+
object_type: 'ScoutFinding',
|
|
706
|
+
idempotency_key,
|
|
707
|
+
privacy_scope: 'business',
|
|
708
|
+
lifecycle_state: finding.readiness,
|
|
709
|
+
body: finding,
|
|
710
|
+
}), out, text),
|
|
711
|
+
);
|
|
712
|
+
server.tool(
|
|
713
|
+
'km_coach_promote_scout_finding',
|
|
714
|
+
'Promote one exact current reviewed public ScoutFinding into one business relationship and one opportunity. Use only after the coach selects the automatically stored finding and confirms the editable prospect fields. This creates no client, message, outbound request, charge, or external action. Exact retries return the original promotion.',
|
|
715
|
+
{
|
|
716
|
+
scout_finding_id: z.string().uuid().describe('Exact current ScoutFinding UUID.'),
|
|
717
|
+
name: z.string().min(1).max(300).describe('Reviewed relationship and opportunity name.'),
|
|
718
|
+
context: z.string().max(2000).optional(),
|
|
719
|
+
source_label: z.string().max(300).optional(),
|
|
720
|
+
next_action: z.string().max(1000).optional(),
|
|
721
|
+
next_action_due_at: z.string().datetime().max(80).optional(),
|
|
722
|
+
state: z.enum(['identified', 'qualified', 'conversation', 'strategy_session', 'proposed', 'nurture']).optional(),
|
|
723
|
+
idempotency_key: z.string().min(1).max(170).regex(/^[A-Za-z0-9][A-Za-z0-9:._-]{0,169}$/).describe('Stable key for this exact finding promotion.'),
|
|
724
|
+
},
|
|
725
|
+
async ({ scout_finding_id, ...body }) => coachOut(
|
|
726
|
+
await call('POST', `/coach/scout-findings/${encodeURIComponent(scout_finding_id)}/promote`, body),
|
|
727
|
+
out,
|
|
728
|
+
text,
|
|
729
|
+
),
|
|
730
|
+
);
|
|
731
|
+
server.tool(
|
|
732
|
+
'km_coach_niche_fit',
|
|
733
|
+
[
|
|
734
|
+
'Run or resume the source-locked Podcast Niche Selector from Fully Booked Coach PDF pages 115 to 125.',
|
|
735
|
+
'A new run is deterministic, uses no provider, costs $0, sends nothing, publishes nothing, and requires a confirmation before saving the artifact and human decision.',
|
|
736
|
+
'Use run_id alone to resume the identical run created in native web or Claude. Never recreate a run from memory when a run id is available.',
|
|
737
|
+
].join(' '),
|
|
738
|
+
{
|
|
739
|
+
input: NICHE_FIT_INPUT.optional().describe('The complete evidence-backed scorecard for a new run.'),
|
|
740
|
+
run_id: z.string().uuid().optional().describe('An existing Book OS run to resume. Use this without input or idempotency_key.'),
|
|
741
|
+
idempotency_key: z.string().min(1).max(96).regex(/^[A-Za-z0-9][A-Za-z0-9:._-]{0,95}$/).optional().describe('Stable key for one new scorecard run and its confirmation retry.'),
|
|
742
|
+
confirm_token: z.string().min(1).max(4096).optional().describe('Use only after the coach approves saving the displayed zero-cost run.'),
|
|
743
|
+
},
|
|
744
|
+
async ({ input, run_id, idempotency_key, confirm_token }) => {
|
|
745
|
+
if (run_id) {
|
|
746
|
+
if (input || idempotency_key || confirm_token) {
|
|
747
|
+
return text('Resume accepts only run_id. Nothing changed.', true);
|
|
748
|
+
}
|
|
749
|
+
return coachBookOut(
|
|
750
|
+
await call('GET', `/coach/book/runs/${encodeURIComponent(run_id)}`),
|
|
751
|
+
'run',
|
|
752
|
+
out,
|
|
753
|
+
text,
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
if (!input || !idempotency_key) {
|
|
757
|
+
return text('A new niche run requires the complete scorecard input and one stable idempotency_key. Nothing changed.', true);
|
|
758
|
+
}
|
|
759
|
+
return coachBookOut(await call(
|
|
760
|
+
'POST',
|
|
761
|
+
'/coach/book/tools/niche-fit/runs',
|
|
762
|
+
{
|
|
763
|
+
tool_spec_id: 'fbc.niche-fit.v1',
|
|
764
|
+
input,
|
|
765
|
+
idempotency_key,
|
|
766
|
+
confirm_token,
|
|
767
|
+
},
|
|
768
|
+
), 'run', out, text);
|
|
769
|
+
},
|
|
770
|
+
);
|
|
771
|
+
}
|