incremnt 0.7.0 → 0.7.2
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 +4 -1
- package/SKILL.md +5 -2
- package/package.json +1 -1
- package/src/coach-facts.js +14 -1
- package/src/contract.js +36 -1
- package/src/format.js +78 -2
- package/src/mcp.js +1 -1
- package/src/openrouter.js +25 -20
- package/src/plan-comparison.js +245 -0
- package/src/prompt-changelog.js +94 -0
- package/src/queries.js +867 -174
- package/src/remote.js +103 -1
- package/src/summary-evals.js +522 -9
- package/src/sync-service.js +265 -4
package/src/sync-service.js
CHANGED
|
@@ -22,6 +22,9 @@ const DEFAULT_RATE_LIMIT_RULES = {
|
|
|
22
22
|
'weekly-checkin-ack': 30,
|
|
23
23
|
'weekly-checkin-start': 10,
|
|
24
24
|
'weekly-digest-current': 30,
|
|
25
|
+
'coach-observations-current': 30,
|
|
26
|
+
'coach-observations-seen': 30,
|
|
27
|
+
'coach-observations-dismiss': 30,
|
|
25
28
|
'dev-login': 10,
|
|
26
29
|
'device-start': 20,
|
|
27
30
|
'device-poll': 300,
|
|
@@ -682,6 +685,47 @@ function createRateLimiter({
|
|
|
682
685
|
};
|
|
683
686
|
}
|
|
684
687
|
|
|
688
|
+
function parseLimit(value, { defaultValue, max }) {
|
|
689
|
+
const parsed = value ? Number.parseInt(value, 10) : defaultValue;
|
|
690
|
+
return Number.isFinite(parsed) && parsed > 0 ? Math.min(parsed, max) : defaultValue;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
function coachObservationSourceTriggerForScoreSnapshots(snapshots) {
|
|
694
|
+
if (!Array.isArray(snapshots)) return undefined;
|
|
695
|
+
const reasons = snapshots.map((snapshot) => snapshot?.triggerReason);
|
|
696
|
+
if (reasons.length === 0 || reasons.some((reason) => !reason)) return undefined;
|
|
697
|
+
if (reasons.includes('sessionSave')) return 'session_completed';
|
|
698
|
+
if (reasons.every((reason) => reason === 'historicalBackfill')) {
|
|
699
|
+
return 'manual_backfill';
|
|
700
|
+
}
|
|
701
|
+
if (reasons.every((reason) => reason === 'dailyRefresh')) {
|
|
702
|
+
return 'daily_refresh';
|
|
703
|
+
}
|
|
704
|
+
return undefined;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
function coachObservationGenerationMetadata(result, sourceTrigger) {
|
|
708
|
+
return {
|
|
709
|
+
attempted: true,
|
|
710
|
+
generated: Number(result?.generated ?? 0),
|
|
711
|
+
persisted: Number(result?.persisted ?? 0),
|
|
712
|
+
skippedReason: result?.skippedReason ?? null,
|
|
713
|
+
sourceTrigger: sourceTrigger ?? null
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
function normalizeAskCoachObservationFollowUp(value) {
|
|
718
|
+
if (!value || typeof value !== 'object') return null;
|
|
719
|
+
const id = String(value.id ?? '').trim();
|
|
720
|
+
return id ? { id: id.slice(0, 128) } : null;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
function selectAskCoachObservationFollowUp(requested, observations) {
|
|
724
|
+
if (!requested) return null;
|
|
725
|
+
return (Array.isArray(observations) ? observations : [])
|
|
726
|
+
.find((observation) => String(observation?.id ?? '') === requested.id);
|
|
727
|
+
}
|
|
728
|
+
|
|
685
729
|
function routeRequest(url, method) {
|
|
686
730
|
const pathname = url.pathname;
|
|
687
731
|
|
|
@@ -1055,6 +1099,28 @@ function routeRequest(url, method) {
|
|
|
1055
1099
|
return { command: 'weekly-digest-current', options: {} };
|
|
1056
1100
|
}
|
|
1057
1101
|
|
|
1102
|
+
if (pathname === '/cli/coach-observations/current') {
|
|
1103
|
+
return {
|
|
1104
|
+
command: 'coach-observations-current',
|
|
1105
|
+
options: {
|
|
1106
|
+
limit: url.searchParams.get('limit') ?? undefined,
|
|
1107
|
+
refresh: url.searchParams.get('refresh') ?? undefined
|
|
1108
|
+
}
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
{
|
|
1113
|
+
const coachObservationActionMatch = pathname.match(/^\/cli\/coach-observations\/([^/]+)\/(seen|dismiss)$/);
|
|
1114
|
+
if (coachObservationActionMatch) {
|
|
1115
|
+
return {
|
|
1116
|
+
command: `coach-observations-${coachObservationActionMatch[2]}`,
|
|
1117
|
+
options: {
|
|
1118
|
+
id: decodeURIComponent(coachObservationActionMatch[1])
|
|
1119
|
+
}
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1058
1124
|
if (pathname === '/cli/health/ai') {
|
|
1059
1125
|
return {
|
|
1060
1126
|
command: 'health-ai',
|
|
@@ -1512,6 +1578,27 @@ async function readJsonBody(request) {
|
|
|
1512
1578
|
}
|
|
1513
1579
|
}
|
|
1514
1580
|
|
|
1581
|
+
async function readOptionalJsonBody(request) {
|
|
1582
|
+
const chunks = [];
|
|
1583
|
+
let totalSize = 0;
|
|
1584
|
+
for await (const chunk of request) {
|
|
1585
|
+
totalSize += chunk.length;
|
|
1586
|
+
if (totalSize > MAX_BODY_BYTES) {
|
|
1587
|
+
throw new Error('Request body too large.');
|
|
1588
|
+
}
|
|
1589
|
+
chunks.push(chunk);
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
const raw = Buffer.concat(chunks).toString('utf8');
|
|
1593
|
+
if (!raw.trim()) return {};
|
|
1594
|
+
|
|
1595
|
+
try {
|
|
1596
|
+
return JSON.parse(raw);
|
|
1597
|
+
} catch {
|
|
1598
|
+
throw new Error('Invalid JSON in request body.');
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1515
1602
|
async function readUrlEncodedBody(request) {
|
|
1516
1603
|
const chunks = [];
|
|
1517
1604
|
let totalSize = 0;
|
|
@@ -1992,6 +2079,10 @@ export function createSyncServiceRequestHandler({
|
|
|
1992
2079
|
insertScoreSnapshotsForAccount = null,
|
|
1993
2080
|
listScoreSnapshotsForAccount = null,
|
|
1994
2081
|
getCurrentWeeklyScoreDigestForAccount = null,
|
|
2082
|
+
generateCoachObservationsForAccount = null,
|
|
2083
|
+
listCurrentCoachObservationsForAccount = null,
|
|
2084
|
+
markCoachObservationSeenForAccount = null,
|
|
2085
|
+
dismissCoachObservationForAccount = null,
|
|
1995
2086
|
// Social
|
|
1996
2087
|
social = null,
|
|
1997
2088
|
onError = null
|
|
@@ -2858,6 +2949,22 @@ export function createSyncServiceRequestHandler({
|
|
|
2858
2949
|
return;
|
|
2859
2950
|
}
|
|
2860
2951
|
const result = await insertScoreSnapshotsForAccount(account, body.snapshots);
|
|
2952
|
+
if (generateCoachObservationsForAccount) {
|
|
2953
|
+
const sourceTrigger = coachObservationSourceTriggerForScoreSnapshots(body.snapshots);
|
|
2954
|
+
try {
|
|
2955
|
+
const generationResult = await generateCoachObservationsForAccount(account, { sourceTrigger });
|
|
2956
|
+
result.coachObservations = coachObservationGenerationMetadata(generationResult, sourceTrigger);
|
|
2957
|
+
} catch (error) {
|
|
2958
|
+
console.error('Coach observation generation after score upload failed:', error.message);
|
|
2959
|
+
result.coachObservations = {
|
|
2960
|
+
attempted: true,
|
|
2961
|
+
generated: 0,
|
|
2962
|
+
persisted: 0,
|
|
2963
|
+
skippedReason: 'generation_failed',
|
|
2964
|
+
sourceTrigger
|
|
2965
|
+
};
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2861
2968
|
json(response, 200, result);
|
|
2862
2969
|
return;
|
|
2863
2970
|
} catch (error) {
|
|
@@ -3496,6 +3603,138 @@ export function createSyncServiceRequestHandler({
|
|
|
3496
3603
|
return;
|
|
3497
3604
|
}
|
|
3498
3605
|
|
|
3606
|
+
if (route.command === 'coach-observations-current') {
|
|
3607
|
+
if (request.method !== 'GET') {
|
|
3608
|
+
methodNotAllowed(response, 'Use GET for /cli/coach-observations/current.');
|
|
3609
|
+
return;
|
|
3610
|
+
}
|
|
3611
|
+
if (route.options.refresh && route.options.refresh !== 'morning_open') {
|
|
3612
|
+
badRequest(response, 'Unsupported coach observations refresh value.');
|
|
3613
|
+
return;
|
|
3614
|
+
}
|
|
3615
|
+
if (!listCurrentCoachObservationsForAccount) {
|
|
3616
|
+
json(response, 503, { error: 'Coach observations not available' });
|
|
3617
|
+
return;
|
|
3618
|
+
}
|
|
3619
|
+
try {
|
|
3620
|
+
const limit = parseLimit(route.options.limit, { defaultValue: 5, max: 20 });
|
|
3621
|
+
let refresh = null;
|
|
3622
|
+
if (route.options.refresh === 'morning_open') {
|
|
3623
|
+
const writeAccount = writeAuthenticator
|
|
3624
|
+
? await writeAuthenticator(requestToken)
|
|
3625
|
+
: requestToken === token
|
|
3626
|
+
? account
|
|
3627
|
+
: null;
|
|
3628
|
+
if (!writeAccount) {
|
|
3629
|
+
unauthorized(response, request);
|
|
3630
|
+
return;
|
|
3631
|
+
}
|
|
3632
|
+
const sourceTrigger = 'daily_refresh';
|
|
3633
|
+
if (generateCoachObservationsForAccount) {
|
|
3634
|
+
try {
|
|
3635
|
+
const generationResult = await generateCoachObservationsForAccount(writeAccount, {
|
|
3636
|
+
sourceTrigger,
|
|
3637
|
+
refresh: route.options.refresh
|
|
3638
|
+
});
|
|
3639
|
+
refresh = coachObservationGenerationMetadata(generationResult, sourceTrigger);
|
|
3640
|
+
} catch (error) {
|
|
3641
|
+
console.error('Coach observation generation on morning open failed:', error.message);
|
|
3642
|
+
refresh = {
|
|
3643
|
+
attempted: true,
|
|
3644
|
+
generated: 0,
|
|
3645
|
+
persisted: 0,
|
|
3646
|
+
skippedReason: 'generation_failed',
|
|
3647
|
+
sourceTrigger
|
|
3648
|
+
};
|
|
3649
|
+
}
|
|
3650
|
+
} else {
|
|
3651
|
+
refresh = {
|
|
3652
|
+
attempted: false,
|
|
3653
|
+
generated: 0,
|
|
3654
|
+
persisted: 0,
|
|
3655
|
+
skippedReason: 'generation_unavailable',
|
|
3656
|
+
sourceTrigger
|
|
3657
|
+
};
|
|
3658
|
+
}
|
|
3659
|
+
}
|
|
3660
|
+
const observations = await listCurrentCoachObservationsForAccount(account, {
|
|
3661
|
+
limit
|
|
3662
|
+
});
|
|
3663
|
+
json(response, 200, {
|
|
3664
|
+
observations: observations ?? [],
|
|
3665
|
+
...(refresh ? { refresh } : {})
|
|
3666
|
+
});
|
|
3667
|
+
} catch (err) {
|
|
3668
|
+
console.error('Coach observations read error:', err.message);
|
|
3669
|
+
json(response, 500, { error: 'Failed to read coach observations' });
|
|
3670
|
+
}
|
|
3671
|
+
return;
|
|
3672
|
+
}
|
|
3673
|
+
|
|
3674
|
+
if (route.command === 'coach-observations-seen') {
|
|
3675
|
+
if (request.method !== 'POST') {
|
|
3676
|
+
methodNotAllowed(response, 'Use POST for /cli/coach-observations/:id/seen.');
|
|
3677
|
+
return;
|
|
3678
|
+
}
|
|
3679
|
+
if (!markCoachObservationSeenForAccount) {
|
|
3680
|
+
json(response, 503, { error: 'Coach observations not available' });
|
|
3681
|
+
return;
|
|
3682
|
+
}
|
|
3683
|
+
let body = {};
|
|
3684
|
+
try {
|
|
3685
|
+
body = await readOptionalJsonBody(request);
|
|
3686
|
+
} catch {
|
|
3687
|
+
badRequest(response, 'Invalid request body.');
|
|
3688
|
+
return;
|
|
3689
|
+
}
|
|
3690
|
+
const seenAt = body?.seenAt ? new Date(body.seenAt) : null;
|
|
3691
|
+
if (seenAt && Number.isNaN(seenAt.getTime())) {
|
|
3692
|
+
badRequest(response, 'Invalid seenAt.');
|
|
3693
|
+
return;
|
|
3694
|
+
}
|
|
3695
|
+
try {
|
|
3696
|
+
const observation = await markCoachObservationSeenForAccount(account, route.options.id, { seenAt });
|
|
3697
|
+
if (!observation) {
|
|
3698
|
+
notFound(response, 'Observation not found');
|
|
3699
|
+
return;
|
|
3700
|
+
}
|
|
3701
|
+
json(response, 200, { observation });
|
|
3702
|
+
} catch (err) {
|
|
3703
|
+
console.error('Coach observation seen error:', err.message);
|
|
3704
|
+
json(response, 500, { error: 'Failed to mark observation seen' });
|
|
3705
|
+
}
|
|
3706
|
+
return;
|
|
3707
|
+
}
|
|
3708
|
+
|
|
3709
|
+
if (route.command === 'coach-observations-dismiss') {
|
|
3710
|
+
if (request.method !== 'POST') {
|
|
3711
|
+
methodNotAllowed(response, 'Use POST for /cli/coach-observations/:id/dismiss.');
|
|
3712
|
+
return;
|
|
3713
|
+
}
|
|
3714
|
+
if (!dismissCoachObservationForAccount) {
|
|
3715
|
+
json(response, 503, { error: 'Coach observations not available' });
|
|
3716
|
+
return;
|
|
3717
|
+
}
|
|
3718
|
+
try {
|
|
3719
|
+
await readOptionalJsonBody(request);
|
|
3720
|
+
} catch {
|
|
3721
|
+
badRequest(response, 'Invalid request body.');
|
|
3722
|
+
return;
|
|
3723
|
+
}
|
|
3724
|
+
try {
|
|
3725
|
+
const observation = await dismissCoachObservationForAccount(account, route.options.id, {});
|
|
3726
|
+
if (!observation) {
|
|
3727
|
+
notFound(response, 'Observation not found');
|
|
3728
|
+
return;
|
|
3729
|
+
}
|
|
3730
|
+
json(response, 200, { observation });
|
|
3731
|
+
} catch (err) {
|
|
3732
|
+
console.error('Coach observation dismiss error:', err.message);
|
|
3733
|
+
json(response, 500, { error: 'Failed to dismiss observation' });
|
|
3734
|
+
}
|
|
3735
|
+
return;
|
|
3736
|
+
}
|
|
3737
|
+
|
|
3499
3738
|
let snapshot;
|
|
3500
3739
|
try {
|
|
3501
3740
|
snapshot = loadSnapshotForAccount
|
|
@@ -4140,6 +4379,7 @@ export function createSyncServiceRequestHandler({
|
|
|
4140
4379
|
|
|
4141
4380
|
const queries = await import('./queries.js');
|
|
4142
4381
|
const exclude = parseExclude(body?.exclude);
|
|
4382
|
+
const requestedCoachObservation = normalizeAskCoachObservationFollowUp(body?.coachObservation);
|
|
4143
4383
|
let coachFacts = [];
|
|
4144
4384
|
if (listCoachFactsForAccount) {
|
|
4145
4385
|
try {
|
|
@@ -4166,10 +4406,21 @@ export function createSyncServiceRequestHandler({
|
|
|
4166
4406
|
history: enrichedSnapshots
|
|
4167
4407
|
};
|
|
4168
4408
|
}
|
|
4409
|
+
let coachObservations = [];
|
|
4410
|
+
if (listCurrentCoachObservationsForAccount) {
|
|
4411
|
+
try {
|
|
4412
|
+
coachObservations = await listCurrentCoachObservationsForAccount(account, { limit: requestedCoachObservation ? 10 : 3 }) ?? [];
|
|
4413
|
+
} catch (observationErr) {
|
|
4414
|
+
console.error('Coach observations read error (ask):', observationErr.message);
|
|
4415
|
+
}
|
|
4416
|
+
}
|
|
4417
|
+
const coachObservationFollowUp = selectAskCoachObservationFollowUp(requestedCoachObservation, coachObservations);
|
|
4169
4418
|
|
|
4170
|
-
const routedContext = queries.
|
|
4171
|
-
? queries.
|
|
4172
|
-
:
|
|
4419
|
+
const routedContext = coachObservationFollowUp && queries.askObservationFollowUpContext
|
|
4420
|
+
? queries.askObservationFollowUpContext(snapshot, question, coachObservationFollowUp, { exclude, coachFacts })
|
|
4421
|
+
: queries.askRoutedContext
|
|
4422
|
+
? queries.askRoutedContext(snapshot, question, { exclude, coachFacts, coachObservations })
|
|
4423
|
+
: { context: queries.askContext(snapshot, { exclude }), metadata: { route: 'legacy' } };
|
|
4173
4424
|
const persistedKind = persistedConversation?.kind ?? (conversationId?.startsWith('weekly-checkin:') ? 'weekly-checkin' : 'ask');
|
|
4174
4425
|
const incrementScorePrelude = formatIncrementScorePrelude(scoreSnapshots);
|
|
4175
4426
|
|
|
@@ -4198,7 +4449,8 @@ export function createSyncServiceRequestHandler({
|
|
|
4198
4449
|
historyTurnCount: canonicalHistory.filter((m) => m.role === 'user').length,
|
|
4199
4450
|
coachFactsIncluded: (routedContext.metadata?.includedCoachFactIds ?? []).length > 0,
|
|
4200
4451
|
coachFactIds: routedContext.metadata?.includedCoachFactIds ?? [],
|
|
4201
|
-
coachFactKinds: routedContext.metadata?.coachFactKinds ?? []
|
|
4452
|
+
coachFactKinds: routedContext.metadata?.coachFactKinds ?? [],
|
|
4453
|
+
coachObservationIds: routedContext.metadata?.includedCoachObservationIds ?? []
|
|
4202
4454
|
}
|
|
4203
4455
|
});
|
|
4204
4456
|
|
|
@@ -4255,6 +4507,15 @@ export function createSyncServiceRequestHandler({
|
|
|
4255
4507
|
});
|
|
4256
4508
|
});
|
|
4257
4509
|
}
|
|
4510
|
+
if (markCoachObservationSeenForAccount) {
|
|
4511
|
+
for (const observationId of routedContext.metadata?.includedCoachObservationIds ?? []) {
|
|
4512
|
+
try {
|
|
4513
|
+
await markCoachObservationSeenForAccount(account, observationId, {});
|
|
4514
|
+
} catch (seenErr) {
|
|
4515
|
+
console.error('Failed to mark coach observation seen:', seenErr.message);
|
|
4516
|
+
}
|
|
4517
|
+
}
|
|
4518
|
+
}
|
|
4258
4519
|
const updatedUserTurns = updatedMessages.filter((m) => m.role === 'user').length;
|
|
4259
4520
|
if (
|
|
4260
4521
|
persistedKind === 'weekly-checkin' &&
|