@modelprofile.com/flexharness 13.2.0 → 13.2.1
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/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.flexharness.core.d.ts +5 -0
- package/dist_ts/classes.flexharness.core.js +51 -8
- package/dist_ts/classes.flexharness.prompts.js +9 -8
- package/dist_ts/classes.flexharness.runs.js +7 -8
- package/dist_ts/interfaces.d.ts +0 -2
- package/dist_ts/interfaces.js +1 -1
- package/dist_ts/utils.json.js +3 -1
- package/dist_ts_chat_harness/functions.mapping.d.ts +0 -1
- package/dist_ts_chat_harness/functions.mapping.js +2 -5
- package/package.json +3 -3
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.flexharness.core.ts +45 -6
- package/ts/classes.flexharness.prompts.ts +6 -6
- package/ts/classes.flexharness.runs.ts +5 -5
- package/ts/interfaces.ts +0 -2
- package/ts/utils.json.ts +2 -0
- package/ts_chat_harness/functions.mapping.ts +1 -3
- package/ts_chat_harness/readme.md +1 -1
|
@@ -969,32 +969,42 @@ export class FlexHarnessCore<TScope> {
|
|
|
969
969
|
...stored.messages.map((message) => message.runId),
|
|
970
970
|
...stored.stagedTerminals.map((terminal) => terminal.runId),
|
|
971
971
|
]);
|
|
972
|
+
// The session's state is the latest real turn's: a cache-warm turn's terminal never sets it.
|
|
972
973
|
let latestTerminal: IFlexTerminalProjection | undefined;
|
|
973
974
|
for (const runId of runIds) {
|
|
974
975
|
const outcome = outcomes.get(runId);
|
|
975
976
|
const stage = stages.get(runId);
|
|
976
977
|
if (outcome === 'accepted' && stage?.status === 'completed') {
|
|
977
978
|
this.runs.replaceVisibleRun(stored, stage);
|
|
978
|
-
latestTerminal = stage;
|
|
979
|
+
if (!stage.userMessage.cacheWarm) latestTerminal = stage;
|
|
979
980
|
} else if (outcome !== 'accepted') {
|
|
980
981
|
const visible = stored.messages.filter((message) => message.runId === runId);
|
|
981
|
-
|
|
982
|
+
// A run that failed or was stopped keeps its terminal and its error: the staged one, or the
|
|
983
|
+
// one it published. Only a run that never reached its terminal is repaired as interrupted.
|
|
984
|
+
const ended = stage && stage.status !== 'completed'
|
|
985
|
+
? stage
|
|
986
|
+
: stage ? undefined : this.publishedTerminal(runId, visible);
|
|
987
|
+
if (ended) {
|
|
988
|
+
if (ended === stage) this.runs.replaceVisibleRun(stored, stage);
|
|
989
|
+
if (!ended.userMessage.cacheWarm) latestTerminal = ended;
|
|
990
|
+
} else if (visible.length > 0 || stage) {
|
|
982
991
|
const status = outcome === 'rejected' ? 'failed' : 'cancelled';
|
|
983
992
|
const terminal = this.repairTerminal(stored.session.sessionId, runId, visible, stage, status);
|
|
984
993
|
this.runs.replaceVisibleRun(stored, terminal);
|
|
985
|
-
latestTerminal = terminal;
|
|
994
|
+
if (!terminal.userMessage.cacheWarm) latestTerminal = terminal;
|
|
986
995
|
}
|
|
987
996
|
} else {
|
|
988
997
|
const visibleSuccess = stored.messages.some((message) =>
|
|
989
998
|
message.runId === runId && message.status === 'completed');
|
|
990
999
|
if (visibleSuccess) {
|
|
991
|
-
|
|
1000
|
+
const terminal = this.repairTerminal(
|
|
992
1001
|
stored.session.sessionId,
|
|
993
1002
|
runId,
|
|
994
1003
|
stored.messages.filter((message) => message.runId === runId),
|
|
995
1004
|
undefined,
|
|
996
1005
|
'completed',
|
|
997
1006
|
);
|
|
1007
|
+
if (!terminal.userMessage.cacheWarm) latestTerminal = terminal;
|
|
998
1008
|
}
|
|
999
1009
|
}
|
|
1000
1010
|
}
|
|
@@ -1033,17 +1043,22 @@ export class FlexHarnessCore<TScope> {
|
|
|
1033
1043
|
}
|
|
1034
1044
|
await this.reversions.recoverPendingCapture(state, stored, scopeId, scope, outcomes);
|
|
1035
1045
|
}
|
|
1046
|
+
// FlexHarness 13.2.0 marked a warm turn's activity with `cacheWarm`; no activity carries it now.
|
|
1047
|
+
delete (stored.session.activity as { cacheWarm?: true }).cacheWarm;
|
|
1036
1048
|
if (latestTerminal) {
|
|
1037
1049
|
const completedAt = latestTerminal.assistantMessage.completedAt ?? new Date().toISOString();
|
|
1050
|
+
const startedAt = stored.session.activity.runId === latestTerminal.runId
|
|
1051
|
+
? stored.session.activity.startedAt
|
|
1052
|
+
: undefined;
|
|
1038
1053
|
stored.session.status = latestTerminal.status === 'completed' ? 'idle' : latestTerminal.status;
|
|
1039
1054
|
stored.session.activity = {
|
|
1040
1055
|
runId: latestTerminal.runId,
|
|
1041
1056
|
status: latestTerminal.status === 'completed' ? 'idle' : latestTerminal.status,
|
|
1057
|
+
...(startedAt === undefined ? {} : { startedAt }),
|
|
1042
1058
|
completedAt,
|
|
1043
1059
|
...terminalActivityError(latestTerminal.assistantMessage),
|
|
1044
|
-
...(latestTerminal.userMessage.cacheWarm ? { cacheWarm: true as const } : {}),
|
|
1045
1060
|
};
|
|
1046
|
-
|
|
1061
|
+
stored.session.updatedAt = completedAt;
|
|
1047
1062
|
} else if (['scheduled', 'running', 'waiting_permission'].includes(stored.session.status)) {
|
|
1048
1063
|
const timestamp = new Date().toISOString();
|
|
1049
1064
|
stored.session.status = 'cancelled';
|
|
@@ -1083,6 +1098,30 @@ export class FlexHarnessCore<TScope> {
|
|
|
1083
1098
|
return beforeSession !== JSON.stringify(stored.session);
|
|
1084
1099
|
}
|
|
1085
1100
|
|
|
1101
|
+
/**
|
|
1102
|
+
* The failed or cancelled terminal a run published, read from its projected messages. A run
|
|
1103
|
+
* still running has its prompt or its last assistant message streaming; it has none.
|
|
1104
|
+
*/
|
|
1105
|
+
private publishedTerminal(
|
|
1106
|
+
runId: string,
|
|
1107
|
+
visible: IFlexMessage[],
|
|
1108
|
+
): IFlexTerminalProjection | undefined {
|
|
1109
|
+
if (visible.some((message) => message.status === 'streaming')) return undefined;
|
|
1110
|
+
const userMessage = visible.find((message) => message.role === 'user' && message.steerId === undefined);
|
|
1111
|
+
const assistantMessage = [...visible].reverse().find((message) => message.role === 'assistant');
|
|
1112
|
+
if (!userMessage || !assistantMessage) return undefined;
|
|
1113
|
+
const status = assistantMessage.status;
|
|
1114
|
+
if (status !== 'failed' && status !== 'cancelled') return undefined;
|
|
1115
|
+
return {
|
|
1116
|
+
runId,
|
|
1117
|
+
status,
|
|
1118
|
+
userMessage,
|
|
1119
|
+
assistantMessage,
|
|
1120
|
+
...(assistantMessage.model ? { model: assistantMessage.model } : {}),
|
|
1121
|
+
...(assistantMessage.usage ? { usage: assistantMessage.usage } : {}),
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1086
1125
|
private repairTerminal(
|
|
1087
1126
|
sessionId: string,
|
|
1088
1127
|
runId: string,
|
|
@@ -642,18 +642,18 @@ export class FlexHarnessPromptQueue<TScope> {
|
|
|
642
642
|
if (run.state.tombstones.has(run.sessionId) || run.state.sessions.get(run.sessionId) !== run.stored) {
|
|
643
643
|
throw new FlexHarnessAbortError('The session was deleted during prompt admission.');
|
|
644
644
|
}
|
|
645
|
+
// A cache-warm turn leaves the session's status, activity, updatedAt and preview as the
|
|
646
|
+
// last real turn left them; the active run alone keeps the session busy.
|
|
647
|
+
if (run.cacheWarm) return;
|
|
645
648
|
run.stored.session.status = run.scheduleKey ? 'scheduled' : 'running';
|
|
646
649
|
run.stored.session.activity = {
|
|
647
650
|
runId: run.runId,
|
|
648
651
|
status: run.scheduleKey ? 'scheduled' : 'running',
|
|
649
652
|
startedAt: reservation.userMessage.createdAt,
|
|
650
|
-
...(run.cacheWarm ? { cacheWarm: true as const } : {}),
|
|
651
653
|
};
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
if (preview !== undefined) run.stored.session.preview = preview;
|
|
656
|
-
}
|
|
654
|
+
run.stored.session.updatedAt = reservation.userMessage.createdAt;
|
|
655
|
+
const preview = run.stored.session.preview ?? sessionPreview(run.stored.messages);
|
|
656
|
+
if (preview !== undefined) run.stored.session.preview = preview;
|
|
657
657
|
});
|
|
658
658
|
} catch (error) {
|
|
659
659
|
if (containsStoreCommitUncertainty(error)) run.state.lifecycle = 'fenced';
|
|
@@ -965,7 +965,7 @@ export class FlexHarnessRuns<TScope> {
|
|
|
965
965
|
this.replaceVisibleRun(run.stored, terminal);
|
|
966
966
|
run.stored.stagedTerminals = run.stored.stagedTerminals.filter((entry) => entry.runId !== run.runId);
|
|
967
967
|
}, true);
|
|
968
|
-
if (!this.core.isTombstoned(run)) {
|
|
968
|
+
if (!this.core.isTombstoned(run) && !run.cacheWarm) {
|
|
969
969
|
await this.core.mutateScope(run.state, () => {
|
|
970
970
|
if (run.state.sessions.get(run.sessionId) !== run.stored) return;
|
|
971
971
|
const timestamp = terminal.assistantMessage.completedAt!;
|
|
@@ -975,9 +975,8 @@ export class FlexHarnessRuns<TScope> {
|
|
|
975
975
|
status: 'idle',
|
|
976
976
|
startedAt: run.stored.session.activity.startedAt,
|
|
977
977
|
completedAt: timestamp,
|
|
978
|
-
...(run.cacheWarm ? { cacheWarm: true as const } : {}),
|
|
979
978
|
};
|
|
980
|
-
|
|
979
|
+
run.stored.session.updatedAt = timestamp;
|
|
981
980
|
}, true);
|
|
982
981
|
}
|
|
983
982
|
}
|
|
@@ -990,6 +989,8 @@ export class FlexHarnessRuns<TScope> {
|
|
|
990
989
|
this.replaceVisibleRun(run.stored, terminal);
|
|
991
990
|
run.stored.stagedTerminals = run.stored.stagedTerminals.filter((entry) => entry.runId !== run.runId);
|
|
992
991
|
}, true);
|
|
992
|
+
// A failed cache-warm turn reports through `cache.warm.stopped`; the session keeps its state.
|
|
993
|
+
if (run.cacheWarm) return;
|
|
993
994
|
await this.core.mutateScope(run.state, () => {
|
|
994
995
|
if (run.state.sessions.get(run.sessionId) !== run.stored) return;
|
|
995
996
|
const timestamp = terminal.assistantMessage.completedAt!;
|
|
@@ -1001,9 +1002,8 @@ export class FlexHarnessRuns<TScope> {
|
|
|
1001
1002
|
startedAt: run.stored.session.activity.startedAt,
|
|
1002
1003
|
completedAt: timestamp,
|
|
1003
1004
|
...terminalActivityError(terminal.assistantMessage),
|
|
1004
|
-
...(run.cacheWarm ? { cacheWarm: true as const } : {}),
|
|
1005
1005
|
};
|
|
1006
|
-
|
|
1006
|
+
run.stored.session.updatedAt = timestamp;
|
|
1007
1007
|
}, true);
|
|
1008
1008
|
}
|
|
1009
1009
|
|
package/ts/interfaces.ts
CHANGED
|
@@ -239,8 +239,6 @@ export interface IFlexSessionActivity {
|
|
|
239
239
|
limit?: TFlexModelLimit;
|
|
240
240
|
/** The permission whose rejection stopped the last run, next to its error. */
|
|
241
241
|
rejectedPermissionId?: string;
|
|
242
|
-
/** The run is or was a cache-warm turn; see {@link IFlexMessage.cacheWarm}. */
|
|
243
|
-
cacheWarm?: true;
|
|
244
242
|
}
|
|
245
243
|
|
|
246
244
|
export interface IFlexSession {
|
package/ts/utils.json.ts
CHANGED
|
@@ -578,6 +578,8 @@ function validateSession(value: unknown, path: string): IValidatedSessionIdentit
|
|
|
578
578
|
requireOptionalString(activity.error, `${path}.activity.error`);
|
|
579
579
|
requireOptionalModelLimit(activity.limit, `${path}.activity.limit`);
|
|
580
580
|
requireOptionalString(activity.rejectedPermissionId, `${path}.activity.rejectedPermissionId`);
|
|
581
|
+
// FlexHarness 13.2.0 stored a warm turn's activity with `cacheWarm`; session repair on load
|
|
582
|
+
// removes it and restores the latest real turn's activity.
|
|
581
583
|
if (activity.cacheWarm !== undefined && activity.cacheWarm !== true) {
|
|
582
584
|
throw new FlexHarnessStoreFormatError(`${path}.activity.cacheWarm must be true when present.`);
|
|
583
585
|
}
|
|
@@ -215,9 +215,7 @@ export function flexPermissionToHarness(
|
|
|
215
215
|
};
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
-
/** A cache-warm turn is idle time to the user: its activity shows as idle. */
|
|
219
218
|
export function flexSessionToHarnessStatus(session: IFlexSession): plugins.IHarnessStatus {
|
|
220
|
-
if (session.activity.cacheWarm) return { type: 'idle' };
|
|
221
219
|
switch (session.activity.status) {
|
|
222
220
|
case 'running':
|
|
223
221
|
case 'scheduled':
|
|
@@ -249,7 +247,7 @@ export function flexSessionToHarnessMeta(session: IFlexSession): plugins.IHarnes
|
|
|
249
247
|
title: session.title ?? session.sessionId,
|
|
250
248
|
...(epoch(session.createdAt) === undefined ? {} : { createdAt: epoch(session.createdAt) }),
|
|
251
249
|
...(epoch(session.updatedAt) === undefined ? {} : { updatedAt: epoch(session.updatedAt) }),
|
|
252
|
-
state:
|
|
250
|
+
state: sessionCardStates[session.activity.status],
|
|
253
251
|
};
|
|
254
252
|
}
|
|
255
253
|
|
|
@@ -78,7 +78,7 @@ The host forwards every event of the harness instance, of every session, in orde
|
|
|
78
78
|
- Permission rows stay in the transcript once answered, showing the decision. A request its run leaves unanswered, because the run was stopped or failed, is removed when the run finishes.
|
|
79
79
|
- FlexHarness emits no event for project state. After a run, or after a project tool's row completes, read `getProjectState()` and pass it to `applyProjectState()` for the task list and the scratchpad.
|
|
80
80
|
- `questions` is always empty: FlexHarness has no question requests.
|
|
81
|
-
- A cache-warm turn (see the FlexHarness readme, Keeping the Prompt Cache Warm) has no rows and leaves the status
|
|
81
|
+
- A cache-warm turn (see the FlexHarness readme, Keeping the Prompt Cache Warm) has no rows and leaves the status and the session card as the last real turn left them and `activeQueueId` unset, so a steer never joins it. Its usage still counts in `usage`: it is spent.
|
|
82
82
|
- Messages the session has not taken in yet (a queued prompt, a steer) carry no text in FlexHarness events. The host that sent them shows them as pending rows.
|
|
83
83
|
- An attachment row needs a name, a media type and a size. The public projection keeps no payload and states these only when the prompt did, so an attachment without all three, such as a remote URL of unknown size, has no row.
|
|
84
84
|
|