@markusylisiurunen/tau 0.3.41 → 0.3.43

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.
Files changed (104) hide show
  1. package/README.md +14 -6
  2. package/dist/core/agent/agent_runtime.js +32 -44
  3. package/dist/core/agent/agent_runtime.js.map +1 -1
  4. package/dist/core/auth/auth_manager.js +25 -9
  5. package/dist/core/auth/auth_manager.js.map +1 -1
  6. package/dist/core/auth/auth_storage.js +1 -0
  7. package/dist/core/auth/auth_storage.js.map +1 -1
  8. package/dist/core/auth/cli.js +49 -35
  9. package/dist/core/auth/cli.js.map +1 -1
  10. package/dist/core/auth/credential_store.js +54 -22
  11. package/dist/core/auth/credential_store.js.map +1 -1
  12. package/dist/core/auth/index.js +1 -1
  13. package/dist/core/auth/index.js.map +1 -1
  14. package/dist/core/auth/providers/openai_codex.js +71 -23
  15. package/dist/core/auth/providers/openai_codex.js.map +1 -1
  16. package/dist/core/cli.js +4 -2
  17. package/dist/core/cli.js.map +1 -1
  18. package/dist/core/config/builtin_themes.js +62 -155
  19. package/dist/core/config/builtin_themes.js.map +1 -1
  20. package/dist/core/index.js +1 -1
  21. package/dist/core/index.js.map +1 -1
  22. package/dist/core/models/tau_model_overrides.js +1 -66
  23. package/dist/core/models/tau_model_overrides.js.map +1 -1
  24. package/dist/core/session/direct_bash.js +1 -0
  25. package/dist/core/session/direct_bash.js.map +1 -1
  26. package/dist/core/session/runner.js +21 -14
  27. package/dist/core/session/runner.js.map +1 -1
  28. package/dist/core/static/code_mode/capabilities.mjs +25 -0
  29. package/dist/core/static/code_mode/history/documentation.md +2 -0
  30. package/dist/core/static/code_mode/history/sandbox_runner.mjs +4 -1
  31. package/dist/core/static/code_mode/nook/documentation.md +2 -0
  32. package/dist/core/static/code_mode/nook/sandbox_runner.mjs +4 -1
  33. package/dist/core/static/code_mode/web/documentation.md +2 -0
  34. package/dist/core/static/code_mode/web/sandbox_runner.mjs +4 -1
  35. package/dist/core/subagents/agent_supervisor.js +3 -3
  36. package/dist/core/subagents/agent_supervisor.js.map +1 -1
  37. package/dist/core/telegram/adapter.js +0 -23
  38. package/dist/core/telegram/adapter.js.map +1 -1
  39. package/dist/core/telegram/session_manager.js +34 -7
  40. package/dist/core/telegram/session_manager.js.map +1 -1
  41. package/dist/core/tools/bash.js +10 -2
  42. package/dist/core/tools/bash.js.map +1 -1
  43. package/dist/core/tools/code_mode.js +1 -0
  44. package/dist/core/tools/code_mode.js.map +1 -1
  45. package/dist/core/tools/presentation.js +1 -1
  46. package/dist/core/utils/auto_retry.js +1 -1
  47. package/dist/core/utils/auto_retry.js.map +1 -1
  48. package/dist/core/version.js +1 -1
  49. package/dist/host/local_session_host.js +319 -198
  50. package/dist/host/local_session_host.js.map +1 -1
  51. package/dist/host/session_protocol_handler.js +1 -1
  52. package/dist/host/session_protocol_handler.js.map +1 -1
  53. package/dist/main.js +16 -2
  54. package/dist/main.js.map +1 -1
  55. package/dist/protocol/index.d.ts +2 -2
  56. package/dist/protocol/index.js +1 -1
  57. package/dist/protocol/index.js.map +1 -1
  58. package/dist/protocol/session_protocol.d.ts +115 -40
  59. package/dist/protocol/session_protocol.js +377 -98
  60. package/dist/protocol/session_protocol.js.map +1 -1
  61. package/dist/sdk/index.d.ts +1 -2
  62. package/dist/sdk/index.js +0 -1
  63. package/dist/sdk/index.js.map +1 -1
  64. package/dist/store/session_snapshot_migrations.js +311 -4
  65. package/dist/store/session_snapshot_migrations.js.map +1 -1
  66. package/dist/tui/chat_controller/assistant_clipboard.js +12 -8
  67. package/dist/tui/chat_controller/assistant_clipboard.js.map +1 -1
  68. package/dist/tui/chat_controller/diff_review_service.js +13 -9
  69. package/dist/tui/chat_controller/diff_review_service.js.map +1 -1
  70. package/dist/tui/chat_controller/history_message_model.js +3 -3
  71. package/dist/tui/chat_controller/history_message_model.js.map +1 -1
  72. package/dist/tui/chat_controller/status_format.js +18 -0
  73. package/dist/tui/chat_controller/status_format.js.map +1 -1
  74. package/dist/tui/chat_view.js +39 -38
  75. package/dist/tui/chat_view.js.map +1 -1
  76. package/dist/tui/session_chat_app.js +1 -3
  77. package/dist/tui/session_chat_app.js.map +1 -1
  78. package/dist/tui/session_chat_controller.js +375 -241
  79. package/dist/tui/session_chat_controller.js.map +1 -1
  80. package/dist/tui/speech_playback.js +4 -4
  81. package/dist/tui/speech_playback.js.map +1 -1
  82. package/dist/tui/ui/assistant_message.js +1 -15
  83. package/dist/tui/ui/assistant_message.js.map +1 -1
  84. package/dist/tui/ui/chat_message_model.js +26 -7
  85. package/dist/tui/ui/chat_message_model.js.map +1 -1
  86. package/dist/tui/ui/custom_editor.js +1 -1
  87. package/dist/tui/ui/custom_editor.js.map +1 -1
  88. package/dist/tui/ui/footer.js +161 -47
  89. package/dist/tui/ui/footer.js.map +1 -1
  90. package/dist/tui/ui/theme/palette.js +10 -16
  91. package/dist/tui/ui/theme/palette.js.map +1 -1
  92. package/dist/tui/ui/theme/theme.js.map +1 -1
  93. package/dist/tui/ui/tool_card.js +12 -9
  94. package/dist/tui/ui/tool_card.js.map +1 -1
  95. package/dist/tui/ui/transcript_notice.js +44 -0
  96. package/dist/tui/ui/transcript_notice.js.map +1 -0
  97. package/dist/tui/ui/transcript_text.js +14 -0
  98. package/dist/tui/ui/transcript_text.js.map +1 -0
  99. package/package.json +10 -12
  100. package/dist/sdk/turn_outcome.d.ts +0 -2
  101. package/dist/sdk/turn_outcome.js +0 -4
  102. package/dist/sdk/turn_outcome.js.map +0 -1
  103. package/dist/tui/ui/system_message.js +0 -22
  104. package/dist/tui/ui/system_message.js.map +0 -1
@@ -1,7 +1,10 @@
1
1
  import { z } from "zod";
2
- export const SESSION_PROTOCOL_VERSION = 8;
2
+ export const SESSION_PROTOCOL_VERSION = 9;
3
3
  export const SESSION_PROTOCOL_MAX_EXEC_CAPTURE_BYTES = 24 * 1024 * 1024;
4
4
  export const SESSION_PROTOCOL_MAX_EXEC_STDIN_BYTES = 16 * 1024 * 1024;
5
+ const SESSION_PROTOCOL_NOTICE_TITLE_MAX_CHARS = 512;
6
+ const SESSION_PROTOCOL_NOTICE_CONTENT_MAX_ENTRIES = 16;
7
+ const SESSION_PROTOCOL_NOTICE_CONTENT_ENTRY_MAX_CHARS = 4_096;
5
8
  export const SESSION_PROTOCOL_METHODS = [
6
9
  "initialize",
7
10
  "session.create",
@@ -65,6 +68,22 @@ const SESSION_PROTOCOL_ERROR_CODE_VALUES = [
65
68
  SESSION_PROTOCOL_ERROR_CODES.cancelled,
66
69
  SESSION_PROTOCOL_ERROR_CODES.internalError,
67
70
  ];
71
+ export function projectSessionProtocolNoticePresentation(title, content) {
72
+ const projectedContent = content
73
+ ? content
74
+ .slice(0, SESSION_PROTOCOL_NOTICE_CONTENT_MAX_ENTRIES)
75
+ .map((entry) => truncateNoticeText(entry, SESSION_PROTOCOL_NOTICE_CONTENT_ENTRY_MAX_CHARS))
76
+ : [];
77
+ return {
78
+ title: truncateNoticeText(title, SESSION_PROTOCOL_NOTICE_TITLE_MAX_CHARS),
79
+ ...(projectedContent.length > 0 ? { content: projectedContent } : {}),
80
+ };
81
+ }
82
+ function truncateNoticeText(value, maxChars) {
83
+ if (value.length <= maxChars)
84
+ return value;
85
+ return `${value.slice(0, maxChars - 1)}…`;
86
+ }
68
87
  const sessionProtocolMethodSchema = z.enum(SESSION_PROTOCOL_METHODS);
69
88
  const sessionProtocolMethodListSchema = z
70
89
  .array(sessionProtocolMethodSchema)
@@ -86,6 +105,9 @@ const nonEmptyStringSchema = z
86
105
  .string()
87
106
  .min(1)
88
107
  .refine((value) => value.trim().length > 0);
108
+ const noticeTitleSchema = nonEmptyStringSchema
109
+ .max(SESSION_PROTOCOL_NOTICE_TITLE_MAX_CHARS)
110
+ .refine((value) => !value.includes("\n") && !value.includes("\r"), "notice title must be one line");
89
111
  const sessionProtocolRequestIdSchema = nonEmptyStringSchema;
90
112
  const nullableSessionProtocolRequestIdSchema = sessionProtocolRequestIdSchema.nullable();
91
113
  const absolutePathSchema = nonEmptyStringSchema.refine((value) => value.startsWith("/"));
@@ -753,50 +775,136 @@ const sessionProtocolMessageSchema = z
753
775
  state: z.enum(["draft", "committed", "interrupted", "discarded"]),
754
776
  modelVisible: z.boolean(),
755
777
  message: sessionProtocolMessagePayloadSchema,
756
- turn: sessionProtocolTurnOutcomeSchema.optional(),
757
778
  })
758
779
  .strip();
780
+ const sessionProtocolSubjectSchema = z.discriminatedUnion("type", [
781
+ z.object({ type: z.literal("session") }).strip(),
782
+ z.object({ type: z.literal("message"), id: nonEmptyStringSchema }).strip(),
783
+ z.object({ type: z.literal("tool"), id: nonEmptyStringSchema }).strip(),
784
+ z.object({ type: z.literal("agent"), id: nonEmptyStringSchema }).strip(),
785
+ z.object({ type: z.literal("operation"), id: nonEmptyStringSchema }).strip(),
786
+ ]);
787
+ const timelineNoticeKindSchema = nonEmptyStringSchema.regex(/^[a-z0-9-]+(?:\.[a-z0-9-]+)+$/, "timeline notice kind must be a lowercase dotted identifier");
759
788
  const sessionProtocolNoticeSchema = z
760
789
  .object({
790
+ kind: timelineNoticeKindSchema,
791
+ version: z.number().int().positive(),
761
792
  severity: z.enum(["info", "warn", "error"]),
762
- text: z.string(),
763
- timestamp: z.number().finite(),
793
+ subject: sessionProtocolSubjectSchema,
794
+ presentation: z
795
+ .object({
796
+ title: noticeTitleSchema,
797
+ content: z
798
+ .array(z.string().max(SESSION_PROTOCOL_NOTICE_CONTENT_ENTRY_MAX_CHARS))
799
+ .min(1)
800
+ .max(SESSION_PROTOCOL_NOTICE_CONTENT_MAX_ENTRIES)
801
+ .optional(),
802
+ })
803
+ .strip(),
804
+ data: z.record(z.string(), z.unknown()),
764
805
  })
765
806
  .strip();
766
- const sessionProtocolOperationSchema = z
767
- .object({
807
+ const sessionProtocolOperationBaseSchema = {
808
+ id: nonEmptyStringSchema,
768
809
  kind: z.enum(["auto-compaction", "manual-compaction", "reload", "rewind"]),
769
- status: z.enum(["running", "succeeded", "failed", "cancelled", "skipped"]),
770
810
  startedAt: z.number().finite(),
771
- finishedAt: z.number().finite().optional(),
772
- summary: z.string().optional(),
773
- error: z.string().optional(),
774
- data: z.record(z.string(), z.unknown()).optional(),
775
- })
776
- .strip();
811
+ };
812
+ const sessionProtocolOperationSchema = z.discriminatedUnion("status", [
813
+ z.object({ ...sessionProtocolOperationBaseSchema, status: z.literal("running") }).strip(),
814
+ z
815
+ .object({
816
+ ...sessionProtocolOperationBaseSchema,
817
+ status: z.literal("succeeded"),
818
+ finishedAt: z.number().finite(),
819
+ })
820
+ .strip(),
821
+ z
822
+ .object({
823
+ ...sessionProtocolOperationBaseSchema,
824
+ status: z.literal("failed"),
825
+ finishedAt: z.number().finite(),
826
+ error: nonEmptyStringSchema,
827
+ })
828
+ .strip(),
829
+ z
830
+ .object({
831
+ ...sessionProtocolOperationBaseSchema,
832
+ status: z.literal("skipped"),
833
+ finishedAt: z.number().finite(),
834
+ reason: z.literal("no-eligible-history"),
835
+ })
836
+ .strip(),
837
+ z
838
+ .object({
839
+ ...sessionProtocolOperationBaseSchema,
840
+ status: z.literal("cancelled"),
841
+ finishedAt: z.number().finite(),
842
+ reason: z.enum(["interrupted", "session-recovered"]),
843
+ })
844
+ .strip(),
845
+ ]);
846
+ const sessionProtocolTimelineItemBaseSchema = {
847
+ id: nonEmptyStringSchema,
848
+ sequence: z.number().int().positive(),
849
+ createdAt: z.number().finite().nonnegative(),
850
+ };
777
851
  const sessionProtocolTimelineItemSchema = z.discriminatedUnion("type", [
778
852
  z
779
853
  .object({
854
+ ...sessionProtocolTimelineItemBaseSchema,
780
855
  type: z.literal("message"),
781
- id: nonEmptyStringSchema,
782
856
  messageId: nonEmptyStringSchema,
783
857
  })
784
858
  .strip(),
785
859
  z
786
860
  .object({
861
+ ...sessionProtocolTimelineItemBaseSchema,
862
+ type: z.literal("tool"),
863
+ toolId: nonEmptyStringSchema,
864
+ })
865
+ .strip(),
866
+ z
867
+ .object({
868
+ ...sessionProtocolTimelineItemBaseSchema,
787
869
  type: z.literal("notice"),
788
- id: nonEmptyStringSchema,
789
870
  notice: sessionProtocolNoticeSchema,
790
871
  })
791
872
  .strip(),
792
873
  z
793
874
  .object({
875
+ ...sessionProtocolTimelineItemBaseSchema,
794
876
  type: z.literal("operation"),
795
- id: nonEmptyStringSchema,
796
- operation: sessionProtocolOperationSchema,
877
+ operationId: nonEmptyStringSchema,
797
878
  })
798
879
  .strip(),
799
880
  ]);
881
+ const sessionProtocolTimelineSchema = z
882
+ .object({
883
+ epoch: z.number().int().positive(),
884
+ sequence: z.number().int().nonnegative(),
885
+ items: z.array(sessionProtocolTimelineItemSchema),
886
+ })
887
+ .strip()
888
+ .superRefine((timeline, ctx) => {
889
+ let previousSequence = 0;
890
+ for (const [index, item] of timeline.items.entries()) {
891
+ if (item.sequence <= previousSequence) {
892
+ ctx.addIssue({
893
+ code: "custom",
894
+ path: ["items", index, "sequence"],
895
+ message: "timeline item sequences must be strictly increasing",
896
+ });
897
+ }
898
+ if (item.sequence > timeline.sequence) {
899
+ ctx.addIssue({
900
+ code: "custom",
901
+ path: ["items", index, "sequence"],
902
+ message: "timeline item sequence exceeds the timeline high-water sequence",
903
+ });
904
+ }
905
+ previousSequence = item.sequence;
906
+ }
907
+ });
800
908
  const sessionProtocolToolRunBaseSchema = z.object({
801
909
  id: nonEmptyStringSchema,
802
910
  toolCallId: nonEmptyStringSchema,
@@ -918,17 +1026,10 @@ const sessionProtocolAgentRunSchema = z
918
1026
  });
919
1027
  }
920
1028
  });
921
- const sessionProtocolFacetSubjectSchema = z.discriminatedUnion("type", [
922
- z.object({ type: z.literal("session") }).strip(),
923
- z.object({ type: z.literal("message"), id: nonEmptyStringSchema }).strip(),
924
- z.object({ type: z.literal("tool"), id: nonEmptyStringSchema }).strip(),
925
- z.object({ type: z.literal("agent"), id: nonEmptyStringSchema }).strip(),
926
- z.object({ type: z.literal("operation"), id: nonEmptyStringSchema }).strip(),
927
- ]);
928
1029
  const sessionProtocolFacetSchema = z
929
1030
  .object({
930
1031
  id: nonEmptyStringSchema,
931
- subject: sessionProtocolFacetSubjectSchema,
1032
+ subject: sessionProtocolSubjectSchema,
932
1033
  kind: nonEmptyStringSchema,
933
1034
  version: z.number().int().positive(),
934
1035
  data: z.record(z.string(), z.unknown()),
@@ -943,11 +1044,11 @@ const sessionProtocolGoalSchema = z
943
1044
  const sessionProtocolAgentStateSnapshotSchema = z
944
1045
  .object({
945
1046
  revision: z.number().int().nonnegative(),
946
- contextEpoch: nonEmptyStringSchema,
1047
+ modelContextKey: nonEmptyStringSchema,
947
1048
  usageCheckpoint: z
948
1049
  .object({
949
1050
  historyEntryId: nonEmptyStringSchema,
950
- contextEpoch: nonEmptyStringSchema,
1051
+ modelContextKey: nonEmptyStringSchema,
951
1052
  tokens: z.number().int().nonnegative(),
952
1053
  })
953
1054
  .strip()
@@ -969,8 +1070,9 @@ const sessionProtocolSnapshotSchema = z
969
1070
  catalog: sessionProtocolContentCatalogSnapshotSchema,
970
1071
  executionEnvironment: sessionProtocolExecutionEnvironmentSnapshotSchema,
971
1072
  messages: z.array(sessionProtocolMessageSchema),
972
- timeline: z.array(sessionProtocolTimelineItemSchema),
1073
+ timeline: sessionProtocolTimelineSchema,
973
1074
  tools: z.record(nonEmptyStringSchema, sessionProtocolToolRunSchema),
1075
+ operations: z.record(nonEmptyStringSchema, sessionProtocolOperationSchema),
974
1076
  agents: z.record(nonEmptyStringSchema, sessionProtocolAgentRunSchema),
975
1077
  facets: z.record(nonEmptyStringSchema, sessionProtocolFacetSchema),
976
1078
  })
@@ -985,22 +1087,15 @@ const sessionProtocolSnapshotSchema = z
985
1087
  message: `duplicate message id '${message.id}'`,
986
1088
  });
987
1089
  }
988
- if (message.turn && message.message.role !== "user") {
989
- ctx.addIssue({
990
- code: "custom",
991
- path: ["messages"],
992
- message: `turn outcome belongs to non-user message '${message.id}'`,
993
- });
994
- }
995
1090
  messagesById.set(message.id, message);
996
1091
  }
997
1092
  const checkpoint = snapshot.agentState.usageCheckpoint;
998
1093
  if (checkpoint) {
999
- if (checkpoint.contextEpoch !== snapshot.agentState.contextEpoch) {
1094
+ if (checkpoint.modelContextKey !== snapshot.agentState.modelContextKey) {
1000
1095
  ctx.addIssue({
1001
1096
  code: "custom",
1002
- path: ["agentState", "usageCheckpoint", "contextEpoch"],
1003
- message: "usage checkpoint context epoch must match agent state",
1097
+ path: ["agentState", "usageCheckpoint", "modelContextKey"],
1098
+ message: "usage checkpoint model context key must match agent state",
1004
1099
  });
1005
1100
  }
1006
1101
  const checkpointMessage = messagesById.get(checkpoint.historyEntryId);
@@ -1019,29 +1114,109 @@ const sessionProtocolSnapshotSchema = z
1019
1114
  });
1020
1115
  }
1021
1116
  }
1117
+ const subjectExists = (subject) => subject.type === "session" ||
1118
+ (subject.type === "message" && messagesById.has(subject.id)) ||
1119
+ (subject.type === "tool" && snapshot.tools[subject.id] !== undefined) ||
1120
+ (subject.type === "agent" && snapshot.agents[subject.id] !== undefined) ||
1121
+ (subject.type === "operation" && snapshot.operations[subject.id] !== undefined);
1022
1122
  const timelineIds = new Set();
1023
- const operationIds = new Set();
1024
- for (const item of snapshot.timeline) {
1123
+ const timelineSequences = new Set();
1124
+ const timelineToolIds = new Set();
1125
+ const timelineOperationIds = new Set();
1126
+ let previousTimelineSequence = 0;
1127
+ for (const item of snapshot.timeline.items) {
1025
1128
  if (timelineIds.has(item.id)) {
1026
1129
  ctx.addIssue({
1027
1130
  code: "custom",
1028
- path: ["timeline"],
1131
+ path: ["timeline", "items"],
1029
1132
  message: `duplicate timeline item id '${item.id}'`,
1030
1133
  });
1031
1134
  }
1032
1135
  timelineIds.add(item.id);
1136
+ if (timelineSequences.has(item.sequence) ||
1137
+ item.sequence <= previousTimelineSequence ||
1138
+ item.sequence > snapshot.timeline.sequence) {
1139
+ ctx.addIssue({
1140
+ code: "custom",
1141
+ path: ["timeline", "items"],
1142
+ message: `timeline item '${item.id}' has invalid sequence '${item.sequence}'`,
1143
+ });
1144
+ }
1145
+ timelineSequences.add(item.sequence);
1146
+ previousTimelineSequence = item.sequence;
1033
1147
  if (item.type === "message" && !messagesById.has(item.messageId)) {
1034
1148
  ctx.addIssue({
1035
1149
  code: "custom",
1036
- path: ["timeline"],
1150
+ path: ["timeline", "items"],
1037
1151
  message: `timeline message item '${item.id}' references unknown message '${item.messageId}'`,
1038
1152
  });
1039
1153
  }
1154
+ if (item.type === "tool") {
1155
+ if (timelineToolIds.has(item.toolId)) {
1156
+ ctx.addIssue({
1157
+ code: "custom",
1158
+ path: ["timeline", "items"],
1159
+ message: `tool '${item.toolId}' has multiple timeline items`,
1160
+ });
1161
+ }
1162
+ timelineToolIds.add(item.toolId);
1163
+ if (snapshot.tools[item.toolId] === undefined) {
1164
+ ctx.addIssue({
1165
+ code: "custom",
1166
+ path: ["timeline", "items"],
1167
+ message: `timeline tool item '${item.id}' references unknown tool '${item.toolId}'`,
1168
+ });
1169
+ }
1170
+ }
1040
1171
  if (item.type === "operation") {
1041
- operationIds.add(item.id);
1172
+ if (timelineOperationIds.has(item.operationId)) {
1173
+ ctx.addIssue({
1174
+ code: "custom",
1175
+ path: ["timeline", "items"],
1176
+ message: `operation '${item.operationId}' has multiple timeline items`,
1177
+ });
1178
+ }
1179
+ timelineOperationIds.add(item.operationId);
1180
+ if (snapshot.operations[item.operationId] === undefined) {
1181
+ ctx.addIssue({
1182
+ code: "custom",
1183
+ path: ["timeline", "items"],
1184
+ message: `timeline operation item '${item.id}' references unknown operation '${item.operationId}'`,
1185
+ });
1186
+ }
1187
+ }
1188
+ if (item.type === "notice" && !subjectExists(item.notice.subject)) {
1189
+ ctx.addIssue({
1190
+ code: "custom",
1191
+ path: ["timeline", "items"],
1192
+ message: `timeline notice '${item.id}' references an unknown ${item.notice.subject.type} subject`,
1193
+ });
1194
+ }
1195
+ }
1196
+ for (const [id, operation] of Object.entries(snapshot.operations)) {
1197
+ if (!timelineOperationIds.has(id)) {
1198
+ ctx.addIssue({
1199
+ code: "custom",
1200
+ path: ["operations", id],
1201
+ message: `operation '${id}' has no timeline item`,
1202
+ });
1203
+ }
1204
+ if (id !== operation.id) {
1205
+ ctx.addIssue({
1206
+ code: "custom",
1207
+ path: ["operations", id],
1208
+ message: `operation map key '${id}' does not match embedded id '${operation.id}'`,
1209
+ });
1042
1210
  }
1043
1211
  }
1044
1212
  for (const [id, tool] of Object.entries(snapshot.tools)) {
1213
+ if (!timelineToolIds.has(id)) {
1214
+ ctx.addIssue({
1215
+ code: "custom",
1216
+ path: ["tools", id],
1217
+ message: `tool '${id}' has no timeline item`,
1218
+ });
1219
+ }
1045
1220
  if (id !== tool.id || id !== tool.toolCallId) {
1046
1221
  ctx.addIssue({
1047
1222
  code: "custom",
@@ -1136,12 +1311,7 @@ const sessionProtocolSnapshotSchema = z
1136
1311
  message: `facet map key '${id}' does not match embedded id '${facet.id}'`,
1137
1312
  });
1138
1313
  }
1139
- const subjectExists = facet.subject.type === "session" ||
1140
- (facet.subject.type === "message" && messagesById.has(facet.subject.id)) ||
1141
- (facet.subject.type === "tool" && snapshot.tools[facet.subject.id] !== undefined) ||
1142
- (facet.subject.type === "agent" && snapshot.agents[facet.subject.id] !== undefined) ||
1143
- (facet.subject.type === "operation" && operationIds.has(facet.subject.id));
1144
- if (!subjectExists) {
1314
+ if (!subjectExists(facet.subject)) {
1145
1315
  ctx.addIssue({
1146
1316
  code: "custom",
1147
1317
  path: ["facets", id, "subject"],
@@ -1150,18 +1320,41 @@ const sessionProtocolSnapshotSchema = z
1150
1320
  }
1151
1321
  }
1152
1322
  });
1153
- const sessionProtocolDeltaReasonSchema = z.enum([
1154
- "user-message",
1155
- "assistant-stream",
1156
- "assistant-message",
1157
- "tool-run",
1158
- "tool-result",
1159
- "notice",
1160
- "agent-run",
1161
- "maintenance",
1162
- "configuration",
1163
- "goal",
1164
- "recovery",
1323
+ const sessionProtocolDeltaCauseSchema = z.union([
1324
+ z
1325
+ .object({
1326
+ type: z.enum([
1327
+ "user-message",
1328
+ "assistant-stream",
1329
+ "assistant-message",
1330
+ "tool-run",
1331
+ "tool-result",
1332
+ "notice",
1333
+ "agent-run",
1334
+ "maintenance",
1335
+ "configuration",
1336
+ "goal",
1337
+ ]),
1338
+ })
1339
+ .strip(),
1340
+ z
1341
+ .object({
1342
+ type: z.literal("compaction"),
1343
+ previousEpoch: z.number().int().positive(),
1344
+ epoch: z.number().int().positive(),
1345
+ kind: z.enum(["auto", "manual"]),
1346
+ cutType: z.enum(["turn-boundary", "split-turn"]),
1347
+ retainedMessageCount: z.number().int().nonnegative(),
1348
+ })
1349
+ .strip(),
1350
+ z
1351
+ .object({
1352
+ type: z.literal("rewind"),
1353
+ epoch: z.number().int().positive(),
1354
+ cutoffSequence: z.number().int().nonnegative(),
1355
+ })
1356
+ .strip(),
1357
+ z.object({ type: z.literal("resync") }).strip(),
1165
1358
  ]);
1166
1359
  const sessionProtocolChangeSchema = z.discriminatedUnion("type", [
1167
1360
  z
@@ -1198,7 +1391,6 @@ const sessionProtocolChangeSchema = z.discriminatedUnion("type", [
1198
1391
  .object({
1199
1392
  type: z.literal("message.append"),
1200
1393
  message: sessionProtocolMessageSchema,
1201
- timelineItem: sessionProtocolTimelineItemSchema.optional(),
1202
1394
  })
1203
1395
  .strip(),
1204
1396
  z
@@ -1224,14 +1416,15 @@ const sessionProtocolChangeSchema = z.discriminatedUnion("type", [
1224
1416
  .strip(),
1225
1417
  z
1226
1418
  .object({
1227
- type: z.literal("timeline.replace"),
1228
- item: sessionProtocolTimelineItemSchema,
1419
+ type: z.literal("timeline.remove"),
1420
+ id: nonEmptyStringSchema,
1229
1421
  })
1230
1422
  .strip(),
1231
1423
  z
1232
1424
  .object({
1233
- type: z.literal("timeline.remove"),
1234
- id: nonEmptyStringSchema,
1425
+ type: z.literal("timeline.advance"),
1426
+ epoch: z.number().int().positive(),
1427
+ sequence: z.number().int().positive(),
1235
1428
  })
1236
1429
  .strip(),
1237
1430
  z
@@ -1246,6 +1439,18 @@ const sessionProtocolChangeSchema = z.discriminatedUnion("type", [
1246
1439
  id: nonEmptyStringSchema,
1247
1440
  })
1248
1441
  .strip(),
1442
+ z
1443
+ .object({
1444
+ type: z.literal("operation.set"),
1445
+ operation: sessionProtocolOperationSchema,
1446
+ })
1447
+ .strip(),
1448
+ z
1449
+ .object({
1450
+ type: z.literal("operation.remove"),
1451
+ id: nonEmptyStringSchema,
1452
+ })
1453
+ .strip(),
1249
1454
  z
1250
1455
  .object({
1251
1456
  type: z.literal("agent.set"),
@@ -1292,7 +1497,7 @@ const sessionProtocolDeltaMessageSchema = z
1292
1497
  sessionId: nonEmptyStringSchema,
1293
1498
  fromRevision: z.number().int().positive().nullable(),
1294
1499
  toRevision: z.number().int().positive(),
1295
- reason: sessionProtocolDeltaReasonSchema,
1500
+ cause: sessionProtocolDeltaCauseSchema,
1296
1501
  delta: sessionProtocolDeltaSchema,
1297
1502
  })
1298
1503
  .strip()
@@ -1334,6 +1539,29 @@ const sessionProtocolDeltaMessageSchema = z
1334
1539
  message: "snapshot.reset revision must equal toRevision",
1335
1540
  });
1336
1541
  }
1542
+ if (message.cause.type === "compaction") {
1543
+ if (message.delta.type !== "snapshot.reset" ||
1544
+ message.cause.epoch !== message.cause.previousEpoch + 1 ||
1545
+ message.delta.snapshot.timeline.epoch !== message.cause.epoch) {
1546
+ ctx.addIssue({
1547
+ code: "custom",
1548
+ path: ["cause"],
1549
+ message: "compaction cause must reset the snapshot to the next timeline epoch",
1550
+ });
1551
+ }
1552
+ }
1553
+ if (message.cause.type === "rewind") {
1554
+ const { cutoffSequence, epoch } = message.cause;
1555
+ if (message.delta.type !== "snapshot.reset" ||
1556
+ message.delta.snapshot.timeline.epoch !== epoch ||
1557
+ message.delta.snapshot.timeline.items.some((item) => item.sequence > cutoffSequence)) {
1558
+ ctx.addIssue({
1559
+ code: "custom",
1560
+ path: ["cause"],
1561
+ message: "rewind cause must reset and truncate the active timeline epoch",
1562
+ });
1563
+ }
1564
+ }
1337
1565
  });
1338
1566
  const sessionProtocolEphemeralAgentThreadUpdateEventSchema = z
1339
1567
  .object({
@@ -1349,7 +1577,27 @@ const sessionProtocolEphemeralAgentThreadUpdateEventSchema = z
1349
1577
  .strip(),
1350
1578
  })
1351
1579
  .strip();
1352
- const sessionProtocolEphemeralEventSchema = sessionProtocolEphemeralAgentThreadUpdateEventSchema;
1580
+ const sessionProtocolFeedbackEventSchema = z
1581
+ .object({
1582
+ type: z.literal("feedback.notice"),
1583
+ title: noticeTitleSchema,
1584
+ tone: z.enum(["default", "error"]),
1585
+ presentation: z.literal("footer"),
1586
+ durationMs: z.number().int().positive().max(60_000),
1587
+ })
1588
+ .strip();
1589
+ const sessionProtocolEphemeralTimelineEventSchema = z
1590
+ .object({
1591
+ type: z.literal("timeline.item"),
1592
+ epoch: z.number().int().positive(),
1593
+ item: sessionProtocolTimelineItemSchema.refine((item) => item.type === "notice", "ephemeral timeline items must be notices"),
1594
+ })
1595
+ .strip();
1596
+ const sessionProtocolEphemeralEventSchema = z.union([
1597
+ sessionProtocolEphemeralAgentThreadUpdateEventSchema,
1598
+ sessionProtocolFeedbackEventSchema,
1599
+ sessionProtocolEphemeralTimelineEventSchema,
1600
+ ]);
1353
1601
  const sessionProtocolEphemeralMessageSchema = z
1354
1602
  .object({
1355
1603
  version: z.literal(SESSION_PROTOCOL_VERSION),
@@ -1594,7 +1842,7 @@ export function createSessionProtocolDeltaMessage(options) {
1594
1842
  sessionId: options.sessionId,
1595
1843
  fromRevision: options.fromRevision,
1596
1844
  toRevision: options.toRevision,
1597
- reason: options.reason,
1845
+ cause: options.cause,
1598
1846
  delta: options.delta,
1599
1847
  };
1600
1848
  const parsedMessage = sessionProtocolDeltaMessageSchema.safeParse(message);
@@ -1629,11 +1877,33 @@ export function createSessionProtocolPendingUserMessagesMessage(options) {
1629
1877
  }
1630
1878
  return parsedMessage.data;
1631
1879
  }
1880
+ function validateSnapshotResetCause(snapshot, message) {
1881
+ if (message.delta.type !== "snapshot.reset") {
1882
+ return;
1883
+ }
1884
+ if (message.cause.type === "compaction" &&
1885
+ message.cause.previousEpoch !== snapshot.timeline.epoch) {
1886
+ throw new Error(`compaction delta previous epoch ${message.cause.previousEpoch} does not match current timeline epoch ${snapshot.timeline.epoch}`);
1887
+ }
1888
+ if (message.cause.type !== "rewind") {
1889
+ return;
1890
+ }
1891
+ if (message.cause.epoch !== snapshot.timeline.epoch) {
1892
+ throw new Error(`rewind delta epoch ${message.cause.epoch} does not match current timeline epoch ${snapshot.timeline.epoch}`);
1893
+ }
1894
+ if (message.cause.cutoffSequence > snapshot.timeline.sequence) {
1895
+ throw new Error(`rewind delta cutoff sequence ${message.cause.cutoffSequence} exceeds current timeline sequence ${snapshot.timeline.sequence}`);
1896
+ }
1897
+ if (message.delta.snapshot.timeline.sequence !== snapshot.timeline.sequence) {
1898
+ throw new Error(`rewind delta timeline sequence ${message.delta.snapshot.timeline.sequence} does not preserve current timeline sequence ${snapshot.timeline.sequence}`);
1899
+ }
1900
+ }
1632
1901
  export function applySessionProtocolDelta(snapshot, message) {
1633
1902
  if (message.sessionId !== snapshot.sessionId) {
1634
1903
  throw new Error(`session delta targets '${message.sessionId}' but current snapshot is '${snapshot.sessionId}'`);
1635
1904
  }
1636
1905
  if (message.delta.type === "snapshot.reset") {
1906
+ validateSnapshotResetCause(snapshot, message);
1637
1907
  return structuredClone(message.delta.snapshot);
1638
1908
  }
1639
1909
  if (message.fromRevision !== snapshot.revision) {
@@ -1669,9 +1939,6 @@ export function applySessionProtocolDelta(snapshot, message) {
1669
1939
  break;
1670
1940
  case "message.append":
1671
1941
  next.messages.push(structuredClone(change.message));
1672
- if (change.timelineItem) {
1673
- next.timeline.push(structuredClone(change.timelineItem));
1674
- }
1675
1942
  break;
1676
1943
  case "message.replace": {
1677
1944
  const index = next.messages.findIndex((entry) => entry.id === change.message.id);
@@ -1688,20 +1955,23 @@ export function applySessionProtocolDelta(snapshot, message) {
1688
1955
  break;
1689
1956
  }
1690
1957
  case "timeline.append":
1691
- next.timeline.push(structuredClone(change.item));
1692
- break;
1693
- case "timeline.replace": {
1694
- const index = next.timeline.findIndex((item) => item.id === change.item.id);
1695
- if (index === -1) {
1696
- next.timeline.push(structuredClone(change.item));
1697
- }
1698
- else {
1699
- next.timeline[index] = structuredClone(change.item);
1958
+ if (change.item.sequence <= next.timeline.sequence) {
1959
+ throw new Error(`timeline append sequence ${change.item.sequence} must exceed current timeline sequence ${next.timeline.sequence}`);
1700
1960
  }
1961
+ next.timeline.items.push(structuredClone(change.item));
1962
+ next.timeline.sequence = change.item.sequence;
1701
1963
  break;
1702
- }
1703
1964
  case "timeline.remove":
1704
- next.timeline = next.timeline.filter((item) => item.id !== change.id);
1965
+ next.timeline.items = next.timeline.items.filter((item) => item.id !== change.id);
1966
+ break;
1967
+ case "timeline.advance":
1968
+ if (next.timeline.epoch !== change.epoch) {
1969
+ throw new Error("timeline advance epoch does not match the active epoch");
1970
+ }
1971
+ if (change.sequence <= next.timeline.sequence) {
1972
+ throw new Error(`timeline advance sequence ${change.sequence} must exceed current timeline sequence ${next.timeline.sequence}`);
1973
+ }
1974
+ next.timeline.sequence = change.sequence;
1705
1975
  break;
1706
1976
  case "tool.set":
1707
1977
  next.tools[change.tool.id] = structuredClone(change.tool);
@@ -1709,6 +1979,12 @@ export function applySessionProtocolDelta(snapshot, message) {
1709
1979
  case "tool.remove":
1710
1980
  delete next.tools[change.id];
1711
1981
  break;
1982
+ case "operation.set":
1983
+ next.operations[change.operation.id] = structuredClone(change.operation);
1984
+ break;
1985
+ case "operation.remove":
1986
+ delete next.operations[change.id];
1987
+ break;
1712
1988
  case "agent.set":
1713
1989
  next.agents[change.agent.id] = structuredClone(change.agent);
1714
1990
  break;
@@ -1751,17 +2027,16 @@ function changeCanInvalidateSnapshot(snapshot, change) {
1751
2027
  const previous = snapshot.facets[change.facet.id];
1752
2028
  return !previous || !hasSameFacetSubject(previous, change.facet);
1753
2029
  }
1754
- case "timeline.replace": {
1755
- const previous = snapshot.timeline.find((item) => item.id === change.item.id);
1756
- return !previous || !hasSameTimelineReferences(previous, change.item);
1757
- }
1758
2030
  case "agent-state.set":
1759
2031
  case "message.append":
1760
2032
  case "message.replace":
1761
2033
  case "message.content.append":
1762
2034
  case "timeline.append":
1763
2035
  case "timeline.remove":
2036
+ case "timeline.advance":
1764
2037
  case "tool.remove":
2038
+ case "operation.set":
2039
+ case "operation.remove":
1765
2040
  case "agent.remove":
1766
2041
  case "facet.remove":
1767
2042
  return true;
@@ -1791,25 +2066,22 @@ function hasSameFacetSubject(previous, facet) {
1791
2066
  (previous.subject.type === "session" ||
1792
2067
  (facet.subject.type !== "session" && previous.subject.id === facet.subject.id)));
1793
2068
  }
1794
- function hasSameTimelineReferences(previous, item) {
1795
- if (previous.type !== item.type)
1796
- return false;
1797
- if (previous.type === "message" && item.type === "message") {
1798
- return previous.messageId === item.messageId;
1799
- }
1800
- return true;
1801
- }
1802
2069
  function applyKeyedRecordDelta(snapshot, message) {
1803
2070
  if (message.delta.type !== "snapshot.patch") {
1804
2071
  return undefined;
1805
2072
  }
1806
2073
  let nextTools;
2074
+ let nextOperations;
1807
2075
  let nextAgents;
1808
2076
  let nextFacets;
1809
2077
  const cloneTools = () => {
1810
2078
  nextTools ??= { ...snapshot.tools };
1811
2079
  return nextTools;
1812
2080
  };
2081
+ const cloneOperations = () => {
2082
+ nextOperations ??= { ...snapshot.operations };
2083
+ return nextOperations;
2084
+ };
1813
2085
  const cloneAgents = () => {
1814
2086
  nextAgents ??= { ...snapshot.agents };
1815
2087
  return nextAgents;
@@ -1826,6 +2098,12 @@ function applyKeyedRecordDelta(snapshot, message) {
1826
2098
  case "tool.remove":
1827
2099
  delete cloneTools()[change.id];
1828
2100
  break;
2101
+ case "operation.set":
2102
+ cloneOperations()[change.operation.id] = structuredClone(change.operation);
2103
+ break;
2104
+ case "operation.remove":
2105
+ delete cloneOperations()[change.id];
2106
+ break;
1829
2107
  case "agent.set":
1830
2108
  cloneAgents()[change.agent.id] = structuredClone(change.agent);
1831
2109
  break;
@@ -1846,6 +2124,7 @@ function applyKeyedRecordDelta(snapshot, message) {
1846
2124
  ...snapshot,
1847
2125
  revision: message.toRevision,
1848
2126
  ...(nextTools ? { tools: nextTools } : {}),
2127
+ ...(nextOperations ? { operations: nextOperations } : {}),
1849
2128
  ...(nextAgents ? { agents: nextAgents } : {}),
1850
2129
  ...(nextFacets ? { facets: nextFacets } : {}),
1851
2130
  };
@@ -2730,8 +3009,8 @@ function parseSessionProtocolDeltaMessage(payload) {
2730
3009
  if (hasIssue(deltaMessage.error, ["toRevision"])) {
2731
3010
  return fail("session.delta.toRevision must be a positive integer");
2732
3011
  }
2733
- if (hasIssue(deltaMessage.error, ["reason"])) {
2734
- return fail("session.delta.reason is invalid");
3012
+ if (hasIssue(deltaMessage.error, ["cause"])) {
3013
+ return fail("session.delta.cause is invalid");
2735
3014
  }
2736
3015
  if (hasIssue(deltaMessage.error, ["delta"])) {
2737
3016
  return fail("session.delta.delta is invalid");
@@ -2746,7 +3025,7 @@ function parseSessionProtocolDeltaMessage(payload) {
2746
3025
  sessionId: deltaMessage.data.sessionId,
2747
3026
  fromRevision: deltaMessage.data.fromRevision,
2748
3027
  toRevision: deltaMessage.data.toRevision,
2749
- reason: deltaMessage.data.reason,
3028
+ cause: deltaMessage.data.cause,
2750
3029
  delta: deltaMessage.data.delta,
2751
3030
  },
2752
3031
  };