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/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 rec = recommendationForExercise(snapshot.exerciseRecommendations, exercise.name);
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((set) => ({
1140
- reps: set.reps ?? null,
1141
- weight: set.weight ?? null
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
- // Group identical sets for compact display: e.g. "4×10 @ 100kg"
2357
- const groups = [];
2358
- let run = 1;
2359
- for (let j = 1; j <= sets.length; j++) {
2360
- const prev = sets[j - 1];
2361
- const curr = sets[j];
2362
- if (curr && curr.weight === prev.weight && curr.reps === prev.reps) {
2363
- run++;
2364
- } else {
2365
- groups.push(`${run}×${prev.reps}${prev.weight > 0 ? ` @ ${prev.weight}kg` : ''}`);
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.join(', ')}${recSuffix}`);
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
- groups.push(`${run}×${prev.reps ?? '?'}${Number(prev.weight) > 0 ? ` @ ${prev.weight}kg` : ''}`);
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
- name: exercise.name ?? exercise.exerciseName,
3075
- plannedSets: plannedSetGroups(exercise.sets ?? exercise.targetSets ?? []),
3076
- note: clippedUserNote(exercise.note),
3077
- recommendation: recommendationForExercise(snapshot.exerciseRecommendations, exercise.name ?? exercise.exerciseName)
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']) {
@@ -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 answer = stripXMLTagBlocks(changesetParsed.answerText);
5392
- return { askResult: result, programDraft: draft, planChangeset: changeset, assistantAnswer: answer };
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);