tinker-agent 1.8.0 → 1.10.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/CHANGELOG.md +41 -1
- package/README.md +65 -6
- package/package.json +1 -1
- package/src/agent/loop.ts +13 -0
- package/src/agent/runtime-session.ts +165 -0
- package/src/agent/session-ledger.ts +20 -3
- package/src/cli/config.ts +11 -2
- package/src/cli/model-profiles.ts +58 -0
- package/src/cli/public-config-contract.ts +73 -7
- package/src/cli/run-runner.ts +4 -1
- package/src/cli/runner-dependencies.ts +35 -10
- package/src/cli/tui-memory.ts +4 -0
- package/src/cli/tui-runner.tsx +8 -1
- package/src/context/context-automation-policy.ts +22 -21
- package/src/context/context-manager.ts +91 -15
- package/src/context/context-policy.ts +0 -2
- package/src/context/context-swap-renderer.ts +1 -1
- package/src/context/prefix-retirement-planner.ts +58 -8
- package/src/context/recall-retirement-contract.ts +5 -4
- package/src/context/swap-planner.ts +33 -27
- package/src/events/stdout-event-printer.ts +17 -0
- package/src/model/fake-model-client.ts +26 -16
- package/src/model/model-api.ts +12 -0
- package/src/model/model-client.ts +9 -1
- package/src/model/moonshot-input-token-estimator.ts +5 -1
- package/src/model/openai-chat-mapping.ts +2 -24
- package/src/model/openai-chat-model-client.ts +18 -294
- package/src/model/openai-image-mapping.ts +20 -0
- package/src/model/openai-model-utils.ts +304 -0
- package/src/model/openai-responses-mapping.ts +532 -0
- package/src/model/openai-responses-model-client.ts +295 -0
- package/src/model/openai-responses-stream.ts +96 -0
- package/src/model/openai-responses-token-estimator.ts +155 -0
- package/src/model/reasoning-effort.ts +60 -0
- package/src/observation/observation-builder.ts +7 -0
- package/src/session/session-catalog.ts +2 -2
- package/src/session/session-history-reader.ts +6 -1
- package/src/session/session-schema.ts +268 -4
- package/src/session/session-store.ts +106 -26
- package/src/skills/skill-context.ts +2 -2
- package/src/tools/bounded-output-preview.ts +276 -0
- package/src/tools/recall.ts +67 -36
- package/src/tools/registry.ts +9 -2
- package/src/tools/task-output-snapshot.ts +6 -22
- package/src/tools/task-output.ts +23 -27
- package/src/tools/types.ts +19 -0
- package/src/tools/update-plan.ts +166 -0
- package/src/tui/app.tsx +82 -5
- package/src/tui/components/plan-view.tsx +43 -0
- package/src/tui/components/prompt-input.tsx +9 -1
- package/src/tui/components/timeline.tsx +7 -0
- package/src/tui/event-store.ts +28 -2
- package/src/tui/slash-commands.ts +20 -0
- package/src/tui/tui-session-controller.ts +7 -0
|
@@ -442,7 +442,9 @@ const schemaDefinitions: readonly SchemaDefinition[] = [
|
|
|
442
442
|
WHERE role IN ('user', 'assistant', 'tool')
|
|
443
443
|
AND content IS NOT NULL
|
|
444
444
|
AND length(content) > 0
|
|
445
|
-
AND NOT (
|
|
445
|
+
AND NOT (
|
|
446
|
+
role = 'tool' AND name IN ('Recall', 'RecallSearch', 'RecallGet')
|
|
447
|
+
)`,
|
|
446
448
|
},
|
|
447
449
|
{
|
|
448
450
|
type: "table",
|
|
@@ -462,7 +464,9 @@ const schemaDefinitions: readonly SchemaDefinition[] = [
|
|
|
462
464
|
WHEN NEW.role IN ('user', 'assistant', 'tool')
|
|
463
465
|
AND NEW.content IS NOT NULL
|
|
464
466
|
AND length(NEW.content) > 0
|
|
465
|
-
AND NOT (
|
|
467
|
+
AND NOT (
|
|
468
|
+
NEW.role = 'tool' AND NEW.name IN ('Recall', 'RecallSearch', 'RecallGet')
|
|
469
|
+
)
|
|
466
470
|
BEGIN
|
|
467
471
|
INSERT INTO message_fts(rowid, content)
|
|
468
472
|
VALUES (NEW.rowid, NEW.content);
|
|
@@ -595,7 +599,12 @@ const schemaDefinitions: readonly SchemaDefinition[] = [
|
|
|
595
599
|
AND pf.kind = 'user' AND pf.state = 'closed'
|
|
596
600
|
AND pf.first_ordinal = NEW.keep_from_ordinal
|
|
597
601
|
AND pf.last_ordinal = NEW.keep_from_ordinal
|
|
598
|
-
AND
|
|
602
|
+
AND (
|
|
603
|
+
t.status <> 'open' OR (
|
|
604
|
+
NOT EXISTS (SELECT 1 FROM iterations WHERE outcome = 'open') AND
|
|
605
|
+
NOT EXISTS (SELECT 1 FROM protocol_frames WHERE state = 'open')
|
|
606
|
+
)
|
|
607
|
+
)
|
|
599
608
|
) AND
|
|
600
609
|
EXISTS (
|
|
601
610
|
SELECT 1 FROM messages m
|
|
@@ -850,6 +859,170 @@ export const SESSION_SCHEMA_V9_FINGERPRINT = sha256(
|
|
|
850
859
|
}),
|
|
851
860
|
);
|
|
852
861
|
|
|
862
|
+
const PRE_SPLIT_RECALL_SCHEMA_V9_FINGERPRINT =
|
|
863
|
+
"27c61d806778689f88211b6eaa6dd9dc3a730dfd4133c862006140576b2ecd10";
|
|
864
|
+
|
|
865
|
+
const PRE_ACTIVE_TURN_RETIREMENT_SCHEMA_V9_FINGERPRINT =
|
|
866
|
+
"263a0415b343a922efc65aab4a3387a8b31de18e82245ce67b9296b7a02f4a26";
|
|
867
|
+
|
|
868
|
+
const ACTIVE_TURN_RETIREMENT_TRIGGER_FRAGMENT = `AND (
|
|
869
|
+
t.status <> 'open' OR (
|
|
870
|
+
NOT EXISTS (SELECT 1 FROM iterations WHERE outcome = 'open') AND
|
|
871
|
+
NOT EXISTS (SELECT 1 FROM protocol_frames WHERE state = 'open')
|
|
872
|
+
)
|
|
873
|
+
)`;
|
|
874
|
+
|
|
875
|
+
const preActiveTurnRetirementSchemaDefinitions = replaceSchemaDefinitionSql(
|
|
876
|
+
schemaDefinitions,
|
|
877
|
+
"trigger",
|
|
878
|
+
"context_revisions_validate_insert",
|
|
879
|
+
(sql) =>
|
|
880
|
+
replaceExactSqlFragment(
|
|
881
|
+
sql,
|
|
882
|
+
ACTIVE_TURN_RETIREMENT_TRIGGER_FRAGMENT,
|
|
883
|
+
"AND t.status <> 'open'",
|
|
884
|
+
),
|
|
885
|
+
);
|
|
886
|
+
|
|
887
|
+
const preSplitRecallSchemaDefinitions = replaceSchemaDefinitionSql(
|
|
888
|
+
replaceSchemaDefinitionSql(
|
|
889
|
+
preActiveTurnRetirementSchemaDefinitions,
|
|
890
|
+
"view",
|
|
891
|
+
"recall_documents",
|
|
892
|
+
() => `CREATE VIEW recall_documents AS
|
|
893
|
+
SELECT rowid AS docid, content
|
|
894
|
+
FROM messages
|
|
895
|
+
WHERE role IN ('user', 'assistant', 'tool')
|
|
896
|
+
AND content IS NOT NULL
|
|
897
|
+
AND length(content) > 0
|
|
898
|
+
AND NOT (role = 'tool' AND name = 'Recall')`,
|
|
899
|
+
),
|
|
900
|
+
"trigger",
|
|
901
|
+
"messages_recall_index",
|
|
902
|
+
() => `CREATE TRIGGER messages_recall_index
|
|
903
|
+
AFTER INSERT ON messages
|
|
904
|
+
WHEN NEW.role IN ('user', 'assistant', 'tool')
|
|
905
|
+
AND NEW.content IS NOT NULL
|
|
906
|
+
AND length(NEW.content) > 0
|
|
907
|
+
AND NOT (NEW.role = 'tool' AND NEW.name = 'Recall')
|
|
908
|
+
BEGIN
|
|
909
|
+
INSERT INTO message_fts(rowid, content)
|
|
910
|
+
VALUES (NEW.rowid, NEW.content);
|
|
911
|
+
END`,
|
|
912
|
+
);
|
|
913
|
+
|
|
914
|
+
export function upgradeRecallIndexContract(database: Database): boolean {
|
|
915
|
+
const applicationId = Number(singlePragmaValue(database, "PRAGMA application_id"));
|
|
916
|
+
const userVersion = Number(singlePragmaValue(database, "PRAGMA user_version"));
|
|
917
|
+
if (
|
|
918
|
+
applicationId !== SESSION_APPLICATION_ID ||
|
|
919
|
+
userVersion !== SESSION_SCHEMA_VERSION
|
|
920
|
+
) {
|
|
921
|
+
return false;
|
|
922
|
+
}
|
|
923
|
+
const meta = database
|
|
924
|
+
.query(
|
|
925
|
+
`SELECT schema_version, schema_fingerprint
|
|
926
|
+
FROM session_meta WHERE singleton = 1`,
|
|
927
|
+
)
|
|
928
|
+
.get() as { schema_version: unknown; schema_fingerprint: unknown } | null;
|
|
929
|
+
if (
|
|
930
|
+
meta === null ||
|
|
931
|
+
Number(meta.schema_version) !== SESSION_SCHEMA_VERSION ||
|
|
932
|
+
meta.schema_fingerprint !== PRE_SPLIT_RECALL_SCHEMA_V9_FINGERPRINT
|
|
933
|
+
) {
|
|
934
|
+
return false;
|
|
935
|
+
}
|
|
936
|
+
const recallView = schemaDefinitions.find(
|
|
937
|
+
(definition) =>
|
|
938
|
+
definition.type === "view" && definition.name === "recall_documents",
|
|
939
|
+
);
|
|
940
|
+
const recallTrigger = schemaDefinitions.find(
|
|
941
|
+
(definition) =>
|
|
942
|
+
definition.type === "trigger" && definition.name === "messages_recall_index",
|
|
943
|
+
);
|
|
944
|
+
const metadataTrigger = schemaDefinitions.find(
|
|
945
|
+
(definition) =>
|
|
946
|
+
definition.type === "trigger" &&
|
|
947
|
+
definition.name === "session_meta_monotonic_update",
|
|
948
|
+
);
|
|
949
|
+
if (
|
|
950
|
+
recallView === undefined ||
|
|
951
|
+
recallTrigger === undefined ||
|
|
952
|
+
metadataTrigger === undefined
|
|
953
|
+
) {
|
|
954
|
+
throw new Error("Recall index schema definitions are missing.");
|
|
955
|
+
}
|
|
956
|
+
database.exec("DROP TRIGGER messages_recall_index");
|
|
957
|
+
database.exec("DROP VIEW recall_documents");
|
|
958
|
+
database.exec(recallView.sql);
|
|
959
|
+
database.exec(recallTrigger.sql);
|
|
960
|
+
rebuildRecallIndex(database);
|
|
961
|
+
database.exec("DROP TRIGGER session_meta_monotonic_update");
|
|
962
|
+
database
|
|
963
|
+
.query(
|
|
964
|
+
`UPDATE session_meta SET schema_fingerprint = ?
|
|
965
|
+
WHERE singleton = 1 AND schema_fingerprint = ?`,
|
|
966
|
+
)
|
|
967
|
+
.run(
|
|
968
|
+
PRE_ACTIVE_TURN_RETIREMENT_SCHEMA_V9_FINGERPRINT,
|
|
969
|
+
PRE_SPLIT_RECALL_SCHEMA_V9_FINGERPRINT,
|
|
970
|
+
);
|
|
971
|
+
database.exec(metadataTrigger.sql);
|
|
972
|
+
return true;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
export function upgradeActiveTurnRetirementContract(database: Database): boolean {
|
|
976
|
+
const applicationId = Number(singlePragmaValue(database, "PRAGMA application_id"));
|
|
977
|
+
const userVersion = Number(singlePragmaValue(database, "PRAGMA user_version"));
|
|
978
|
+
if (
|
|
979
|
+
applicationId !== SESSION_APPLICATION_ID ||
|
|
980
|
+
userVersion !== SESSION_SCHEMA_VERSION
|
|
981
|
+
) {
|
|
982
|
+
return false;
|
|
983
|
+
}
|
|
984
|
+
const meta = database
|
|
985
|
+
.query(
|
|
986
|
+
`SELECT schema_version, schema_fingerprint
|
|
987
|
+
FROM session_meta WHERE singleton = 1`,
|
|
988
|
+
)
|
|
989
|
+
.get() as { schema_version: unknown; schema_fingerprint: unknown } | null;
|
|
990
|
+
if (
|
|
991
|
+
meta === null ||
|
|
992
|
+
Number(meta.schema_version) !== SESSION_SCHEMA_VERSION ||
|
|
993
|
+
meta.schema_fingerprint !== PRE_ACTIVE_TURN_RETIREMENT_SCHEMA_V9_FINGERPRINT
|
|
994
|
+
) {
|
|
995
|
+
return false;
|
|
996
|
+
}
|
|
997
|
+
const revisionTrigger = schemaDefinitions.find(
|
|
998
|
+
(definition) =>
|
|
999
|
+
definition.type === "trigger" &&
|
|
1000
|
+
definition.name === "context_revisions_validate_insert",
|
|
1001
|
+
);
|
|
1002
|
+
const metadataTrigger = schemaDefinitions.find(
|
|
1003
|
+
(definition) =>
|
|
1004
|
+
definition.type === "trigger" &&
|
|
1005
|
+
definition.name === "session_meta_monotonic_update",
|
|
1006
|
+
);
|
|
1007
|
+
if (revisionTrigger === undefined || metadataTrigger === undefined) {
|
|
1008
|
+
throw new Error("Active-turn retirement schema definitions are missing.");
|
|
1009
|
+
}
|
|
1010
|
+
database.exec("DROP TRIGGER context_revisions_validate_insert");
|
|
1011
|
+
database.exec(revisionTrigger.sql);
|
|
1012
|
+
database.exec("DROP TRIGGER session_meta_monotonic_update");
|
|
1013
|
+
database
|
|
1014
|
+
.query(
|
|
1015
|
+
`UPDATE session_meta SET schema_fingerprint = ?
|
|
1016
|
+
WHERE singleton = 1 AND schema_fingerprint = ?`,
|
|
1017
|
+
)
|
|
1018
|
+
.run(
|
|
1019
|
+
SESSION_SCHEMA_V9_FINGERPRINT,
|
|
1020
|
+
PRE_ACTIVE_TURN_RETIREMENT_SCHEMA_V9_FINGERPRINT,
|
|
1021
|
+
);
|
|
1022
|
+
database.exec(metadataTrigger.sql);
|
|
1023
|
+
return true;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
853
1026
|
export function configureWritableDatabase(database: Database): void {
|
|
854
1027
|
database.exec("PRAGMA foreign_keys = ON");
|
|
855
1028
|
const journal = singlePragmaValue(database, "PRAGMA journal_mode = WAL");
|
|
@@ -893,7 +1066,61 @@ export function reinstallSessionCloneTriggers(database: Database): void {
|
|
|
893
1066
|
}
|
|
894
1067
|
}
|
|
895
1068
|
|
|
1069
|
+
export function verifyReadableSessionSchema(
|
|
1070
|
+
database: Database,
|
|
1071
|
+
sessionId?: SessionId,
|
|
1072
|
+
): "current" | "migratable" {
|
|
1073
|
+
verifySessionSchemaVersion(database, sessionId);
|
|
1074
|
+
const meta = database
|
|
1075
|
+
.query(
|
|
1076
|
+
`SELECT schema_version, schema_fingerprint
|
|
1077
|
+
FROM session_meta WHERE singleton = 1`,
|
|
1078
|
+
)
|
|
1079
|
+
.get() as { schema_version: unknown; schema_fingerprint: unknown } | null;
|
|
1080
|
+
if (
|
|
1081
|
+
meta === null ||
|
|
1082
|
+
Number(meta.schema_version) !== SESSION_SCHEMA_VERSION ||
|
|
1083
|
+
typeof meta.schema_fingerprint !== "string"
|
|
1084
|
+
) {
|
|
1085
|
+
throw new SessionError(
|
|
1086
|
+
"SESSION_SCHEMA_INVALID",
|
|
1087
|
+
"verify_readable_schema",
|
|
1088
|
+
"Session schema metadata is invalid.",
|
|
1089
|
+
{ sessionId },
|
|
1090
|
+
);
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
let expectedDefinitions: readonly SchemaDefinition[];
|
|
1094
|
+
let compatibility: "current" | "migratable";
|
|
1095
|
+
if (meta.schema_fingerprint === SESSION_SCHEMA_V9_FINGERPRINT) {
|
|
1096
|
+
expectedDefinitions = schemaDefinitions;
|
|
1097
|
+
compatibility = "current";
|
|
1098
|
+
} else if (
|
|
1099
|
+
meta.schema_fingerprint === PRE_ACTIVE_TURN_RETIREMENT_SCHEMA_V9_FINGERPRINT
|
|
1100
|
+
) {
|
|
1101
|
+
expectedDefinitions = preActiveTurnRetirementSchemaDefinitions;
|
|
1102
|
+
compatibility = "migratable";
|
|
1103
|
+
} else if (meta.schema_fingerprint === PRE_SPLIT_RECALL_SCHEMA_V9_FINGERPRINT) {
|
|
1104
|
+
expectedDefinitions = preSplitRecallSchemaDefinitions;
|
|
1105
|
+
compatibility = "migratable";
|
|
1106
|
+
} else {
|
|
1107
|
+
throw new SessionError(
|
|
1108
|
+
"SESSION_SCHEMA_INVALID",
|
|
1109
|
+
"verify_readable_schema",
|
|
1110
|
+
"Session schema is neither current nor a recognized migration source.",
|
|
1111
|
+
{ sessionId },
|
|
1112
|
+
);
|
|
1113
|
+
}
|
|
1114
|
+
verifySessionSchemaDefinitions(database, expectedDefinitions, sessionId);
|
|
1115
|
+
return compatibility;
|
|
1116
|
+
}
|
|
1117
|
+
|
|
896
1118
|
export function verifySessionSchema(database: Database, sessionId?: SessionId): void {
|
|
1119
|
+
verifySessionSchemaVersion(database, sessionId);
|
|
1120
|
+
verifySessionSchemaDefinitions(database, schemaDefinitions, sessionId);
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
function verifySessionSchemaVersion(database: Database, sessionId?: SessionId): void {
|
|
897
1124
|
const applicationId = Number(singlePragmaValue(database, "PRAGMA application_id"));
|
|
898
1125
|
const userVersion = Number(singlePragmaValue(database, "PRAGMA user_version"));
|
|
899
1126
|
if (
|
|
@@ -907,7 +1134,13 @@ export function verifySessionSchema(database: Database, sessionId?: SessionId):
|
|
|
907
1134
|
{ sessionId },
|
|
908
1135
|
);
|
|
909
1136
|
}
|
|
1137
|
+
}
|
|
910
1138
|
|
|
1139
|
+
function verifySessionSchemaDefinitions(
|
|
1140
|
+
database: Database,
|
|
1141
|
+
expectedDefinitions: readonly SchemaDefinition[],
|
|
1142
|
+
sessionId?: SessionId,
|
|
1143
|
+
): void {
|
|
911
1144
|
const actual = database
|
|
912
1145
|
.query(
|
|
913
1146
|
`SELECT type, name, sql FROM sqlite_schema
|
|
@@ -918,7 +1151,7 @@ export function verifySessionSchema(database: Database, sessionId?: SessionId):
|
|
|
918
1151
|
const actualByName = new Map(
|
|
919
1152
|
actual.map((entry) => [`${entry.type}:${entry.name}`, normalizeSql(entry.sql)]),
|
|
920
1153
|
);
|
|
921
|
-
for (const expected of
|
|
1154
|
+
for (const expected of expectedDefinitions) {
|
|
922
1155
|
const actualSql = actualByName.get(`${expected.type}:${expected.name}`);
|
|
923
1156
|
if (actualSql !== normalizeSql(expected.sql)) {
|
|
924
1157
|
throw new SessionError(
|
|
@@ -1053,3 +1286,34 @@ function singlePragmaValue(database: Database, sql: string): unknown {
|
|
|
1053
1286
|
function normalizeSql(sql: string): string {
|
|
1054
1287
|
return sql.replace(/\s+/g, " ").trim().replace(/;$/, "").toLowerCase();
|
|
1055
1288
|
}
|
|
1289
|
+
|
|
1290
|
+
function replaceSchemaDefinitionSql(
|
|
1291
|
+
definitions: readonly SchemaDefinition[],
|
|
1292
|
+
type: SchemaDefinition["type"],
|
|
1293
|
+
name: string,
|
|
1294
|
+
replace: (sql: string) => string,
|
|
1295
|
+
): readonly SchemaDefinition[] {
|
|
1296
|
+
const target = definitions.find(
|
|
1297
|
+
(definition) => definition.type === type && definition.name === name,
|
|
1298
|
+
);
|
|
1299
|
+
if (target === undefined) {
|
|
1300
|
+
throw new Error(`Schema definition is missing: ${type} ${name}.`);
|
|
1301
|
+
}
|
|
1302
|
+
return definitions.map((definition) =>
|
|
1303
|
+
definition === target
|
|
1304
|
+
? { ...definition, sql: replace(definition.sql) }
|
|
1305
|
+
: definition,
|
|
1306
|
+
);
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
function replaceExactSqlFragment(
|
|
1310
|
+
sql: string,
|
|
1311
|
+
currentFragment: string,
|
|
1312
|
+
previousFragment: string,
|
|
1313
|
+
): string {
|
|
1314
|
+
const replaced = sql.replace(currentFragment, previousFragment);
|
|
1315
|
+
if (replaced === sql) {
|
|
1316
|
+
throw new Error("Schema definition does not contain its historical fragment.");
|
|
1317
|
+
}
|
|
1318
|
+
return replaced;
|
|
1319
|
+
}
|
|
@@ -28,7 +28,10 @@ import {
|
|
|
28
28
|
createModelContextProfile,
|
|
29
29
|
type ModelContextProfile,
|
|
30
30
|
} from "../model/model-context-profile";
|
|
31
|
-
import
|
|
31
|
+
import {
|
|
32
|
+
MODEL_MESSAGE_PROTOCOL_ADAPTERS,
|
|
33
|
+
type ModelMessageProtocol,
|
|
34
|
+
} from "../model/model-client";
|
|
32
35
|
import type { InputTokenEstimatorCompatibility } from "../model/input-token-estimator";
|
|
33
36
|
import type { ToolDefinition, ToolRawResult } from "../tools/types";
|
|
34
37
|
import { sha256, stableJsonStringify } from "../model/model-request-preflight";
|
|
@@ -45,7 +48,6 @@ import {
|
|
|
45
48
|
ContextRevisionCompiler,
|
|
46
49
|
createInitialContextRevision,
|
|
47
50
|
} from "../context/context-revision-compiler";
|
|
48
|
-
import { recallFirstRetirementPolicyV1 } from "../context/context-policy";
|
|
49
51
|
import {
|
|
50
52
|
ContextSwapRenderer,
|
|
51
53
|
SWAP_OBSERVATION_FORMAT,
|
|
@@ -90,7 +92,10 @@ import { ImageAssetStore } from "../image/image-asset-store";
|
|
|
90
92
|
import type { MeasuredContextAnchor } from "../agent/context-meter";
|
|
91
93
|
import type { ProjectInstructionManifest } from "../instructions/project-instructions";
|
|
92
94
|
import { SUPPORTED_RECALL_RETIREMENT_CONTRACT_VERSIONS } from "../context/recall-retirement-contract";
|
|
93
|
-
import type {
|
|
95
|
+
import type {
|
|
96
|
+
ActiveTurnBoundary,
|
|
97
|
+
ClosedTurnBoundary,
|
|
98
|
+
} from "../context/prefix-retirement-planner";
|
|
94
99
|
import {
|
|
95
100
|
AdmissionStaleError,
|
|
96
101
|
InMemorySessionLedger,
|
|
@@ -106,11 +111,14 @@ import { SessionLease } from "./session-lock";
|
|
|
106
111
|
import {
|
|
107
112
|
SESSION_SCHEMA_V9_FINGERPRINT,
|
|
108
113
|
SESSION_SCHEMA_VERSION,
|
|
114
|
+
upgradeActiveTurnRetirementContract,
|
|
115
|
+
upgradeRecallIndexContract,
|
|
109
116
|
configureWritableDatabase,
|
|
110
117
|
createSessionSchema,
|
|
111
118
|
dropSessionCloneTriggers,
|
|
112
119
|
rebuildRecallIndex,
|
|
113
120
|
reinstallSessionCloneTriggers,
|
|
121
|
+
verifyReadableSessionSchema,
|
|
114
122
|
verifyRecallIndex,
|
|
115
123
|
verifySessionSchema,
|
|
116
124
|
verifySqliteIntegrity,
|
|
@@ -231,6 +239,7 @@ export type CommitSwapRevisionInput = {
|
|
|
231
239
|
nextActiveOverrideManifestSha256: string;
|
|
232
240
|
canonicalSequenceSha256: string;
|
|
233
241
|
renderedMessageSha256: string;
|
|
242
|
+
activeTurnId?: TurnId;
|
|
234
243
|
};
|
|
235
244
|
|
|
236
245
|
export type CommitSwapRevisionFaultStage =
|
|
@@ -288,6 +297,7 @@ export type CommitPrefixRetirementRevisionInput = {
|
|
|
288
297
|
nextActiveOverrideManifestSha256: string;
|
|
289
298
|
canonicalSequenceSha256: string;
|
|
290
299
|
renderedMessageSha256: string;
|
|
300
|
+
activeTurnId?: TurnId;
|
|
291
301
|
};
|
|
292
302
|
|
|
293
303
|
export type CommitPrefixRetirementRevisionFaultStage =
|
|
@@ -564,8 +574,13 @@ export class SessionStore implements SessionLedgerCommitter {
|
|
|
564
574
|
let database: Database | undefined;
|
|
565
575
|
try {
|
|
566
576
|
database = openWritableDatabase(databasePath);
|
|
567
|
-
verifySessionSchema(database, input.sessionId);
|
|
568
577
|
verifySqliteIntegrity(database, input.sessionId);
|
|
578
|
+
verifyReadableSessionSchema(database, input.sessionId);
|
|
579
|
+
const recallIndexContractUpgraded = runTransaction(database, () =>
|
|
580
|
+
upgradeRecallIndexContract(database!),
|
|
581
|
+
);
|
|
582
|
+
runTransaction(database, () => upgradeActiveTurnRetirementContract(database!));
|
|
583
|
+
verifySessionSchema(database, input.sessionId);
|
|
569
584
|
const store = new SessionStore(database, lease, {
|
|
570
585
|
sessionId: input.sessionId,
|
|
571
586
|
workspaceRoot,
|
|
@@ -573,6 +588,7 @@ export class SessionStore implements SessionLedgerCommitter {
|
|
|
573
588
|
databasePath,
|
|
574
589
|
clock,
|
|
575
590
|
});
|
|
591
|
+
store.recallIndexRebuilt = recallIndexContractUpgraded;
|
|
576
592
|
const meta = store.readMeta();
|
|
577
593
|
if (meta.initializationState !== "ready" && input.allowIncomplete !== true) {
|
|
578
594
|
throw new SessionError(
|
|
@@ -926,25 +942,38 @@ export class SessionStore implements SessionLedgerCommitter {
|
|
|
926
942
|
}
|
|
927
943
|
|
|
928
944
|
assertContextRevisionIdle(): void {
|
|
945
|
+
this.assertContextRevisionBoundary();
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
assertContextRevisionBoundary(activeTurnId?: TurnId): void {
|
|
929
949
|
this.requireOpen();
|
|
930
950
|
const row = this.database
|
|
931
951
|
.query(
|
|
932
952
|
`SELECT
|
|
933
953
|
(SELECT COUNT(*) FROM turns WHERE status = 'open') AS open_turns,
|
|
954
|
+
(SELECT turn_id FROM turns WHERE status = 'open' LIMIT 1) AS open_turn_id,
|
|
934
955
|
(SELECT COUNT(*) FROM iterations WHERE outcome = 'open') AS open_iterations,
|
|
935
956
|
(SELECT COUNT(*) FROM protocol_frames WHERE state = 'open') AS open_frames`,
|
|
936
957
|
)
|
|
937
958
|
.get() as Record<string, unknown> | null;
|
|
959
|
+
const openTurnCount =
|
|
960
|
+
row === null ? -1 : numberFromSql(row.open_turns, "open_turns");
|
|
961
|
+
const openTurnId =
|
|
962
|
+
row === null
|
|
963
|
+
? null
|
|
964
|
+
: (nullableStringFromSql(row.open_turn_id, "open_turn_id") as TurnId | null);
|
|
938
965
|
if (
|
|
939
966
|
row === null ||
|
|
940
|
-
|
|
967
|
+
(activeTurnId === undefined
|
|
968
|
+
? openTurnCount !== 0
|
|
969
|
+
: openTurnCount !== 1 || openTurnId !== activeTurnId) ||
|
|
941
970
|
numberFromSql(row.open_iterations, "open_iterations") !== 0 ||
|
|
942
971
|
numberFromSql(row.open_frames, "open_frames") !== 0
|
|
943
972
|
) {
|
|
944
973
|
throw new SessionError(
|
|
945
974
|
"SESSION_INTEGRITY_FAILED",
|
|
946
|
-
"
|
|
947
|
-
"Context revision requires a
|
|
975
|
+
"assert_context_revision_boundary",
|
|
976
|
+
"Context revision requires an idle store or a closed active-turn iteration boundary.",
|
|
948
977
|
{ sessionId: this.sessionId },
|
|
949
978
|
);
|
|
950
979
|
}
|
|
@@ -955,16 +984,35 @@ export class SessionStore implements SessionLedgerCommitter {
|
|
|
955
984
|
this.assertContextRevisionIdle();
|
|
956
985
|
const canonical = this.loadProtocolView();
|
|
957
986
|
this.validator.validate(canonical, { fullIntegrity: true });
|
|
958
|
-
return this.
|
|
987
|
+
return this.readRetirementBoundaries(canonical).closedTurns;
|
|
959
988
|
}
|
|
960
989
|
|
|
961
|
-
|
|
990
|
+
loadRetirementBoundaries(activeTurnId?: TurnId): {
|
|
991
|
+
readonly closedTurns: readonly ClosedTurnBoundary[];
|
|
992
|
+
readonly activeTurn?: ActiveTurnBoundary;
|
|
993
|
+
} {
|
|
994
|
+
this.requireOpen();
|
|
995
|
+
this.assertContextRevisionBoundary(activeTurnId);
|
|
996
|
+
const canonical = this.loadProtocolView();
|
|
997
|
+
this.validator.validate(canonical, {
|
|
998
|
+
allowOpenTail: activeTurnId !== undefined,
|
|
999
|
+
fullIntegrity: true,
|
|
1000
|
+
});
|
|
1001
|
+
return this.readRetirementBoundaries(canonical, activeTurnId);
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
private readRetirementBoundaries(
|
|
962
1005
|
canonical: ProtocolContextView,
|
|
963
|
-
|
|
1006
|
+
activeTurnId?: TurnId,
|
|
1007
|
+
): {
|
|
1008
|
+
readonly closedTurns: readonly ClosedTurnBoundary[];
|
|
1009
|
+
readonly activeTurn?: ActiveTurnBoundary;
|
|
1010
|
+
} {
|
|
964
1011
|
const rows = this.database
|
|
965
1012
|
.query("SELECT * FROM turns ORDER BY turn_number")
|
|
966
1013
|
.all() as Array<Record<string, unknown>>;
|
|
967
1014
|
const boundaries: ClosedTurnBoundary[] = [];
|
|
1015
|
+
let activeTurn: ActiveTurnBoundary | undefined;
|
|
968
1016
|
let expectedOrdinal = 2;
|
|
969
1017
|
for (let index = 0; index < rows.length; index += 1) {
|
|
970
1018
|
const row = requireItem(rows, index, "turn row");
|
|
@@ -972,8 +1020,8 @@ export class SessionStore implements SessionLedgerCommitter {
|
|
|
972
1020
|
const turnNumber = numberFromSql(row.turn_number, "turn_number");
|
|
973
1021
|
const status = enumFromSql(
|
|
974
1022
|
row.status,
|
|
975
|
-
["completed", "failed", "cancelled", "interrupted"] as const,
|
|
976
|
-
"
|
|
1023
|
+
["open", "completed", "failed", "cancelled", "interrupted"] as const,
|
|
1024
|
+
"turn status",
|
|
977
1025
|
);
|
|
978
1026
|
const frames = canonical.frames.filter((frame) => frame.turnId === turnId);
|
|
979
1027
|
const messages = canonical.messages.filter(
|
|
@@ -981,6 +1029,29 @@ export class SessionStore implements SessionLedgerCommitter {
|
|
|
981
1029
|
);
|
|
982
1030
|
const firstMessage = messages[0];
|
|
983
1031
|
const lastMessage = messages.at(-1);
|
|
1032
|
+
if (status === "open") {
|
|
1033
|
+
if (
|
|
1034
|
+
activeTurnId === undefined ||
|
|
1035
|
+
turnId !== activeTurnId ||
|
|
1036
|
+
index !== rows.length - 1 ||
|
|
1037
|
+
turnNumber !== index + 1 ||
|
|
1038
|
+
messages.length < 1 ||
|
|
1039
|
+
frames.length < 1 ||
|
|
1040
|
+
firstMessage?.role !== "user" ||
|
|
1041
|
+
firstMessage.ordinal !== expectedOrdinal ||
|
|
1042
|
+
lastMessage?.ordinal !== canonical.messages.length ||
|
|
1043
|
+
frames.some((frame) => frame.state !== "closed")
|
|
1044
|
+
) {
|
|
1045
|
+
throw new Error(`Turn ${turnId} has an invalid active boundary.`);
|
|
1046
|
+
}
|
|
1047
|
+
activeTurn = Object.freeze({
|
|
1048
|
+
turnId,
|
|
1049
|
+
turnNumber,
|
|
1050
|
+
firstOrdinal: expectedOrdinal,
|
|
1051
|
+
});
|
|
1052
|
+
expectedOrdinal = canonical.messages.length + 1;
|
|
1053
|
+
continue;
|
|
1054
|
+
}
|
|
984
1055
|
let nextFrameOrdinal = expectedOrdinal;
|
|
985
1056
|
for (const frame of frames) {
|
|
986
1057
|
if (
|
|
@@ -1019,7 +1090,13 @@ export class SessionStore implements SessionLedgerCommitter {
|
|
|
1019
1090
|
if (expectedOrdinal !== canonical.messages.length + 1) {
|
|
1020
1091
|
throw new Error("Closed turn boundaries do not cover canonical history.");
|
|
1021
1092
|
}
|
|
1022
|
-
|
|
1093
|
+
if ((activeTurnId === undefined) !== (activeTurn === undefined)) {
|
|
1094
|
+
throw new Error("Active retirement boundary does not match the open turn.");
|
|
1095
|
+
}
|
|
1096
|
+
return Object.freeze({
|
|
1097
|
+
closedTurns: Object.freeze(boundaries),
|
|
1098
|
+
...(activeTurn === undefined ? {} : { activeTurn }),
|
|
1099
|
+
});
|
|
1023
1100
|
}
|
|
1024
1101
|
|
|
1025
1102
|
commitSwapRevision(
|
|
@@ -1043,7 +1120,7 @@ export class SessionStore implements SessionLedgerCommitter {
|
|
|
1043
1120
|
) {
|
|
1044
1121
|
throw new Error("Context revision commit base is stale.");
|
|
1045
1122
|
}
|
|
1046
|
-
this.
|
|
1123
|
+
this.assertContextRevisionBoundary(input.activeTurnId);
|
|
1047
1124
|
|
|
1048
1125
|
const active = this.revisionCompiler.compileActive(snapshot);
|
|
1049
1126
|
const candidateOverrides = [
|
|
@@ -1229,25 +1306,27 @@ export class SessionStore implements SessionLedgerCommitter {
|
|
|
1229
1306
|
) {
|
|
1230
1307
|
throw new Error("Prefix retirement commit base is stale.");
|
|
1231
1308
|
}
|
|
1232
|
-
this.
|
|
1233
|
-
const
|
|
1309
|
+
this.assertContextRevisionBoundary(input.activeTurnId);
|
|
1310
|
+
const retirementBoundaries = this.readRetirementBoundaries(
|
|
1311
|
+
snapshot.canonical,
|
|
1312
|
+
input.activeTurnId,
|
|
1313
|
+
);
|
|
1314
|
+
const closedTurns = retirementBoundaries.closedTurns;
|
|
1234
1315
|
const activeTurns = closedTurns.filter(
|
|
1235
1316
|
(turn) => turn.firstOrdinal >= baseRevision.keepFromOrdinal,
|
|
1236
1317
|
);
|
|
1237
|
-
const nextBoundary =
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
);
|
|
1318
|
+
const nextBoundary = [
|
|
1319
|
+
...activeTurns,
|
|
1320
|
+
...(retirementBoundaries.activeTurn === undefined
|
|
1321
|
+
? []
|
|
1322
|
+
: [retirementBoundaries.activeTurn]),
|
|
1323
|
+
].find((turn) => turn.firstOrdinal === input.nextKeepFromOrdinal);
|
|
1243
1324
|
const retiredTurns = activeTurns.filter(
|
|
1244
1325
|
(turn) => turn.lastOrdinal < input.nextKeepFromOrdinal,
|
|
1245
1326
|
);
|
|
1246
1327
|
if (
|
|
1247
1328
|
nextBoundary === undefined ||
|
|
1248
1329
|
input.nextKeepFromOrdinal <= baseRevision.keepFromOrdinal ||
|
|
1249
|
-
retainedTurns.length <
|
|
1250
|
-
recallFirstRetirementPolicyV1.protectedRecentTurnCount ||
|
|
1251
1330
|
retiredTurns.length !== input.retiredTurnCount ||
|
|
1252
1331
|
retiredTurns.reduce((total, turn) => total + turn.frameCount, 0) !==
|
|
1253
1332
|
input.retiredFrameCount ||
|
|
@@ -3608,7 +3687,7 @@ export function createSessionCompatibilityContract(input: {
|
|
|
3608
3687
|
throw new Error("Session compatibility reasoning replay flag must be boolean.");
|
|
3609
3688
|
}
|
|
3610
3689
|
if (
|
|
3611
|
-
!
|
|
3690
|
+
!MODEL_MESSAGE_PROTOCOL_ADAPTERS.includes(input.messageProtocol.adapter) ||
|
|
3612
3691
|
input.messageProtocol.serializationVersion.trim() === ""
|
|
3613
3692
|
) {
|
|
3614
3693
|
throw new Error("Session compatibility message protocol is invalid.");
|
|
@@ -4842,6 +4921,7 @@ export function decodeStoredToolRawResult(value: unknown): ToolRawResult {
|
|
|
4842
4921
|
"glob",
|
|
4843
4922
|
"grep",
|
|
4844
4923
|
"bash",
|
|
4924
|
+
"update_plan",
|
|
4845
4925
|
"task_list",
|
|
4846
4926
|
"task_output",
|
|
4847
4927
|
"task_input",
|
|
@@ -5472,7 +5552,7 @@ function decodeSessionCompatibilityContract(
|
|
|
5472
5552
|
const protocol: ModelMessageProtocol = Object.freeze({
|
|
5473
5553
|
adapter: enumFromSql(
|
|
5474
5554
|
messageProtocol.adapter,
|
|
5475
|
-
|
|
5555
|
+
MODEL_MESSAGE_PROTOCOL_ADAPTERS,
|
|
5476
5556
|
"compatibility message adapter",
|
|
5477
5557
|
),
|
|
5478
5558
|
serializationVersion: stringFromSql(
|
|
@@ -298,7 +298,7 @@ export function renderSkillActivationReceipt(input: {
|
|
|
298
298
|
`source=${source}`,
|
|
299
299
|
"activation=The full historical instructions were promoted out of this tool observation.",
|
|
300
300
|
"current=Consult the current active_agent_skills system section; absence means inactive.",
|
|
301
|
-
"historical=Use
|
|
301
|
+
"historical=Use RecallGet with source to recover the original activation observation.",
|
|
302
302
|
].join("\n")
|
|
303
303
|
: [
|
|
304
304
|
"[Tinker Agent Skill activation rejected]",
|
|
@@ -306,7 +306,7 @@ export function renderSkillActivationReceipt(input: {
|
|
|
306
306
|
`source=${source}`,
|
|
307
307
|
`status=${input.outcome === "unavailable" ? "unavailable" : "not_dispatched"}`,
|
|
308
308
|
"current=This skill was not added to the active system surface.",
|
|
309
|
-
"historical=Use
|
|
309
|
+
"historical=Use RecallGet with source to recover the original activation observation.",
|
|
310
310
|
].join("\n");
|
|
311
311
|
const originalBytes = Buffer.byteLength(input.message.content, "utf8");
|
|
312
312
|
const renderedBytes = Buffer.byteLength(renderedContent, "utf8");
|