incremnt 0.8.4 → 0.8.6
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/package.json +1 -1
- package/src/ask-answer-verifier.js +25 -0
- package/src/ask-coach.js +254 -38
- package/src/coach-prompt-assembly.js +34 -0
- package/src/coach-prompt-layers.js +62 -0
- package/src/contract.js +12 -1
- package/src/format.js +33 -0
- package/src/openrouter.js +17 -79
- package/src/program-schedule-action.js +107 -0
- package/src/prompt-security.js +4 -0
- package/src/queries.js +230 -31
- package/src/remote.js +7 -0
- package/src/sync-service.js +31 -7
package/src/queries.js
CHANGED
|
@@ -541,6 +541,49 @@ export function recommendationForExercise(recommendations, exerciseName) {
|
|
|
541
541
|
return null;
|
|
542
542
|
}
|
|
543
543
|
|
|
544
|
+
function resolvedRecommendationForProgramExercise(snapshot, { programId, dayIndex, exerciseIndex, exerciseName }) {
|
|
545
|
+
const rows = Array.isArray(snapshot?.resolvedProgramRecommendations)
|
|
546
|
+
? snapshot.resolvedProgramRecommendations
|
|
547
|
+
: [];
|
|
548
|
+
if (rows.length === 0) return null;
|
|
549
|
+
|
|
550
|
+
const programKey = String(programId ?? '');
|
|
551
|
+
const exact = rows.find((row) =>
|
|
552
|
+
String(row?.programId ?? '') === programKey &&
|
|
553
|
+
Number(row?.dayIndex) === Number(dayIndex) &&
|
|
554
|
+
Number(row?.exerciseIndex) === Number(exerciseIndex)
|
|
555
|
+
);
|
|
556
|
+
if (exact) return exact;
|
|
557
|
+
|
|
558
|
+
const canonical = canonicalExerciseName(exerciseName);
|
|
559
|
+
return rows.find((row) =>
|
|
560
|
+
String(row?.programId ?? '') === programKey &&
|
|
561
|
+
Number(row?.dayIndex) === Number(dayIndex) &&
|
|
562
|
+
canonicalExerciseName(row?.exerciseName ?? row?.exerciseSlug) === canonical
|
|
563
|
+
) ?? null;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function publicRecommendationResolution(row) {
|
|
567
|
+
if (!row) return null;
|
|
568
|
+
return {
|
|
569
|
+
status: row.status ?? 'none',
|
|
570
|
+
reason: row.reason ?? null,
|
|
571
|
+
workingSetCount: row.workingSetCount ?? null,
|
|
572
|
+
warmupSetCount: row.warmupSetCount ?? null,
|
|
573
|
+
targetRepsCount: row.targetRepsCount ?? null,
|
|
574
|
+
displayText: row.displayText ?? (row.recommendation ? formatRecommendation(row.recommendation) : null),
|
|
575
|
+
recommendation: row.recommendation ? formatRecommendation(row.recommendation) : null
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function setRowForProgramDetail(set) {
|
|
580
|
+
return {
|
|
581
|
+
reps: set?.reps ?? null,
|
|
582
|
+
weight: set?.weight ?? null,
|
|
583
|
+
isWarmup: Boolean(set?.isWarmup)
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
|
|
544
587
|
function databaseExerciseNames(name) {
|
|
545
588
|
const canonical = canonicalExerciseName(name);
|
|
546
589
|
return normalizedExerciseAliasMapping[canonical] ?? [normalizeExerciseName(name)];
|
|
@@ -1129,24 +1172,141 @@ export function programDetail(snapshot, programId) {
|
|
|
1129
1172
|
days: (program.days ?? []).map((day, index) => ({
|
|
1130
1173
|
dayIndex: index,
|
|
1131
1174
|
title: day.title ?? null,
|
|
1132
|
-
exercises: (day.exercises ?? []).map((exercise) => {
|
|
1133
|
-
const
|
|
1175
|
+
exercises: (day.exercises ?? []).map((exercise, exerciseIndex) => {
|
|
1176
|
+
const resolved = resolvedRecommendationForProgramExercise(snapshot, {
|
|
1177
|
+
programId: program.id,
|
|
1178
|
+
dayIndex: index,
|
|
1179
|
+
exerciseIndex,
|
|
1180
|
+
exerciseName: exercise.name
|
|
1181
|
+
});
|
|
1182
|
+
const rec = resolved
|
|
1183
|
+
? (resolved.status === 'current' ? resolved.recommendation : null)
|
|
1184
|
+
: recommendationForExercise(snapshot.exerciseRecommendations, exercise.name);
|
|
1134
1185
|
return {
|
|
1135
1186
|
name: exercise.name,
|
|
1136
1187
|
muscleGroup: exercise.muscleGroup ?? null,
|
|
1137
1188
|
supersetGroupId: exercise.supersetGroupId ?? null,
|
|
1138
1189
|
supersetOrder: exercise.supersetOrder ?? null,
|
|
1139
|
-
sets: (exercise.sets ?? []).map(
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
})),
|
|
1143
|
-
...(rec ? { recommendation: formatRecommendation(rec) } : {})
|
|
1190
|
+
sets: (exercise.sets ?? []).map(setRowForProgramDetail),
|
|
1191
|
+
...(rec ? { recommendation: formatRecommendation(rec) } : {}),
|
|
1192
|
+
...(resolved ? { recommendationResolution: publicRecommendationResolution(resolved) } : {})
|
|
1144
1193
|
};
|
|
1145
1194
|
})
|
|
1146
1195
|
}))
|
|
1147
1196
|
};
|
|
1148
1197
|
}
|
|
1149
1198
|
|
|
1199
|
+
function programChangeValueText(value) {
|
|
1200
|
+
if (value == null) return null;
|
|
1201
|
+
if (typeof value === 'string') return value;
|
|
1202
|
+
if (typeof value === 'number' || typeof value === 'boolean') return String(value);
|
|
1203
|
+
if (typeof value === 'object') {
|
|
1204
|
+
if (value.value != null) return String(value.value);
|
|
1205
|
+
if (value.type && value.value == null) return null;
|
|
1206
|
+
}
|
|
1207
|
+
return JSON.stringify(value);
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
function compactProgramChangePatch(patch) {
|
|
1211
|
+
return {
|
|
1212
|
+
path: patch.path ?? null,
|
|
1213
|
+
field: patch.field ?? null,
|
|
1214
|
+
dayIndex: patch.dayIndex ?? null,
|
|
1215
|
+
dayTitle: patch.dayTitle ?? null,
|
|
1216
|
+
exerciseName: patch.exerciseName ?? null,
|
|
1217
|
+
exerciseSlug: patch.exerciseSlug ?? null,
|
|
1218
|
+
setIndex: patch.setIndex ?? null,
|
|
1219
|
+
before: programChangeValueText(patch.before),
|
|
1220
|
+
after: programChangeValueText(patch.after)
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
function splitProgramChangeList(value) {
|
|
1225
|
+
if (value == null || value === '') return [];
|
|
1226
|
+
return String(value).split('|').map((item) => item.trim()).filter(Boolean);
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
function affectedExercisesFromPatch(patch) {
|
|
1230
|
+
if (patch.exerciseName) return [patch.exerciseName];
|
|
1231
|
+
if (patch.field !== 'exerciseList') return [];
|
|
1232
|
+
|
|
1233
|
+
const before = splitProgramChangeList(patch.before);
|
|
1234
|
+
const after = splitProgramChangeList(patch.after);
|
|
1235
|
+
const beforeSet = new Set(before);
|
|
1236
|
+
const afterSet = new Set(after);
|
|
1237
|
+
const changed = [
|
|
1238
|
+
...before.filter((name) => !afterSet.has(name)),
|
|
1239
|
+
...after.filter((name) => !beforeSet.has(name))
|
|
1240
|
+
];
|
|
1241
|
+
return changed.length > 0 ? changed : after;
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
function compactProgramChangeRecord(record) {
|
|
1245
|
+
const patches = (record.patches ?? []).map(compactProgramChangePatch);
|
|
1246
|
+
return {
|
|
1247
|
+
id: record.id ?? null,
|
|
1248
|
+
createdAt: record.createdAt ?? null,
|
|
1249
|
+
source: record.source ?? null,
|
|
1250
|
+
kind: record.kind ?? null,
|
|
1251
|
+
status: record.status ?? null,
|
|
1252
|
+
summary: record.summary ?? null,
|
|
1253
|
+
rationale: record.rationale ?? null,
|
|
1254
|
+
relatedActionId: record.relatedActionId ?? null,
|
|
1255
|
+
relatedObservationId: record.relatedObservationId ?? null,
|
|
1256
|
+
beforeDigest: record.beforeDigest ?? null,
|
|
1257
|
+
afterDigest: record.afterDigest ?? null,
|
|
1258
|
+
affectedExercises: uniqueArray(patches.flatMap(affectedExercisesFromPatch)),
|
|
1259
|
+
affectedDays: uniqueArray(patches.map((patch) => patch.dayTitle).filter(Boolean)),
|
|
1260
|
+
affectedFields: uniqueArray(patches.map((patch) => patch.field).filter(Boolean)),
|
|
1261
|
+
patches
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
export function programChangeHistory(snapshot, { programId = null, limit = 20 } = {}) {
|
|
1266
|
+
const program = resolveProgramForQuery(snapshot, programId);
|
|
1267
|
+
if (!program) return null;
|
|
1268
|
+
const boundedLimit = boundedInteger(limit, { defaultValue: 20, min: 1, max: 100 });
|
|
1269
|
+
const records = Array.isArray(program.changeHistory) ? program.changeHistory : [];
|
|
1270
|
+
const sortedRecords = records
|
|
1271
|
+
.slice()
|
|
1272
|
+
.sort((lhs, rhs) => String(rhs.createdAt ?? '').localeCompare(String(lhs.createdAt ?? '')));
|
|
1273
|
+
const changes = sortedRecords
|
|
1274
|
+
.slice(0, boundedLimit)
|
|
1275
|
+
.map(compactProgramChangeRecord);
|
|
1276
|
+
return {
|
|
1277
|
+
programId: program.id,
|
|
1278
|
+
programName: program.name,
|
|
1279
|
+
limit: boundedLimit,
|
|
1280
|
+
totalChanges: records.length,
|
|
1281
|
+
changes
|
|
1282
|
+
};
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
export function getProgramChangeHistory(snapshot, { programId = null, limit = 20 } = {}) {
|
|
1286
|
+
const payload = programChangeHistory(snapshot, { programId, limit });
|
|
1287
|
+
if (!payload) {
|
|
1288
|
+
return coachToolResult('get_program_change_history', { programId, limit }, {
|
|
1289
|
+
missingDataFlags: ['no_active_program']
|
|
1290
|
+
});
|
|
1291
|
+
}
|
|
1292
|
+
return coachToolResult('get_program_change_history', {
|
|
1293
|
+
programId: payload.programId,
|
|
1294
|
+
limit: payload.limit
|
|
1295
|
+
}, {
|
|
1296
|
+
rows: payload.changes,
|
|
1297
|
+
facts: {
|
|
1298
|
+
programId: payload.programId,
|
|
1299
|
+
programName: payload.programName,
|
|
1300
|
+
totalChanges: payload.totalChanges,
|
|
1301
|
+
returnedChanges: payload.changes.length,
|
|
1302
|
+
restoreAvailable: false
|
|
1303
|
+
},
|
|
1304
|
+
sourceIds: payload.changes.map((change) => change.id).filter(Boolean),
|
|
1305
|
+
sourceTimestamp: payload.changes[0]?.createdAt ?? null,
|
|
1306
|
+
missingDataFlags: payload.totalChanges === 0 ? ['no_program_change_history'] : []
|
|
1307
|
+
});
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1150
1310
|
export function formatRecommendation(rec) {
|
|
1151
1311
|
if (!rec || !rec.kind) return null;
|
|
1152
1312
|
const amount = rec.amount ?? 0;
|
|
@@ -2350,26 +2510,22 @@ export function askContext(snapshot, { exclude = new Set(), today = new Date() }
|
|
|
2350
2510
|
const day = days[i];
|
|
2351
2511
|
const upNext = i === currentDayIndex ? ' [UP NEXT]' : '';
|
|
2352
2512
|
lines.push(` ${day.title ?? `Day ${i + 1}`}${upNext}:`);
|
|
2353
|
-
for (const exercise of day.exercises ?? []) {
|
|
2513
|
+
for (const [exerciseIndex, exercise] of (day.exercises ?? []).entries()) {
|
|
2354
2514
|
const sets = exercise.sets ?? [];
|
|
2355
2515
|
if (sets.length === 0) continue;
|
|
2356
|
-
|
|
2357
|
-
const
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
run = 1;
|
|
2367
|
-
}
|
|
2368
|
-
}
|
|
2369
|
-
const rec = recommendationForExercise(snapshot.exerciseRecommendations, exercise.name);
|
|
2516
|
+
const groups = plannedSetGroups(sets);
|
|
2517
|
+
const resolved = resolvedRecommendationForProgramExercise(snapshot, {
|
|
2518
|
+
programId: program.id,
|
|
2519
|
+
dayIndex: i,
|
|
2520
|
+
exerciseIndex,
|
|
2521
|
+
exerciseName: exercise.name
|
|
2522
|
+
});
|
|
2523
|
+
const rec = resolved
|
|
2524
|
+
? (resolved.status === 'current' ? resolved.recommendation : null)
|
|
2525
|
+
: recommendationForExercise(snapshot.exerciseRecommendations, exercise.name);
|
|
2370
2526
|
const recLabel = rec ? formatRecommendation(rec) : null;
|
|
2371
2527
|
const recSuffix = recLabel ? ` → next: ${recLabel}` : '';
|
|
2372
|
-
lines.push(` ${exercise.name}: ${groups
|
|
2528
|
+
lines.push(` ${exercise.name}: ${groups}${recSuffix}`);
|
|
2373
2529
|
}
|
|
2374
2530
|
}
|
|
2375
2531
|
}
|
|
@@ -2543,10 +2699,11 @@ function plannedSetGroups(sets = []) {
|
|
|
2543
2699
|
for (let i = 1; i <= sets.length; i++) {
|
|
2544
2700
|
const prev = sets[i - 1];
|
|
2545
2701
|
const curr = sets[i];
|
|
2546
|
-
if (curr && curr.weight === prev.weight && curr.reps === prev.reps) {
|
|
2702
|
+
if (curr && curr.weight === prev.weight && curr.reps === prev.reps && Boolean(curr.isWarmup) === Boolean(prev.isWarmup)) {
|
|
2547
2703
|
run++;
|
|
2548
2704
|
} else {
|
|
2549
|
-
|
|
2705
|
+
const prefix = prev.isWarmup ? 'warmup ' : '';
|
|
2706
|
+
groups.push(`${prefix}${run}×${prev.reps ?? '?'}${Number(prev.weight) > 0 ? ` @ ${prev.weight}kg` : ''}`);
|
|
2550
2707
|
run = 1;
|
|
2551
2708
|
}
|
|
2552
2709
|
}
|
|
@@ -3070,12 +3227,24 @@ export function getNextSession(snapshot, { historyLimit = 8, today = new Date(),
|
|
|
3070
3227
|
const currentDayIndex = program?.currentDayIndex ?? 0;
|
|
3071
3228
|
const day = program?.days?.[currentDayIndex] ?? null;
|
|
3072
3229
|
const exerciseCanonicals = exercisesForDay(day);
|
|
3073
|
-
const exercises = (day?.exercises ?? []).map((exercise) =>
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3230
|
+
const exercises = (day?.exercises ?? []).map((exercise, exerciseIndex) => {
|
|
3231
|
+
const exerciseName = exercise.name ?? exercise.exerciseName;
|
|
3232
|
+
const resolved = resolvedRecommendationForProgramExercise(snapshot, {
|
|
3233
|
+
programId: program?.id,
|
|
3234
|
+
dayIndex: currentDayIndex,
|
|
3235
|
+
exerciseIndex,
|
|
3236
|
+
exerciseName
|
|
3237
|
+
});
|
|
3238
|
+
return {
|
|
3239
|
+
name: exerciseName,
|
|
3240
|
+
plannedSets: plannedSetGroups(exercise.sets ?? exercise.targetSets ?? []),
|
|
3241
|
+
note: clippedUserNote(exercise.note),
|
|
3242
|
+
recommendation: resolved
|
|
3243
|
+
? (resolved.status === 'current' ? resolved.recommendation : null)
|
|
3244
|
+
: recommendationForExercise(snapshot.exerciseRecommendations, exerciseName),
|
|
3245
|
+
...(resolved ? { recommendationResolution: publicRecommendationResolution(resolved) } : {})
|
|
3246
|
+
};
|
|
3247
|
+
});
|
|
3079
3248
|
const history = getExerciseHistory(snapshot, {
|
|
3080
3249
|
exercises: [...exerciseCanonicals].map((canonical) => ({ canonical, displayName: canonical })),
|
|
3081
3250
|
limit: historyLimit,
|
|
@@ -4554,6 +4723,18 @@ export const COACH_READ_TOOL_SCHEMAS = Object.freeze({
|
|
|
4554
4723
|
},
|
|
4555
4724
|
outputSchema: COACH_TOOL_RESULT_SCHEMA
|
|
4556
4725
|
}),
|
|
4726
|
+
get_program_change_history: Object.freeze({
|
|
4727
|
+
description: 'Read recent durable program prescription and schedule changes for the active or requested program.',
|
|
4728
|
+
inputSchema: {
|
|
4729
|
+
type: 'object',
|
|
4730
|
+
properties: {
|
|
4731
|
+
programId: { type: 'string', description: 'Optional program ID; defaults to active program.' },
|
|
4732
|
+
limit: { type: 'integer', minimum: 1, maximum: 100, default: 20 }
|
|
4733
|
+
},
|
|
4734
|
+
additionalProperties: false
|
|
4735
|
+
},
|
|
4736
|
+
outputSchema: COACH_TOOL_RESULT_SCHEMA
|
|
4737
|
+
}),
|
|
4557
4738
|
get_training_profile: Object.freeze({
|
|
4558
4739
|
description: 'Summarize stable lifter profile evidence from current program, logged exercises, cadence, and recent notes.',
|
|
4559
4740
|
inputSchema: {
|
|
@@ -4744,6 +4925,12 @@ function normalizeCoachToolInput(toolName, input = {}) {
|
|
|
4744
4925
|
limitExercises: boundedInteger(source.limitExercises, { defaultValue: 10, min: 1, max: 50 })
|
|
4745
4926
|
};
|
|
4746
4927
|
}
|
|
4928
|
+
if (toolName === 'get_program_change_history') {
|
|
4929
|
+
return {
|
|
4930
|
+
programId: source.programId ? String(source.programId) : null,
|
|
4931
|
+
limit: boundedInteger(source.limit, { defaultValue: 20, min: 1, max: 100 })
|
|
4932
|
+
};
|
|
4933
|
+
}
|
|
4747
4934
|
if (toolName === 'get_training_profile') {
|
|
4748
4935
|
return {
|
|
4749
4936
|
since: normalizeDateOnly(source.since),
|
|
@@ -4807,6 +4994,7 @@ export function executeCoachReadTool(snapshot, toolName, input = {}) {
|
|
|
4807
4994
|
if (toolName === 'get_increment_score') return getIncrementScore(snapshot, params);
|
|
4808
4995
|
if (toolName === 'get_exercise_progress_summary') return getExerciseProgressSummary(snapshot, params);
|
|
4809
4996
|
if (toolName === 'get_program_progress') return getProgramProgress(snapshot, params);
|
|
4997
|
+
if (toolName === 'get_program_change_history') return getProgramChangeHistory(snapshot, params);
|
|
4810
4998
|
if (toolName === 'get_training_profile') return getTrainingProfile(snapshot, params);
|
|
4811
4999
|
if (toolName === 'get_athlete_snapshot') return getAthleteSnapshot(snapshot, params);
|
|
4812
5000
|
if (toolName === 'get_muscle_volume_trend') return getMuscleVolumeTrend(snapshot, params);
|
|
@@ -5524,6 +5712,17 @@ export function executeReadCommand(snapshot, normalizedCommand, options = {}) {
|
|
|
5524
5712
|
};
|
|
5525
5713
|
}
|
|
5526
5714
|
|
|
5715
|
+
if (normalizedCommand === 'program-history') {
|
|
5716
|
+
const payload = programChangeHistory(snapshot, {
|
|
5717
|
+
programId: requiredOption(options, 'program-id'),
|
|
5718
|
+
limit: options.limit
|
|
5719
|
+
});
|
|
5720
|
+
if (!payload) {
|
|
5721
|
+
return { ok: false, error: options['program-id'] ? `Program not found: ${options['program-id']}` : 'No programs found' };
|
|
5722
|
+
}
|
|
5723
|
+
return { ok: true, payload };
|
|
5724
|
+
}
|
|
5725
|
+
|
|
5527
5726
|
if (normalizedCommand === 'exercise-progress-summary') {
|
|
5528
5727
|
const exerciseName = requiredOption(options, 'name', 'exercise');
|
|
5529
5728
|
return {
|
package/src/remote.js
CHANGED
|
@@ -139,6 +139,7 @@ const remoteCommandHandlers = {
|
|
|
139
139
|
'program-summary': executeRemoteRead,
|
|
140
140
|
'program-detail': executeRemoteRead,
|
|
141
141
|
'program-progress': executeRemoteRead,
|
|
142
|
+
'program-history': executeRemoteRead,
|
|
142
143
|
'exercise-progress-summary': executeRemoteRead,
|
|
143
144
|
'cycle-summary-list': executeRemoteRead,
|
|
144
145
|
'cycle-summary-show': executeRemoteRead,
|
|
@@ -257,6 +258,12 @@ function endpointForCommand(baseUrl, normalizedCommand, options) {
|
|
|
257
258
|
if (options.limitExercises) url.searchParams.set('limitExercises', options.limitExercises);
|
|
258
259
|
return url;
|
|
259
260
|
}
|
|
261
|
+
case 'program-history': {
|
|
262
|
+
const url = resolveServiceUrl(baseUrl, '/cli/programs/history');
|
|
263
|
+
if (options['program-id']) url.searchParams.set('program-id', options['program-id']);
|
|
264
|
+
if (options.limit) url.searchParams.set('limit', options.limit);
|
|
265
|
+
return url;
|
|
266
|
+
}
|
|
260
267
|
case 'cycle-summary-list': {
|
|
261
268
|
const cyclesUrl = resolveServiceUrl(baseUrl, '/cli/cycles');
|
|
262
269
|
if (options['program-id']) {
|
package/src/sync-service.js
CHANGED
|
@@ -25,6 +25,7 @@ import { sanitizeHistory, detectSystemPromptLeak, stripXMLTagBlocks } from './pr
|
|
|
25
25
|
import { enrichScoreSnapshots } from './score-context.js';
|
|
26
26
|
import { extractAskProgramDraft } from './program-draft.js';
|
|
27
27
|
import { extractPlanChangeset } from './plan-changeset.js';
|
|
28
|
+
import { extractProgramScheduleAction } from './program-schedule-action.js';
|
|
28
29
|
|
|
29
30
|
const MAX_BODY_BYTES = 10 * 1024 * 1024; // 10 MB
|
|
30
31
|
const DEFAULT_RATE_LIMIT_WINDOW_MS = 60_000;
|
|
@@ -285,6 +286,7 @@ export function sanitizeAskStructuredResponseForStorage(structured) {
|
|
|
285
286
|
const limitations = askStorageStringArray(structured.limitations, { maxItems: ASK_STRUCTURED_MAX_ITEMS, maxLength: 240 });
|
|
286
287
|
const answerVerification = sanitizeAskAnswerVerificationReceipt(structured.answerVerification);
|
|
287
288
|
const programDraft = sanitizeAskProgramDraftForStorage(structured.programDraft);
|
|
289
|
+
const programScheduleAction = sanitizeAskProgramDraftForStorage(structured.programScheduleAction);
|
|
288
290
|
|
|
289
291
|
return {
|
|
290
292
|
...(answer ? { answer } : {}),
|
|
@@ -294,7 +296,8 @@ export function sanitizeAskStructuredResponseForStorage(structured) {
|
|
|
294
296
|
followUpSuggestions,
|
|
295
297
|
limitations,
|
|
296
298
|
...(answerVerification ? { answerVerification } : {}),
|
|
297
|
-
programDraft
|
|
299
|
+
programDraft,
|
|
300
|
+
programScheduleAction
|
|
298
301
|
};
|
|
299
302
|
}
|
|
300
303
|
|
|
@@ -648,6 +651,7 @@ export function buildAskInteractionLogPayload({
|
|
|
648
651
|
followUpSuggestionCount: Array.isArray(structured?.followUpSuggestions) ? structured.followUpSuggestions.length : undefined,
|
|
649
652
|
limitationCount: Array.isArray(structured?.limitations) ? structured.limitations.length : undefined,
|
|
650
653
|
hasProgramDraft: structured?.programDraft != null ? true : undefined,
|
|
654
|
+
hasProgramScheduleAction: structured?.programScheduleAction != null ? true : undefined,
|
|
651
655
|
askVerificationStatus: answerVerification.status,
|
|
652
656
|
askVerificationRetryCount: typeof answerVerification.retryCount === 'number' ? answerVerification.retryCount : undefined,
|
|
653
657
|
askVerificationDegraded: answerVerification.degraded === true ? true : undefined,
|
|
@@ -1260,6 +1264,16 @@ function routeRequest(url, method) {
|
|
|
1260
1264
|
};
|
|
1261
1265
|
}
|
|
1262
1266
|
|
|
1267
|
+
if (pathname === '/cli/programs/history') {
|
|
1268
|
+
return {
|
|
1269
|
+
command: 'program-history',
|
|
1270
|
+
options: {
|
|
1271
|
+
'program-id': url.searchParams.get('program-id') ?? undefined,
|
|
1272
|
+
limit: url.searchParams.get('limit') ?? undefined
|
|
1273
|
+
}
|
|
1274
|
+
};
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1263
1277
|
const programShowMatch = pathname.match(/^\/cli\/programs\/([^/]+)$/);
|
|
1264
1278
|
if (programShowMatch) {
|
|
1265
1279
|
return { command: 'program-detail', options: { id: programShowMatch[1] } };
|
|
@@ -5379,7 +5393,12 @@ export function createSyncServiceRequestHandler({
|
|
|
5379
5393
|
canonicalizeExerciseName: canonicalExerciseName
|
|
5380
5394
|
});
|
|
5381
5395
|
const changesetParsed = extractPlanChangeset(parsed.answerText);
|
|
5396
|
+
const scheduleActionParsed = extractProgramScheduleAction(changesetParsed.answerText, {
|
|
5397
|
+
expectedStartDate: routedContext.metadata?.programScheduleActionStartDate ?? null,
|
|
5398
|
+
requireTrailing: true
|
|
5399
|
+
});
|
|
5382
5400
|
const requestedPlanAdjustment = requestedCoachObservation?.intent === 'plan_adjustment';
|
|
5401
|
+
const requestedProgramScheduleAction = routedContext.metadata?.intent?.requestedAction === 'schedule_program_action';
|
|
5383
5402
|
const draft = missingRequestedCoachObservation && requestedCoachObservation?.intent === 'successor_plan'
|
|
5384
5403
|
? undefined
|
|
5385
5404
|
: parsed.programDraft;
|
|
@@ -5388,8 +5407,11 @@ export function createSyncServiceRequestHandler({
|
|
|
5388
5407
|
const changeset = !requestedPlanAdjustment || missingRequestedCoachObservation
|
|
5389
5408
|
? undefined
|
|
5390
5409
|
: changesetParsed.planChangeset;
|
|
5391
|
-
const
|
|
5392
|
-
|
|
5410
|
+
const programScheduleAction = requestedProgramScheduleAction
|
|
5411
|
+
? scheduleActionParsed.programScheduleAction ?? undefined
|
|
5412
|
+
: undefined;
|
|
5413
|
+
const answer = stripXMLTagBlocks(scheduleActionParsed.answerText);
|
|
5414
|
+
return { askResult: result, programDraft: draft, planChangeset: changeset, programScheduleAction, assistantAnswer: answer };
|
|
5393
5415
|
};
|
|
5394
5416
|
|
|
5395
5417
|
let attempt = await generateAttempt(ctx);
|
|
@@ -5499,7 +5521,8 @@ export function createSyncServiceRequestHandler({
|
|
|
5499
5521
|
...attempt,
|
|
5500
5522
|
assistantAnswer: safeAskVerificationFallback(),
|
|
5501
5523
|
programDraft: undefined,
|
|
5502
|
-
planChangeset: undefined
|
|
5524
|
+
planChangeset: undefined,
|
|
5525
|
+
programScheduleAction: undefined
|
|
5503
5526
|
};
|
|
5504
5527
|
}
|
|
5505
5528
|
}
|
|
@@ -5525,7 +5548,7 @@ export function createSyncServiceRequestHandler({
|
|
|
5525
5548
|
});
|
|
5526
5549
|
}
|
|
5527
5550
|
|
|
5528
|
-
const { askResult, assistantAnswer, programDraft, planChangeset } = attempt;
|
|
5551
|
+
const { askResult, assistantAnswer, programDraft, planChangeset, programScheduleAction } = attempt;
|
|
5529
5552
|
const promptSurface = askResult.promptSurface
|
|
5530
5553
|
?? (persistedKind === 'weekly-checkin' ? 'weekly-checkin' : 'ask');
|
|
5531
5554
|
const promptVersion = askResult.promptVersion
|
|
@@ -5539,7 +5562,7 @@ export function createSyncServiceRequestHandler({
|
|
|
5539
5562
|
...(programDraftRetryCount > 0 ? { programDraftRetryCount } : {}),
|
|
5540
5563
|
...(planChangesetRetryCount > 0 ? { planChangesetRetryCount } : {})
|
|
5541
5564
|
};
|
|
5542
|
-
const structured = buildAskStructuredResponse(assistantAnswer, routingMetadata, { programDraft, planChangeset, question });
|
|
5565
|
+
const structured = buildAskStructuredResponse(assistantAnswer, routingMetadata, { programDraft, planChangeset, programScheduleAction, question });
|
|
5543
5566
|
console.log(`ask-coach-meta ${JSON.stringify(buildAskInteractionLogPayload({
|
|
5544
5567
|
accountId: account.id,
|
|
5545
5568
|
status: 'ok',
|
|
@@ -5642,7 +5665,8 @@ export function createSyncServiceRequestHandler({
|
|
|
5642
5665
|
metadata,
|
|
5643
5666
|
structured,
|
|
5644
5667
|
programDraft,
|
|
5645
|
-
planChangeset
|
|
5668
|
+
planChangeset,
|
|
5669
|
+
programScheduleAction
|
|
5646
5670
|
});
|
|
5647
5671
|
} catch (err) {
|
|
5648
5672
|
console.error('AI ask error:', err.message);
|