@usabledev/usable-chat 1.178.3 → 1.180.0

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 (2) hide show
  1. package/cli.js +850 -97
  2. package/package.json +1 -1
package/cli.js CHANGED
@@ -4159,6 +4159,150 @@ var init_zod = __esm({
4159
4159
  }
4160
4160
  });
4161
4161
 
4162
+ // src/core/todos/types.ts
4163
+ function validateItemInvariants(items, context) {
4164
+ const ids = /* @__PURE__ */ new Set();
4165
+ let inProgressCount = 0;
4166
+ for (const [index2, item] of items.entries()) {
4167
+ if (ids.has(item.id)) {
4168
+ context.addIssue({
4169
+ code: external_exports.ZodIssueCode.custom,
4170
+ message: "Todo item IDs must be unique",
4171
+ path: [index2, "id"]
4172
+ });
4173
+ }
4174
+ ids.add(item.id);
4175
+ if (item.status === "in_progress") inProgressCount += 1;
4176
+ }
4177
+ if (inProgressCount > 1) {
4178
+ context.addIssue({
4179
+ code: external_exports.ZodIssueCode.custom,
4180
+ message: "Only one todo item may be in progress",
4181
+ path: []
4182
+ });
4183
+ }
4184
+ if (new TextEncoder().encode(JSON.stringify(items)).byteLength > MAX_TODO_SERIALIZED_BYTES) {
4185
+ context.addIssue({
4186
+ code: external_exports.ZodIssueCode.custom,
4187
+ message: "Todo list exceeds the serialized size limit",
4188
+ path: []
4189
+ });
4190
+ }
4191
+ }
4192
+ function parseTodoItems(value) {
4193
+ return todoItemsSchema.parse(value);
4194
+ }
4195
+ function parseTodoSnapshot(value) {
4196
+ return todoSnapshotSchema.parse(value);
4197
+ }
4198
+ function createEmptyTodoSnapshot() {
4199
+ return {
4200
+ schemaVersion: TODO_SCHEMA_VERSION,
4201
+ revision: 0,
4202
+ items: [],
4203
+ updatedAt: null,
4204
+ recentCommands: []
4205
+ };
4206
+ }
4207
+ function createExpiredTodoSnapshot(previous, now2) {
4208
+ const current = parseTodoSnapshot(previous);
4209
+ return parseTodoSnapshot({
4210
+ schemaVersion: TODO_SCHEMA_VERSION,
4211
+ revision: current.revision + 1,
4212
+ items: [],
4213
+ updatedAt: now2.toISOString(),
4214
+ recentCommands: []
4215
+ });
4216
+ }
4217
+ function createTodoCandidate(input) {
4218
+ const previous = parseTodoSnapshot(input.previous);
4219
+ const items = parseTodoItems(input.items);
4220
+ const revision = previous.revision + 1;
4221
+ return parseTodoSnapshot({
4222
+ schemaVersion: TODO_SCHEMA_VERSION,
4223
+ revision,
4224
+ items,
4225
+ updatedAt: input.now.toISOString(),
4226
+ recentCommands: [
4227
+ ...previous.recentCommands,
4228
+ { id: input.commandId, fingerprint: input.fingerprint, revision }
4229
+ ].slice(-MAX_TODO_COMMANDS)
4230
+ });
4231
+ }
4232
+ function isTodoSnapshotExpired(snapshot, now2) {
4233
+ if (!snapshot.updatedAt || snapshot.revision === 0) return false;
4234
+ if (snapshot.items.length === 0 && snapshot.recentCommands.length === 0) return false;
4235
+ const updatedAt = Date.parse(snapshot.updatedAt);
4236
+ return !Number.isFinite(updatedAt) || now2.getTime() - updatedAt >= TODO_TTL_MS;
4237
+ }
4238
+ var TODO_SCHEMA_VERSION, TODO_TTL_MS, MAX_TODO_ITEMS, MAX_TODO_TEXT_LENGTH, MAX_TODO_SERIALIZED_BYTES, MAX_TODO_COMMANDS, todoStatusSchema, todoItemSchema, todoCommandRecordSchema, todoSnapshotSchema, todoItemsSchema, todoUpdateInputSchema;
4239
+ var init_types2 = __esm({
4240
+ "src/core/todos/types.ts"() {
4241
+ "use strict";
4242
+ init_zod();
4243
+ TODO_SCHEMA_VERSION = 1;
4244
+ TODO_TTL_MS = 30 * 60 * 1e3;
4245
+ MAX_TODO_ITEMS = 50;
4246
+ MAX_TODO_TEXT_LENGTH = 300;
4247
+ MAX_TODO_SERIALIZED_BYTES = 16 * 1024;
4248
+ MAX_TODO_COMMANDS = 32;
4249
+ todoStatusSchema = external_exports.enum(["pending", "in_progress", "completed"]);
4250
+ todoItemSchema = external_exports.object({
4251
+ id: external_exports.string().uuid(),
4252
+ content: external_exports.string().trim().min(1).max(MAX_TODO_TEXT_LENGTH),
4253
+ status: todoStatusSchema
4254
+ }).strict();
4255
+ todoCommandRecordSchema = external_exports.object({
4256
+ id: external_exports.string().uuid(),
4257
+ fingerprint: external_exports.string().regex(/^[a-f0-9]{64}$/),
4258
+ revision: external_exports.number().int().positive()
4259
+ }).strict();
4260
+ todoSnapshotSchema = external_exports.object({
4261
+ schemaVersion: external_exports.literal(TODO_SCHEMA_VERSION),
4262
+ revision: external_exports.number().int().nonnegative(),
4263
+ items: external_exports.array(todoItemSchema).max(MAX_TODO_ITEMS),
4264
+ updatedAt: external_exports.string().datetime().nullable(),
4265
+ recentCommands: external_exports.array(todoCommandRecordSchema).max(MAX_TODO_COMMANDS)
4266
+ }).strict().superRefine((snapshot, context) => {
4267
+ validateItemInvariants(snapshot.items, context);
4268
+ if (snapshot.revision === 0 && snapshot.updatedAt !== null) {
4269
+ context.addIssue({
4270
+ code: external_exports.ZodIssueCode.custom,
4271
+ message: "An empty revision must not have an update timestamp",
4272
+ path: ["updatedAt"]
4273
+ });
4274
+ }
4275
+ if (snapshot.revision > 0 && snapshot.updatedAt === null) {
4276
+ context.addIssue({
4277
+ code: external_exports.ZodIssueCode.custom,
4278
+ message: "A persisted revision must have an update timestamp",
4279
+ path: ["updatedAt"]
4280
+ });
4281
+ }
4282
+ if (snapshot.revision === 0 && (snapshot.items.length > 0 || snapshot.recentCommands.length > 0)) {
4283
+ context.addIssue({
4284
+ code: external_exports.ZodIssueCode.custom,
4285
+ message: "Revision zero must be the canonical empty snapshot",
4286
+ path: []
4287
+ });
4288
+ }
4289
+ if (snapshot.recentCommands.some((command) => command.revision > snapshot.revision)) {
4290
+ context.addIssue({
4291
+ code: external_exports.ZodIssueCode.custom,
4292
+ message: "Command revision cannot exceed snapshot revision",
4293
+ path: ["recentCommands"]
4294
+ });
4295
+ }
4296
+ });
4297
+ todoItemsSchema = external_exports.array(todoItemSchema).max(MAX_TODO_ITEMS).superRefine((items, context) => validateItemInvariants(items, context));
4298
+ todoUpdateInputSchema = external_exports.object({
4299
+ baseRevision: external_exports.number().int().nonnegative(),
4300
+ commandId: external_exports.string().uuid(),
4301
+ items: todoItemsSchema
4302
+ }).strict();
4303
+ }
4304
+ });
4305
+
4162
4306
  // node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/core/core.js
4163
4307
  // @__NO_SIDE_EFFECTS__
4164
4308
  function $constructor(name18, initializer3, params) {
@@ -22858,7 +23002,7 @@ var init_ComponentLogger = __esm({
22858
23002
 
22859
23003
  // node_modules/.pnpm/@opentelemetry+api@1.9.0/node_modules/@opentelemetry/api/build/esm/diag/types.js
22860
23004
  var DiagLogLevel;
22861
- var init_types2 = __esm({
23005
+ var init_types3 = __esm({
22862
23006
  "node_modules/.pnpm/@opentelemetry+api@1.9.0/node_modules/@opentelemetry/api/build/esm/diag/types.js"() {
22863
23007
  "use strict";
22864
23008
  (function(DiagLogLevel2) {
@@ -22900,7 +23044,7 @@ function createLogLevelDiagLogger(maxLevel, logger5) {
22900
23044
  var init_logLevelLogger = __esm({
22901
23045
  "node_modules/.pnpm/@opentelemetry+api@1.9.0/node_modules/@opentelemetry/api/build/esm/diag/internal/logLevelLogger.js"() {
22902
23046
  "use strict";
22903
- init_types2();
23047
+ init_types3();
22904
23048
  }
22905
23049
  });
22906
23050
 
@@ -22911,7 +23055,7 @@ var init_diag = __esm({
22911
23055
  "use strict";
22912
23056
  init_ComponentLogger();
22913
23057
  init_logLevelLogger();
22914
- init_types2();
23058
+ init_types3();
22915
23059
  init_global_utils();
22916
23060
  __read2 = function(o21, n31) {
22917
23061
  var m33 = typeof Symbol === "function" && o21[Symbol.iterator];
@@ -50502,7 +50646,7 @@ var init_mask_color = __esm({
50502
50646
 
50503
50647
  // node_modules/.pnpm/bmp-ts@1.0.9/node_modules/bmp-ts/dist/esm/types.js
50504
50648
  var BmpCompression;
50505
- var init_types3 = __esm({
50649
+ var init_types4 = __esm({
50506
50650
  "node_modules/.pnpm/bmp-ts@1.0.9/node_modules/bmp-ts/dist/esm/types.js"() {
50507
50651
  "use strict";
50508
50652
  (function(BmpCompression2) {
@@ -50522,7 +50666,7 @@ var init_decoder = __esm({
50522
50666
  "use strict";
50523
50667
  init_header_types();
50524
50668
  init_mask_color();
50525
- init_types3();
50669
+ init_types4();
50526
50670
  BmpDecoder = class {
50527
50671
  // Header
50528
50672
  flag;
@@ -51161,7 +51305,7 @@ var init_esm3 = __esm({
51161
51305
  "use strict";
51162
51306
  init_decoder();
51163
51307
  init_encoder();
51164
- init_types3();
51308
+ init_types4();
51165
51309
  }
51166
51310
  });
51167
51311
 
@@ -79100,7 +79244,7 @@ var init_measure_text = __esm({
79100
79244
  });
79101
79245
 
79102
79246
  // node_modules/.pnpm/@jimp+plugin-print@1.6.1/node_modules/@jimp/plugin-print/dist/esm/types.js
79103
- var init_types4 = __esm({
79247
+ var init_types5 = __esm({
79104
79248
  "node_modules/.pnpm/@jimp+plugin-print@1.6.1/node_modules/@jimp/plugin-print/dist/esm/types.js"() {
79105
79249
  "use strict";
79106
79250
  }
@@ -79163,7 +79307,7 @@ var init_esm27 = __esm({
79163
79307
  init_zod();
79164
79308
  init_measure_text();
79165
79309
  init_measure_text();
79166
- init_types4();
79310
+ init_types5();
79167
79311
  PrintOptionsSchema = external_exports.object({
79168
79312
  /** the x position to draw the image */
79169
79313
  x: external_exports.number(),
@@ -92728,7 +92872,7 @@ function createJsonTransform(fn4) {
92728
92872
  };
92729
92873
  }
92730
92874
  var types, NotTagged, Identifier, Parameter, Builder, defaultHandlers, builders, serializers, parsers, mergeUserTypes, escapeIdentifier, inferType, escapeBackslash, escapeQuote, arraySerializer, arrayParserState, arrayParser, toCamel, toPascal, toKebab, fromCamel, fromPascal, fromKebab, camel, pascal, kebab;
92731
- var init_types5 = __esm({
92875
+ var init_types6 = __esm({
92732
92876
  "node_modules/.pnpm/postgres@3.4.7/node_modules/postgres/src/types.js"() {
92733
92877
  "use strict";
92734
92878
  init_query2();
@@ -93817,7 +93961,7 @@ var connection_default, uid, Sync, Flush, SSLRequest, ExecuteUnnamed, DescribeUn
93817
93961
  var init_connection = __esm({
93818
93962
  "node_modules/.pnpm/postgres@3.4.7/node_modules/postgres/src/connection.js"() {
93819
93963
  "use strict";
93820
- init_types5();
93964
+ init_types6();
93821
93965
  init_errors5();
93822
93966
  init_result();
93823
93967
  init_queue();
@@ -94554,7 +94698,7 @@ var src_default;
94554
94698
  var init_src = __esm({
94555
94699
  "node_modules/.pnpm/postgres@3.4.7/node_modules/postgres/src/index.js"() {
94556
94700
  "use strict";
94557
- init_types5();
94701
+ init_types6();
94558
94702
  init_connection();
94559
94703
  init_query2();
94560
94704
  init_queue();
@@ -104058,6 +104202,7 @@ __export(schema_exports, {
104058
104202
  configCollaborators: () => configCollaborators,
104059
104203
  conversationContext: () => conversationContext,
104060
104204
  conversationShares: () => conversationShares,
104205
+ conversationTodoState: () => conversationTodoState,
104061
104206
  conversations: () => conversations,
104062
104207
  customExperts: () => customExperts,
104063
104208
  embedKeys: () => embedKeys,
@@ -104082,7 +104227,7 @@ __export(schema_exports, {
104082
104227
  vaultSecrets: () => vaultSecrets,
104083
104228
  vizDailyStats: () => vizDailyStats
104084
104229
  });
104085
- var pathwayState, pathwayLeases, pathwayInstances, users, projects, conversations, messages2, artifacts, vizDailyStats, conversationShares, projectShares;
104230
+ var pathwayState, pathwayLeases, pathwayInstances, users, projects, conversations, conversationTodoState, messages2, artifacts, vizDailyStats, conversationShares, projectShares;
104086
104231
  var init_schema2 = __esm({
104087
104232
  "src/lib/db/schema.ts"() {
104088
104233
  "use strict";
@@ -104199,6 +104344,21 @@ var init_schema2 = __esm({
104199
104344
  parentIdIdx: index("conversations_parent_id_idx").on(table.parentConversationId)
104200
104345
  })
104201
104346
  );
104347
+ conversationTodoState = pgTable(
104348
+ "conversation_todo_state",
104349
+ {
104350
+ // No FK: conversation projection is eventually consistent and may land
104351
+ // after a first-turn checklist update. Lifecycle handlers remove rows.
104352
+ conversationId: uuid3("conversation_id").primaryKey(),
104353
+ userId: varchar("user_id", { length: 255 }).notNull(),
104354
+ snapshot: jsonb("snapshot").$type().notNull(),
104355
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull()
104356
+ },
104357
+ (table) => ({
104358
+ userIdIdx: index("conversation_todo_state_user_id_idx").on(table.userId),
104359
+ updatedAtIdx: index("conversation_todo_state_updated_at_idx").on(table.updatedAt)
104360
+ })
104361
+ );
104202
104362
  messages2 = pgTable(
104203
104363
  "messages",
104204
104364
  {
@@ -110919,9 +111079,9 @@ var require_dist_cjs12 = __commonJS({
110919
111079
  httpHandler: httpHandlerExtensionConfiguration.httpHandler()
110920
111080
  };
110921
111081
  }, "resolveHttpHandlerRuntimeConfig");
110922
- var import_types20 = require_dist_cjs11();
111082
+ var import_types25 = require_dist_cjs11();
110923
111083
  var _Field = class _Field {
110924
- constructor({ name: name18, kind = import_types20.FieldPosition.HEADER, values: values2 = [] }) {
111084
+ constructor({ name: name18, kind = import_types25.FieldPosition.HEADER, values: values2 = [] }) {
110925
111085
  this.name = name18;
110926
111086
  this.kind = kind;
110927
111087
  this.values = values2;
@@ -111231,8 +111391,8 @@ var require_dist_cjs14 = __commonJS({
111231
111391
  normalizeProvider: () => normalizeProvider8
111232
111392
  });
111233
111393
  module.exports = __toCommonJS2(src_exports2);
111234
- var import_types20 = require_dist_cjs13();
111235
- var getSmithyContext13 = /* @__PURE__ */ __name((context) => context[import_types20.SMITHY_CONTEXT_KEY] || (context[import_types20.SMITHY_CONTEXT_KEY] = {}), "getSmithyContext");
111394
+ var import_types25 = require_dist_cjs13();
111395
+ var getSmithyContext13 = /* @__PURE__ */ __name((context) => context[import_types25.SMITHY_CONTEXT_KEY] || (context[import_types25.SMITHY_CONTEXT_KEY] = {}), "getSmithyContext");
111236
111396
  var normalizeProvider8 = /* @__PURE__ */ __name((input) => {
111237
111397
  if (typeof input === "function")
111238
111398
  return input;
@@ -113015,12 +113175,12 @@ var require_dist_cjs24 = __commonJS({
113015
113175
  });
113016
113176
 
113017
113177
  // node_modules/.pnpm/@smithy+core@3.23.11/node_modules/@smithy/core/dist-es/getSmithyContext.js
113018
- var import_types12, getSmithyContext;
113178
+ var import_types13, getSmithyContext;
113019
113179
  var init_getSmithyContext = __esm({
113020
113180
  "node_modules/.pnpm/@smithy+core@3.23.11/node_modules/@smithy/core/dist-es/getSmithyContext.js"() {
113021
113181
  "use strict";
113022
- import_types12 = __toESM(require_dist_cjs2());
113023
- getSmithyContext = (context) => context[import_types12.SMITHY_CONTEXT_KEY] || (context[import_types12.SMITHY_CONTEXT_KEY] = {});
113182
+ import_types13 = __toESM(require_dist_cjs2());
113183
+ getSmithyContext = (context) => context[import_types13.SMITHY_CONTEXT_KEY] || (context[import_types13.SMITHY_CONTEXT_KEY] = {});
113024
113184
  }
113025
113185
  });
113026
113186
 
@@ -117857,12 +118017,12 @@ var init_DefaultIdentityProviderConfig = __esm({
117857
118017
  });
117858
118018
 
117859
118019
  // node_modules/.pnpm/@smithy+core@3.23.11/node_modules/@smithy/core/dist-es/util-identity-and-auth/httpAuthSchemes/httpApiKeyAuth.js
117860
- var import_protocol_http7, import_types13, HttpApiKeyAuthSigner;
118020
+ var import_protocol_http7, import_types14, HttpApiKeyAuthSigner;
117861
118021
  var init_httpApiKeyAuth = __esm({
117862
118022
  "node_modules/.pnpm/@smithy+core@3.23.11/node_modules/@smithy/core/dist-es/util-identity-and-auth/httpAuthSchemes/httpApiKeyAuth.js"() {
117863
118023
  "use strict";
117864
118024
  import_protocol_http7 = __toESM(require_dist_cjs3());
117865
- import_types13 = __toESM(require_dist_cjs2());
118025
+ import_types14 = __toESM(require_dist_cjs2());
117866
118026
  HttpApiKeyAuthSigner = class {
117867
118027
  async sign(httpRequest, identity, signingProperties) {
117868
118028
  if (!signingProperties) {
@@ -117878,9 +118038,9 @@ var init_httpApiKeyAuth = __esm({
117878
118038
  throw new Error("request could not be signed with `apiKey` since the `apiKey` is not defined");
117879
118039
  }
117880
118040
  const clonedRequest = import_protocol_http7.HttpRequest.clone(httpRequest);
117881
- if (signingProperties.in === import_types13.HttpApiKeyAuthLocation.QUERY) {
118041
+ if (signingProperties.in === import_types14.HttpApiKeyAuthLocation.QUERY) {
117882
118042
  clonedRequest.query[signingProperties.name] = identity.apiKey;
117883
- } else if (signingProperties.in === import_types13.HttpApiKeyAuthLocation.HEADER) {
118043
+ } else if (signingProperties.in === import_types14.HttpApiKeyAuthLocation.HEADER) {
117884
118044
  clonedRequest.headers[signingProperties.name] = signingProperties.scheme ? `${signingProperties.scheme} ${identity.apiKey}` : identity.apiKey;
117885
118045
  } else {
117886
118046
  throw new Error("request can only be signed with `apiKey` locations `query` or `header`, but found: `" + signingProperties.in + "`");
@@ -139370,7 +139530,7 @@ var require_dist_cjs81 = __commonJS({
139370
139530
  const fromContext = context.streamCollector(streamBody);
139371
139531
  return import_util_stream3.Uint8ArrayBlobAdapter.mutate(await fromContext);
139372
139532
  }, "collectBody");
139373
- var import_types20 = require_dist_cjs11();
139533
+ var import_types25 = require_dist_cjs11();
139374
139534
  var _Command = class _Command {
139375
139535
  constructor() {
139376
139536
  this.middlewareStack = (0, import_middleware_stack.constructStack)();
@@ -139406,7 +139566,7 @@ var require_dist_cjs81 = __commonJS({
139406
139566
  commandName,
139407
139567
  inputFilterSensitiveLog,
139408
139568
  outputFilterSensitiveLog,
139409
- [import_types20.SMITHY_CONTEXT_KEY]: {
139569
+ [import_types25.SMITHY_CONTEXT_KEY]: {
139410
139570
  ...smithyContext
139411
139571
  },
139412
139572
  ...additionalContext
@@ -140092,8 +140252,8 @@ var require_dist_cjs81 = __commonJS({
140092
140252
  }, "emitWarningIfUnsupportedVersion");
140093
140253
  var getChecksumConfiguration = /* @__PURE__ */ __name((runtimeConfig) => {
140094
140254
  const checksumAlgorithms = [];
140095
- for (const id in import_types20.AlgorithmId) {
140096
- const algorithmId = import_types20.AlgorithmId[id];
140255
+ for (const id in import_types25.AlgorithmId) {
140256
+ const algorithmId = import_types25.AlgorithmId[id];
140097
140257
  if (runtimeConfig[algorithmId] === void 0) {
140098
140258
  continue;
140099
140259
  }
@@ -148124,7 +148284,7 @@ var init_zod_compat = __esm({
148124
148284
 
148125
148285
  // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/types.js
148126
148286
  var LATEST_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS, RELATED_TASK_META_KEY, JSONRPC_VERSION, AssertObjectSchema, ProgressTokenSchema, CursorSchema, TaskCreationParamsSchema, TaskMetadataSchema, RelatedTaskMetadataSchema, RequestMetaSchema, BaseRequestParamsSchema, TaskAugmentedRequestParamsSchema, isTaskAugmentedRequestParams, RequestSchema, NotificationsParamsSchema, NotificationSchema, ResultSchema, RequestIdSchema, JSONRPCRequestSchema, isJSONRPCRequest, JSONRPCNotificationSchema, isJSONRPCNotification, JSONRPCResultResponseSchema, isJSONRPCResultResponse, ErrorCode, JSONRPCErrorResponseSchema, isJSONRPCErrorResponse, JSONRPCMessageSchema, JSONRPCResponseSchema, EmptyResultSchema, CancelledNotificationParamsSchema, CancelledNotificationSchema, IconSchema, IconsSchema, BaseMetadataSchema, ImplementationSchema, FormElicitationCapabilitySchema, ElicitationCapabilitySchema, ClientTasksCapabilitySchema, ServerTasksCapabilitySchema, ClientCapabilitiesSchema, InitializeRequestParamsSchema, InitializeRequestSchema, ServerCapabilitiesSchema, InitializeResultSchema, InitializedNotificationSchema, isInitializedNotification, PingRequestSchema, ProgressSchema, ProgressNotificationParamsSchema, ProgressNotificationSchema, PaginatedRequestParamsSchema, PaginatedRequestSchema, PaginatedResultSchema, TaskStatusSchema, TaskSchema, CreateTaskResultSchema, TaskStatusNotificationParamsSchema, TaskStatusNotificationSchema, GetTaskRequestSchema, GetTaskResultSchema, GetTaskPayloadRequestSchema, GetTaskPayloadResultSchema, ListTasksRequestSchema, ListTasksResultSchema, CancelTaskRequestSchema, CancelTaskResultSchema, ResourceContentsSchema, TextResourceContentsSchema, Base64Schema, BlobResourceContentsSchema, RoleSchema, AnnotationsSchema, ResourceSchema, ResourceTemplateSchema, ListResourcesRequestSchema, ListResourcesResultSchema, ListResourceTemplatesRequestSchema, ListResourceTemplatesResultSchema, ResourceRequestParamsSchema, ReadResourceRequestParamsSchema, ReadResourceRequestSchema, ReadResourceResultSchema, ResourceListChangedNotificationSchema, SubscribeRequestParamsSchema, SubscribeRequestSchema, UnsubscribeRequestParamsSchema, UnsubscribeRequestSchema, ResourceUpdatedNotificationParamsSchema, ResourceUpdatedNotificationSchema, PromptArgumentSchema, PromptSchema, ListPromptsRequestSchema, ListPromptsResultSchema, GetPromptRequestParamsSchema, GetPromptRequestSchema, TextContentSchema, ImageContentSchema, AudioContentSchema, ToolUseContentSchema, EmbeddedResourceSchema, ResourceLinkSchema, ContentBlockSchema, PromptMessageSchema, GetPromptResultSchema, PromptListChangedNotificationSchema, ToolAnnotationsSchema, ToolExecutionSchema, ToolSchema, ListToolsRequestSchema, ListToolsResultSchema, CallToolResultSchema, CompatibilityCallToolResultSchema, CallToolRequestParamsSchema, CallToolRequestSchema, ToolListChangedNotificationSchema, ListChangedOptionsBaseSchema, LoggingLevelSchema, SetLevelRequestParamsSchema, SetLevelRequestSchema, LoggingMessageNotificationParamsSchema, LoggingMessageNotificationSchema, ModelHintSchema, ModelPreferencesSchema, ToolChoiceSchema, ToolResultContentSchema, SamplingContentSchema, SamplingMessageContentBlockSchema, SamplingMessageSchema, CreateMessageRequestParamsSchema, CreateMessageRequestSchema, CreateMessageResultSchema, CreateMessageResultWithToolsSchema, BooleanSchemaSchema, StringSchemaSchema, NumberSchemaSchema, UntitledSingleSelectEnumSchemaSchema, TitledSingleSelectEnumSchemaSchema, LegacyTitledEnumSchemaSchema, SingleSelectEnumSchemaSchema, UntitledMultiSelectEnumSchemaSchema, TitledMultiSelectEnumSchemaSchema, MultiSelectEnumSchemaSchema, EnumSchemaSchema, PrimitiveSchemaDefinitionSchema, ElicitRequestFormParamsSchema, ElicitRequestURLParamsSchema, ElicitRequestParamsSchema, ElicitRequestSchema, ElicitationCompleteNotificationParamsSchema, ElicitationCompleteNotificationSchema, ElicitResultSchema, ResourceTemplateReferenceSchema, PromptReferenceSchema, CompleteRequestParamsSchema, CompleteRequestSchema, CompleteResultSchema, RootSchema, ListRootsRequestSchema, ListRootsResultSchema, RootsListChangedNotificationSchema, ClientRequestSchema, ClientNotificationSchema, ClientResultSchema, ServerRequestSchema, ServerNotificationSchema, ServerResultSchema, McpError, UrlElicitationRequiredError;
148127
- var init_types6 = __esm({
148287
+ var init_types7 = __esm({
148128
148288
  "node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/types.js"() {
148129
148289
  "use strict";
148130
148290
  init_v4();
@@ -150098,7 +150258,7 @@ var init_protocol = __esm({
150098
150258
  "node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js"() {
150099
150259
  "use strict";
150100
150260
  init_zod_compat();
150101
- init_types6();
150261
+ init_types7();
150102
150262
  init_interfaces();
150103
150263
  init_zod_json_schema_compat();
150104
150264
  DEFAULT_REQUEST_TIMEOUT_MSEC = 6e4;
@@ -157972,7 +158132,7 @@ var ExperimentalClientTasks;
157972
158132
  var init_client6 = __esm({
157973
158133
  "node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/client.js"() {
157974
158134
  "use strict";
157975
- init_types6();
158135
+ init_types7();
157976
158136
  ExperimentalClientTasks = class {
157977
158137
  constructor(_client2) {
157978
158138
  this._client = _client2;
@@ -158215,7 +158375,7 @@ var init_client7 = __esm({
158215
158375
  "node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/client/index.js"() {
158216
158376
  "use strict";
158217
158377
  init_protocol();
158218
- init_types6();
158378
+ init_types7();
158219
158379
  init_ajv_provider();
158220
158380
  init_zod_compat();
158221
158381
  init_client6();
@@ -159595,7 +159755,7 @@ var init_auth4 = __esm({
159595
159755
  "node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/client/auth.js"() {
159596
159756
  "use strict";
159597
159757
  init_index_node();
159598
- init_types6();
159758
+ init_types7();
159599
159759
  init_auth3();
159600
159760
  init_auth3();
159601
159761
  init_auth_utils();
@@ -159616,7 +159776,7 @@ var init_streamableHttp = __esm({
159616
159776
  "node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/client/streamableHttp.js"() {
159617
159777
  "use strict";
159618
159778
  init_transport();
159619
- init_types6();
159779
+ init_types7();
159620
159780
  init_auth4();
159621
159781
  init_stream();
159622
159782
  DEFAULT_STREAMABLE_HTTP_RECONNECTION_OPTIONS = {
@@ -181559,7 +181719,7 @@ var require_websocket = __commonJS({
181559
181719
  var http3 = __require("http");
181560
181720
  var net2 = __require("net");
181561
181721
  var tls2 = __require("tls");
181562
- var { randomBytes: randomBytes5, createHash: createHash6 } = __require("crypto");
181722
+ var { randomBytes: randomBytes5, createHash: createHash7 } = __require("crypto");
181563
181723
  var { Duplex, Readable: Readable3 } = __require("stream");
181564
181724
  var { URL: URL2 } = __require("url");
181565
181725
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -182219,7 +182379,7 @@ var require_websocket = __commonJS({
182219
182379
  abortHandshake(websocket, socket, "Invalid Upgrade header");
182220
182380
  return;
182221
182381
  }
182222
- const digest = createHash6("sha1").update(key + GUID).digest("base64");
182382
+ const digest = createHash7("sha1").update(key + GUID).digest("base64");
182223
182383
  if (res.headers["sec-websocket-accept"] !== digest) {
182224
182384
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
182225
182385
  return;
@@ -182586,7 +182746,7 @@ var require_websocket_server = __commonJS({
182586
182746
  var EventEmitter4 = __require("events");
182587
182747
  var http3 = __require("http");
182588
182748
  var { Duplex } = __require("stream");
182589
- var { createHash: createHash6 } = __require("crypto");
182749
+ var { createHash: createHash7 } = __require("crypto");
182590
182750
  var extension2 = require_extension();
182591
182751
  var PerMessageDeflate2 = require_permessage_deflate();
182592
182752
  var subprotocol2 = require_subprotocol();
@@ -182887,7 +183047,7 @@ var require_websocket_server = __commonJS({
182887
183047
  );
182888
183048
  }
182889
183049
  if (this._state > RUNNING) return abortHandshake(socket, 503);
182890
- const digest = createHash6("sha1").update(key + GUID).digest("base64");
183050
+ const digest = createHash7("sha1").update(key + GUID).digest("base64");
182891
183051
  const headers = [
182892
183052
  "HTTP/1.1 101 Switching Protocols",
182893
183053
  "Upgrade: websocket",
@@ -202533,8 +202693,10 @@ __export(tool_categories_exports, {
202533
202693
  filterPlanModeTools: () => filterPlanModeTools,
202534
202694
  filterToExpertTools: () => filterToExpertTools,
202535
202695
  filterToMainAgentTools: () => filterToMainAgentTools,
202696
+ filterToPersonaEnabledTools: () => filterToPersonaEnabledTools,
202536
202697
  getExpertForTool: () => getExpertForTool,
202537
202698
  getToolsForExpert: () => getToolsForExpert,
202699
+ hasPersonaToolAllowlist: () => hasPersonaToolAllowlist,
202538
202700
  isExpertOnlyTool: () => isExpertOnlyTool,
202539
202701
  isMainAgentTool: () => isMainAgentTool
202540
202702
  });
@@ -202564,6 +202726,19 @@ function filterToMainAgentTools(allTools) {
202564
202726
  }
202565
202727
  return filtered;
202566
202728
  }
202729
+ function hasPersonaToolAllowlist(enabledTools) {
202730
+ return enabledTools !== void 0;
202731
+ }
202732
+ function filterToPersonaEnabledTools(tools, enabledTools) {
202733
+ const enabled = new Set(enabledTools);
202734
+ return Object.fromEntries(
202735
+ Object.entries(tools).filter(([name18]) => {
202736
+ if (name18.startsWith("parent_")) return true;
202737
+ const baseName = extractBaseToolName(name18);
202738
+ return enabled.has(name18) || enabled.has(baseName);
202739
+ })
202740
+ );
202741
+ }
202567
202742
  function extractBaseToolName(name18) {
202568
202743
  if (name18.includes("__")) return name18.split("__").pop() || name18;
202569
202744
  if (name18.startsWith("mcp_usable_")) return name18.slice("mcp_usable_".length);
@@ -202668,7 +202843,8 @@ var init_tool_categories = __esm({
202668
202843
  "browser_folder__write_sandbox_file",
202669
202844
  "browser_folder__edit_file",
202670
202845
  "browser_folder__create_directory",
202671
- "browser_folder__delete_entry"
202846
+ "browser_folder__delete_entry",
202847
+ "update_todo_list"
202672
202848
  ];
202673
202849
  PLAN_MODE_FILTERED_TOOLS = [
202674
202850
  ...DISCUSSION_MODE_FILTERED_TOOLS,
@@ -214659,6 +214835,197 @@ Use this to discover the current state of background work \u2014 running, comple
214659
214835
  }
214660
214836
  });
214661
214837
 
214838
+ // src/core/tools/todo-list.ts
214839
+ import { createHash as createHash6 } from "node:crypto";
214840
+ function fingerprintUpdate(input) {
214841
+ return createHash6("sha256").update(JSON.stringify({ baseRevision: input.baseRevision, items: input.items })).digest("hex");
214842
+ }
214843
+ async function withTodoMutationLock(conversationId, task) {
214844
+ const previous = mutationQueues.get(conversationId) ?? Promise.resolve();
214845
+ let release;
214846
+ const gate = new Promise((resolve8) => {
214847
+ release = resolve8;
214848
+ });
214849
+ const queued = previous.catch(() => {
214850
+ }).then(() => gate);
214851
+ mutationQueues.set(conversationId, queued);
214852
+ await previous.catch(() => {
214853
+ });
214854
+ try {
214855
+ return await task();
214856
+ } finally {
214857
+ release();
214858
+ if (mutationQueues.get(conversationId) === queued) mutationQueues.delete(conversationId);
214859
+ }
214860
+ }
214861
+ async function writeMirror(context, snapshot) {
214862
+ if (!context.bash) return;
214863
+ await context.bash.fs.writeFile("/persist/todos.json", JSON.stringify(snapshot, null, 2));
214864
+ }
214865
+ async function restoreMirror(context, snapshot) {
214866
+ try {
214867
+ await writeMirror(context, snapshot);
214868
+ } catch {
214869
+ }
214870
+ }
214871
+ function requireConversation(context) {
214872
+ if (!context.conversationId) throw new Error("A conversation is required for todo lists");
214873
+ return { conversationId: context.conversationId, ownerId: context.session.user?.id };
214874
+ }
214875
+ var mutationQueues, readTodoListTool, updateTodoListTool;
214876
+ var init_todo_list = __esm({
214877
+ "src/core/tools/todo-list.ts"() {
214878
+ "use strict";
214879
+ init_zod();
214880
+ init_types2();
214881
+ mutationQueues = /* @__PURE__ */ new Map();
214882
+ readTodoListTool = {
214883
+ name: "read_todo_list",
214884
+ description: "Read the current agent execution checklist. Use this before updating an existing checklist so you have its latest revision.",
214885
+ parameters: external_exports.object({}).strict(),
214886
+ type: "retrieve",
214887
+ async execute(_params, context) {
214888
+ try {
214889
+ const identity = requireConversation(context);
214890
+ const snapshot = context.todoStore && !context.ephemeral ? await context.todoStore.loadTodoSnapshot(identity) : context.todoSession?.snapshot ?? createEmptyTodoSnapshot();
214891
+ if (context.todoSession) context.todoSession.snapshot = snapshot;
214892
+ await restoreMirror(context, snapshot);
214893
+ return {
214894
+ success: true,
214895
+ data: { persistent: Boolean(context.todoStore) && !context.ephemeral, snapshot }
214896
+ };
214897
+ } catch (error41) {
214898
+ return { success: false, error: error41 instanceof Error ? error41.message : String(error41) };
214899
+ }
214900
+ }
214901
+ };
214902
+ updateTodoListTool = {
214903
+ name: "update_todo_list",
214904
+ description: `Replace the agent's complete execution checklist atomically. Use stable UUIDs for retained items, keep list order meaningful, allow at most one in_progress item, and send an empty items array to clear. Read the list first and pass its revision as baseRevision.`,
214905
+ parameters: todoUpdateInputSchema,
214906
+ type: "update",
214907
+ async execute(rawInput, context) {
214908
+ if (context.chatMode === "discussion" || context.chatMode === "plan") {
214909
+ return {
214910
+ success: false,
214911
+ error: `Todo list updates are unavailable in ${context.chatMode} mode`
214912
+ };
214913
+ }
214914
+ let identity;
214915
+ try {
214916
+ identity = requireConversation(context);
214917
+ } catch (error41) {
214918
+ return { success: false, error: error41 instanceof Error ? error41.message : String(error41) };
214919
+ }
214920
+ const input = todoUpdateInputSchema.parse(rawInput);
214921
+ const fingerprint = fingerprintUpdate(input);
214922
+ const transient = context.ephemeral || !context.todoStore;
214923
+ return withTodoMutationLock(identity.conversationId, async () => {
214924
+ try {
214925
+ const previous = transient ? context.todoSession?.snapshot ?? createEmptyTodoSnapshot() : await context.todoStore.loadTodoSnapshot(identity);
214926
+ const priorCommand = previous.recentCommands.find(
214927
+ (command) => command.id === input.commandId
214928
+ );
214929
+ if (priorCommand) {
214930
+ if (priorCommand.fingerprint !== fingerprint) {
214931
+ return {
214932
+ success: false,
214933
+ error: "Todo command ID was already used with a different payload"
214934
+ };
214935
+ }
214936
+ try {
214937
+ await writeMirror(context, previous);
214938
+ } catch (error41) {
214939
+ return {
214940
+ success: false,
214941
+ error: `Could not mirror todo list: ${error41 instanceof Error ? error41.message : String(error41)}`
214942
+ };
214943
+ }
214944
+ if (context.todoSession) context.todoSession.snapshot = previous;
214945
+ return {
214946
+ success: true,
214947
+ data: { persistent: !transient, status: "idempotent", snapshot: previous }
214948
+ };
214949
+ }
214950
+ if (previous.revision !== input.baseRevision) {
214951
+ return {
214952
+ success: false,
214953
+ error: `Todo revision conflict: expected ${input.baseRevision}, current revision is ${previous.revision}`
214954
+ };
214955
+ }
214956
+ const candidate = createTodoCandidate({
214957
+ previous,
214958
+ items: input.items,
214959
+ commandId: input.commandId,
214960
+ fingerprint,
214961
+ now: /* @__PURE__ */ new Date()
214962
+ });
214963
+ try {
214964
+ await writeMirror(context, candidate);
214965
+ } catch (error41) {
214966
+ return {
214967
+ success: false,
214968
+ error: `Could not mirror todo list: ${error41 instanceof Error ? error41.message : String(error41)}`
214969
+ };
214970
+ }
214971
+ let status = "committed";
214972
+ let snapshot = candidate;
214973
+ if (!transient) {
214974
+ let result;
214975
+ try {
214976
+ result = await context.todoStore.compareAndSwapTodoSnapshot({
214977
+ ...identity,
214978
+ expectedRevision: input.baseRevision,
214979
+ candidate,
214980
+ commandId: input.commandId,
214981
+ fingerprint
214982
+ });
214983
+ } catch (error41) {
214984
+ await restoreMirror(context, previous);
214985
+ throw error41;
214986
+ }
214987
+ snapshot = parseTodoSnapshot(result.snapshot);
214988
+ if (result.status === "conflict") {
214989
+ await restoreMirror(context, previous);
214990
+ const command = snapshot.recentCommands.find((entry) => entry.id === input.commandId);
214991
+ if (command && command.fingerprint !== fingerprint) {
214992
+ return {
214993
+ success: false,
214994
+ error: "Todo command ID was already used with a different payload"
214995
+ };
214996
+ }
214997
+ return {
214998
+ success: false,
214999
+ error: `Todo revision conflict: expected ${input.baseRevision}, current revision is ${snapshot.revision}`
215000
+ };
215001
+ }
215002
+ status = result.status;
215003
+ }
215004
+ if (context.todoSession) context.todoSession.snapshot = snapshot;
215005
+ if (status === "committed") {
215006
+ const event = {
215007
+ schemaVersion: TODO_SCHEMA_VERSION,
215008
+ conversationId: identity.conversationId,
215009
+ turnId: context.messageId,
215010
+ toolCallId: context.toolCallId ?? input.commandId,
215011
+ persistent: !transient,
215012
+ snapshot
215013
+ };
215014
+ context.emit?.("todo-list-updated", event);
215015
+ }
215016
+ return { success: true, data: { persistent: !transient, status, snapshot } };
215017
+ } catch (error41) {
215018
+ return {
215019
+ success: false,
215020
+ error: `Could not persist todo list: ${error41 instanceof Error ? error41.message : String(error41)}`
215021
+ };
215022
+ }
215023
+ });
215024
+ }
215025
+ };
215026
+ }
215027
+ });
215028
+
214662
215029
  // src/core/billing/credit-authorization.ts
214663
215030
  async function authorizeCreditBeforeProvider(port, input) {
214664
215031
  if (!port) return void 0;
@@ -214996,7 +215363,9 @@ async function storePauseState(state2) {
214996
215363
  await redis.set(pauseKey(state2.resumeToken), JSON.stringify(state2), "PX", ttlMs);
214997
215364
  return;
214998
215365
  } catch (error41) {
214999
- logger.warn("askQuestion", "Redis store failed, falling back to SQLite", { error: String(error41) });
215366
+ logger.warn("askQuestion", "Redis store failed, falling back to SQLite", {
215367
+ error: String(error41)
215368
+ });
215000
215369
  }
215001
215370
  }
215002
215371
  sqliteStorePauseState(state2.resumeToken, state2, {
@@ -215020,7 +215389,9 @@ async function getPauseState(resumeToken) {
215020
215389
  const inFlightStreamKey = await redis.get(inflightKey(resumeToken));
215021
215390
  return { state: state2, consumedAt, inFlightStreamKey };
215022
215391
  } catch (error41) {
215023
- logger.warn("askQuestion", "Redis get failed, falling back to SQLite", { error: String(error41) });
215392
+ logger.warn("askQuestion", "Redis get failed, falling back to SQLite", {
215393
+ error: String(error41)
215394
+ });
215024
215395
  }
215025
215396
  }
215026
215397
  const row = sqliteGetPauseState(resumeToken);
@@ -215037,7 +215408,9 @@ async function deletePauseState(resumeToken) {
215037
215408
  try {
215038
215409
  await redis.del(pauseKey(resumeToken), consumedKey(resumeToken), inflightKey(resumeToken));
215039
215410
  } catch (error41) {
215040
- logger.warn("askQuestion", "Redis delete failed, falling back to SQLite", { error: String(error41) });
215411
+ logger.warn("askQuestion", "Redis delete failed, falling back to SQLite", {
215412
+ error: String(error41)
215413
+ });
215041
215414
  }
215042
215415
  }
215043
215416
  sqliteDeletePauseState(resumeToken);
@@ -215056,7 +215429,9 @@ async function claimConsume(resumeToken) {
215056
215429
  );
215057
215430
  return result === "OK";
215058
215431
  } catch (error41) {
215059
- logger.warn("askQuestion", "Redis claimConsume failed, falling back to SQLite", { error: String(error41) });
215432
+ logger.warn("askQuestion", "Redis claimConsume failed, falling back to SQLite", {
215433
+ error: String(error41)
215434
+ });
215060
215435
  }
215061
215436
  }
215062
215437
  return sqliteClaimConsume(resumeToken, idempotencyMs);
@@ -215069,7 +215444,9 @@ async function setInFlightStreamKey(resumeToken, streamKey) {
215069
215444
  await redis.set(inflightKey(resumeToken), streamKey, "PX", idempotencyMs);
215070
215445
  return;
215071
215446
  } catch (error41) {
215072
- logger.warn("askQuestion", "Redis setInFlightStreamKey failed, falling back to SQLite", { error: String(error41) });
215447
+ logger.warn("askQuestion", "Redis setInFlightStreamKey failed, falling back to SQLite", {
215448
+ error: String(error41)
215449
+ });
215073
215450
  }
215074
215451
  }
215075
215452
  sqliteSetInFlightStreamKey(resumeToken, streamKey);
@@ -286854,6 +287231,7 @@ async function drainPendingPause({
286854
287231
  mcpTools,
286855
287232
  questions: data2.questions,
286856
287233
  context: data2.context,
287234
+ temporary: context.metadata?.temporary === true,
286857
287235
  embedSnapshot,
286858
287236
  pausedAt: now2.toISOString(),
286859
287237
  expiresAt: expiresAt.toISOString()
@@ -287263,21 +287641,37 @@ async function orchestrate(request) {
287263
287641
  return [name18, cleaned];
287264
287642
  })
287265
287643
  );
287644
+ const todoSession = { snapshot: createEmptyTodoSnapshot() };
287645
+ const agentProgressEnabled = context.metadata?.agentProgressEnabled === true;
287646
+ if (agentProgressEnabled) {
287647
+ const toJsonSchema2 = (await Promise.resolve().then(() => (init_esm2(), esm_exports))).zodToJsonSchema;
287648
+ const asTodoAiTool = (todoTool) => {
287649
+ const parameters = toJsonSchema2(todoTool.parameters, { $refStrategy: "none" });
287650
+ delete parameters.$schema;
287651
+ return {
287652
+ description: todoTool.description,
287653
+ parameters,
287654
+ execute: async (args, options2) => {
287655
+ const toolContext = options2?.executionContext;
287656
+ if (!toolContext) throw new Error("Todo tool execution context is unavailable");
287657
+ toolContext.toolCallId = options2?.toolCallId;
287658
+ toolContext.todoStore = context.todoStore;
287659
+ toolContext.todoSession = todoSession;
287660
+ toolContext.chatMode = context.metadata?.chatMode;
287661
+ toolContext.ephemeral = context.metadata?.temporary === true;
287662
+ const result = await todoTool.execute(args, toolContext);
287663
+ if (!result.success) return { error: result.error };
287664
+ return result.data;
287665
+ }
287666
+ };
287667
+ };
287668
+ aiTools.read_todo_list = asTodoAiTool(readTodoListTool);
287669
+ aiTools.update_todo_list = asTodoAiTool(updateTodoListTool);
287670
+ }
287266
287671
  const allToolsForSubagents = { ...aiTools };
287267
- if (persona.enabledTools && persona.enabledTools.length > 0) {
287268
- const customEnabledTools = new Set(persona.enabledTools);
287269
- const filteredTools = {};
287270
- for (const [name18, tool2] of Object.entries(aiTools)) {
287271
- if (name18.startsWith("parent_")) {
287272
- filteredTools[name18] = tool2;
287273
- continue;
287274
- }
287275
- const baseName = name18.includes("__") ? name18.split("__").pop() || name18 : name18;
287276
- if (customEnabledTools.has(name18) || customEnabledTools.has(baseName)) {
287277
- filteredTools[name18] = tool2;
287278
- }
287279
- }
287280
- aiTools = filteredTools;
287672
+ delete allToolsForSubagents.update_todo_list;
287673
+ if (hasPersonaToolAllowlist(persona.enabledTools)) {
287674
+ aiTools = filterToPersonaEnabledTools(aiTools, persona.enabledTools);
287281
287675
  const parentToolCount = Object.keys(aiTools).filter((n31) => n31.startsWith("parent_")).length;
287282
287676
  orchestrationLogger.info("Custom tool filtering applied (embed main agent)", {
287283
287677
  totalMcpTools: Object.keys(allToolsForSubagents).length,
@@ -288850,7 +289244,7 @@ ${allowedWorkspaceIds.map((id, idx) => ` ${idx + 1}. \`${id}\``).join("\n")}
288850
289244
  - Once you know the workspace, include it in your tool calls
288851
289245
  `;
288852
289246
  }
288853
- const hasRestrictedTools = persona.enabledTools && persona.enabledTools.length > 0;
289247
+ const hasRestrictedTools = hasPersonaToolAllowlist(persona.enabledTools);
288854
289248
  const availableToolNames = hasRestrictedTools ? persona.enabledTools : null;
288855
289249
  let usableKnowledgeBaseSection = "";
288856
289250
  if (!hasRestrictedTools) {
@@ -289040,6 +289434,16 @@ Never treat a markdown file in working memory as a completed deliverable.
289040
289434
  systemMessageParts.push(LANGUAGE_PROMPT);
289041
289435
  }
289042
289436
  }
289437
+ if ("update_todo_list" in aiTools) {
289438
+ systemMessageParts.push(`<agent-progress>
289439
+ Use the agent progress checklist only for genuinely multi-step execution work.
289440
+ - Create it early, after you understand the work; do not create one for trivial questions.
289441
+ - Update it immediately after real progress, preserving stable item IDs and list order.
289442
+ - Keep at most one item in progress and never mark required unfinished work complete.
289443
+ - Completed lists remain visible; clear only when the work is explicitly replaced or no longer relevant.
289444
+ This checklist is operational progress, not the plan-mode plan or a user-managed task list.
289445
+ </agent-progress>`);
289446
+ }
289043
289447
  if (chatMode === "discussion") {
289044
289448
  systemMessageParts.push(`<discussion-mode>
289045
289449
  CRITICAL: You are in DISCUSSION MODE (read-only). This is a hard constraint you MUST follow.
@@ -289324,7 +289728,11 @@ ${combinedSystemMessage}` : combinedSystemMessage;
289324
289728
  },
289325
289729
  // Thread the working-memory bash FS so applet FS tools (mount/write/save)
289326
289730
  // can read/write /persist. Same instance the `working_memory` tool uses.
289327
- bash
289731
+ bash,
289732
+ todoStore: context.todoStore,
289733
+ todoSession,
289734
+ chatMode,
289735
+ ephemeral: context.metadata?.temporary === true
289328
289736
  };
289329
289737
  const toolWithExecute = tool2;
289330
289738
  if (toolWithExecute.execute) {
@@ -289359,6 +289767,7 @@ ${combinedSystemMessage}` : combinedSystemMessage;
289359
289767
  if (conversationId && context.chatStore) {
289360
289768
  try {
289361
289769
  persistedFiles = await context.chatStore.loadWorkingMemory(conversationId);
289770
+ delete persistedFiles["/persist/todos.json"];
289362
289771
  if (Object.keys(persistedFiles).length > 0) {
289363
289772
  orchestrationLogger.info(
289364
289773
  "Loaded persisted working memory",
@@ -289374,13 +289783,26 @@ ${combinedSystemMessage}` : combinedSystemMessage;
289374
289783
  });
289375
289784
  }
289376
289785
  }
289786
+ if (agentProgressEnabled && conversationId && context.todoStore && context.metadata?.temporary !== true) {
289787
+ try {
289788
+ todoSession.snapshot = await context.todoStore.loadTodoSnapshot({
289789
+ conversationId,
289790
+ ownerId: context.session.user?.id
289791
+ });
289792
+ } catch (err) {
289793
+ orchestrationLogger.warn("Failed to load agent todo list", {
289794
+ error: err instanceof Error ? err.message : String(err)
289795
+ });
289796
+ }
289797
+ }
289377
289798
  const { Bash: JustBash } = await Promise.resolve().then(() => (init_bundle(), bundle_exports));
289378
289799
  const bash = new JustBash({
289379
289800
  files: {
289380
289801
  "/research/.gitkeep": "",
289381
289802
  "/data/.gitkeep": "",
289382
289803
  "/persist/.gitkeep": "",
289383
- ...persistedFiles
289804
+ ...persistedFiles,
289805
+ ...agentProgressEnabled && todoSession.snapshot.revision > 0 ? { "/persist/todos.json": JSON.stringify(todoSession.snapshot, null, 2) } : {}
289384
289806
  },
289385
289807
  javascript: true
289386
289808
  });
@@ -290313,7 +290735,7 @@ Re-read them (working_memory on web, bash on the CLI \u2014 grep/sed projection,
290313
290735
  registeredParentToolSchemas: Array.from(
290314
290736
  context.registeredParentToolSchemas.values()
290315
290737
  ),
290316
- isTemporary: false
290738
+ isTemporary: context.metadata?.temporary === true
290317
290739
  } : void 0;
290318
290740
  await drainPendingPause({
290319
290741
  pending: pendingPauseRef.current,
@@ -290365,7 +290787,7 @@ Re-read them (working_memory on web, bash on the CLI \u2014 grep/sed projection,
290365
290787
  try {
290366
290788
  const allPaths = bash.fs.getAllPaths();
290367
290789
  const persistPaths = allPaths.filter(
290368
- (p29) => p29.startsWith("/persist/") && !p29.endsWith(".gitkeep")
290790
+ (p29) => p29.startsWith("/persist/") && !p29.endsWith(".gitkeep") && p29 !== "/persist/todos.json"
290369
290791
  );
290370
290792
  const persistData = {};
290371
290793
  for (const p29 of persistPaths) {
@@ -290404,8 +290826,7 @@ Re-read them (working_memory on web, bash on the CLI \u2014 grep/sed projection,
290404
290826
  });
290405
290827
  }
290406
290828
  };
290407
- savePersistFiles().catch(() => {
290408
- });
290829
+ await savePersistFiles();
290409
290830
  }
290410
290831
  multiplexer.close();
290411
290832
  resolveStreamComplete();
@@ -290445,7 +290866,7 @@ Re-read them (working_memory on web, bash on the CLI \u2014 grep/sed projection,
290445
290866
  try {
290446
290867
  const allPaths = bash.fs.getAllPaths();
290447
290868
  const persistPaths = allPaths.filter(
290448
- (p29) => p29.startsWith("/persist/") && !p29.endsWith(".gitkeep")
290869
+ (p29) => p29.startsWith("/persist/") && !p29.endsWith(".gitkeep") && p29 !== "/persist/todos.json"
290449
290870
  );
290450
290871
  const persistData = {};
290451
290872
  for (const p29 of persistPaths) {
@@ -290517,6 +290938,7 @@ function createOrchestratorRequest(messages4, context, config3, persona, apiKey,
290517
290938
  creditAuthorization: context.creditAuthorization,
290518
290939
  chatStore: context.chatStore,
290519
290940
  // Persistence port (Drizzle for Next.js, SQLite for CLI)
290941
+ todoStore: context.todoStore,
290520
290942
  metadata: context.metadata,
290521
290943
  allowedWorkspaceIds: context.allowedWorkspaceIds,
290522
290944
  registeredParentToolSchemas: context.registeredParentToolSchemas,
@@ -290610,6 +291032,8 @@ var init_orchestrator = __esm({
290610
291032
  init_spawn_subagent();
290611
291033
  init_await_subagents();
290612
291034
  init_list_subagents();
291035
+ init_todo_list();
291036
+ init_types2();
290613
291037
  init_spawned_task_store();
290614
291038
  init_redis();
290615
291039
  init_executor();
@@ -291853,7 +292277,7 @@ var ReadBuffer;
291853
292277
  var init_stdio = __esm({
291854
292278
  "node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/stdio.js"() {
291855
292279
  "use strict";
291856
- init_types6();
292280
+ init_types7();
291857
292281
  ReadBuffer = class {
291858
292282
  append(chunk2) {
291859
292283
  this._buffer = this._buffer ? Buffer.concat([this._buffer, chunk2]) : chunk2;
@@ -293349,8 +293773,10 @@ async function runCliTurn(options2) {
293349
293773
  model = config2.ai.defaultModel,
293350
293774
  reasoningEffort,
293351
293775
  conversationId,
293776
+ ephemeral,
293352
293777
  session,
293353
293778
  chatStore,
293779
+ todoStore,
293354
293780
  onText,
293355
293781
  onEvent,
293356
293782
  scriptedLlm,
@@ -293428,9 +293854,15 @@ async function runCliTurn(options2) {
293428
293854
  emitter: createEventEmitter({ messageId: crypto.randomUUID() }),
293429
293855
  sessionPathway: noopSessionPathway,
293430
293856
  chatStore,
293857
+ todoStore,
293431
293858
  extensions: extensions2?.runtime,
293432
293859
  abortSignal,
293433
- metadata: { conversationId }
293860
+ metadata: {
293861
+ conversationId,
293862
+ temporary: ephemeral === true,
293863
+ // The CLI is a local, user-controlled surface without web user grants.
293864
+ agentProgressEnabled: true
293865
+ }
293434
293866
  },
293435
293867
  {
293436
293868
  model,
@@ -307842,6 +308274,87 @@ var init_tui_markdown = __esm({
307842
308274
  }
307843
308275
  });
307844
308276
 
308277
+ // src/cli/todo-checklist.ts
308278
+ function reduceTodoListUpdate(current, payload2) {
308279
+ const parsed = todoSnapshotSchema.safeParse(payload2);
308280
+ if (!parsed.success || parsed.data.revision <= current.revision) return current;
308281
+ return parsed.data;
308282
+ }
308283
+ function sanitizeTerminalText(value) {
308284
+ return value.replace(OSC, "").replace(STRING_CONTROL, "").replace(CSI, "").replace(TWO_BYTE_ESCAPE, "").replace(ESCAPE, "").replace(/[\r\n\t]+/g, " ").replace(C0_C1, "").replace(BIDI_CONTROLS, "").replace(/ +/g, " ").trim();
308285
+ }
308286
+ function codePointWidth(character) {
308287
+ const code = character.codePointAt(0) ?? 0;
308288
+ if (new RegExp("\\p{Mark}", "u").test(character) || code === 8205 || code >= 65024 && code <= 65039) {
308289
+ return 0;
308290
+ }
308291
+ if (code >= 4352 && (code <= 4447 || code === 9001 || code === 9002 || code >= 11904 && code <= 42191 && code !== 12351 || code >= 44032 && code <= 55203 || code >= 63744 && code <= 64255 || code >= 65040 && code <= 65049 || code >= 65072 && code <= 65135 || code >= 65280 && code <= 65376 || code >= 65504 && code <= 65510 || code >= 127744 && code <= 129791 || code >= 131072 && code <= 262141)) {
308292
+ return 2;
308293
+ }
308294
+ return 1;
308295
+ }
308296
+ function terminalCellWidth(value) {
308297
+ let width = 0;
308298
+ for (const character of value) width += codePointWidth(character);
308299
+ return width;
308300
+ }
308301
+ function truncateToCells(value, maxWidth) {
308302
+ if (maxWidth <= 0) return "";
308303
+ if (terminalCellWidth(value) <= maxWidth) return value;
308304
+ if (maxWidth === 1) return "\u2026";
308305
+ let out = "";
308306
+ let width = 0;
308307
+ for (const character of value) {
308308
+ const next = codePointWidth(character);
308309
+ if (width + next > maxWidth - 1) break;
308310
+ out += character;
308311
+ width += next;
308312
+ }
308313
+ return `${out}\u2026`;
308314
+ }
308315
+ function renderTodoChecklist(list3, width, options2 = {}) {
308316
+ const safeWidth = Math.max(1, Math.floor(width));
308317
+ if (list3.items.length === 0) return [];
308318
+ const completed = list3.items.filter((todo) => todo.status === "completed").length;
308319
+ const persistence = options2.persistent === false ? " (temporary)" : "";
308320
+ const lines = [
308321
+ truncateToCells(
308322
+ `Agent progress${persistence} ${completed}/${list3.items.length} complete`,
308323
+ safeWidth
308324
+ )
308325
+ ];
308326
+ const maxItems = options2.expanded ? list3.items.length : Math.max(1, options2.maxCompactItems ?? 4);
308327
+ const visible = list3.items.slice(0, maxItems);
308328
+ for (const todo of visible) {
308329
+ const marker18 = todo.status === "completed" ? "[x]" : todo.status === "in_progress" ? "[>]" : "[ ]";
308330
+ lines.push(truncateToCells(` ${marker18} ${sanitizeTerminalText(todo.content)}`, safeWidth));
308331
+ }
308332
+ const hidden = list3.items.length - visible.length;
308333
+ if (hidden > 0) lines.push(truncateToCells(` ... +${hidden} more`, safeWidth));
308334
+ return lines;
308335
+ }
308336
+ var EMPTY_TODO_LIST, OSC, STRING_CONTROL, CSI, TWO_BYTE_ESCAPE, ESCAPE, C0_C1, BIDI_CONTROLS;
308337
+ var init_todo_checklist = __esm({
308338
+ "src/cli/todo-checklist.ts"() {
308339
+ "use strict";
308340
+ init_types2();
308341
+ EMPTY_TODO_LIST = Object.freeze({
308342
+ schemaVersion: 1,
308343
+ revision: 0,
308344
+ items: [],
308345
+ updatedAt: null,
308346
+ recentCommands: []
308347
+ });
308348
+ OSC = /(?:\u001b\]|\u009d)[\s\S]*?(?:\u0007|\u001b\\|\u009c|$)/g;
308349
+ STRING_CONTROL = /(?:\u001b[P^_X]|[\u0090\u0098\u009e\u009f])[\s\S]*?(?:\u001b\\|\u009c|$)/g;
308350
+ CSI = /(?:\u001b\[|\u009b)[0-?]*[ -/]*[@-~]/g;
308351
+ TWO_BYTE_ESCAPE = /\u001b[0-?]/g;
308352
+ ESCAPE = /\u001b[ -/]*[@-~]/g;
308353
+ C0_C1 = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/g;
308354
+ BIDI_CONTROLS = /[\u061c\u200e\u200f\u202a-\u202e\u2066-\u2069]/g;
308355
+ }
308356
+ });
308357
+
307845
308358
  // src/cli/warp-agent.ts
307846
308359
  import { openSync, writeSync } from "node:fs";
307847
308360
  import { basename as basename9 } from "node:path";
@@ -308001,11 +308514,15 @@ function renderItem(item, width, toolsExpanded) {
308001
308514
  case "tool":
308002
308515
  return renderTool(item, width, toolsExpanded);
308003
308516
  case "system":
308004
- return [...wrap(item.text, width).map((l14) => fg3(item.tone === "error" ? "#f87171" : "#9ca3af", l14)), ""];
308517
+ return [
308518
+ ...wrap(item.text, width).map((l14) => fg3(item.tone === "error" ? "#f87171" : "#9ca3af", l14)),
308519
+ ""
308520
+ ];
308005
308521
  }
308006
308522
  }
308007
308523
  async function launchTui(options2) {
308008
308524
  const { runTurn, actions, initialHistory, version: version4, autoSubmit, extensions: extensions2, bannerOverride } = options2;
308525
+ const autoSubmitQueue = autoSubmit ? Array.isArray(autoSubmit) ? [...autoSubmit] : [autoSubmit] : [];
308009
308526
  const terminal = options2.terminal ?? new ProcessTerminal();
308010
308527
  const exit = options2.onExit ?? ((code) => process.exit(code));
308011
308528
  const ui2 = new TUI(terminal);
@@ -308013,6 +308530,25 @@ async function launchTui(options2) {
308013
308530
  editor.focused = true;
308014
308531
  const bangTools = createLocalTools();
308015
308532
  let items = [...initialHistory];
308533
+ let todoList = reduceTodoListUpdate(EMPTY_TODO_LIST, options2.initialTodos);
308534
+ let todoPersistent = true;
308535
+ let todoTransient = null;
308536
+ let endedTransientTurns = [];
308537
+ let todoExpiryTimer = null;
308538
+ let todoExpiredView = false;
308539
+ function scheduleTodoExpiry() {
308540
+ if (todoExpiryTimer) clearTimeout(todoExpiryTimer);
308541
+ todoExpiryTimer = null;
308542
+ if (!todoPersistent || !todoList.updatedAt || todoList.items.length === 0) return;
308543
+ const remaining = Date.parse(todoList.updatedAt) + TODO_TTL_MS - Date.now();
308544
+ const expire = () => {
308545
+ todoExpiredView = true;
308546
+ todoExpiryTimer = null;
308547
+ ui2.requestRender();
308548
+ };
308549
+ if (remaining <= 0) expire();
308550
+ else todoExpiryTimer = setTimeout(expire, Math.min(remaining, 2147483647));
308551
+ }
308016
308552
  let compactedPrefix = [];
308017
308553
  let busy = false;
308018
308554
  let status = "";
@@ -308156,9 +308692,27 @@ async function launchTui(options2) {
308156
308692
  case "new":
308157
308693
  actions.newSession();
308158
308694
  items = [];
308695
+ todoList = EMPTY_TODO_LIST;
308696
+ todoPersistent = true;
308697
+ todoTransient = null;
308698
+ endedTransientTurns = [];
308699
+ todoExpiredView = false;
308700
+ scheduleTodoExpiry();
308159
308701
  compactedPrefix = [];
308160
308702
  sys("Started a new session.");
308161
308703
  return;
308704
+ case "todos": {
308705
+ const lines = renderTodoChecklist(
308706
+ todoExpiredView ? EMPTY_TODO_LIST : todoList,
308707
+ Math.max(1, terminal.columns),
308708
+ {
308709
+ expanded: true,
308710
+ persistent: todoPersistent
308711
+ }
308712
+ );
308713
+ sys(lines.length > 0 ? lines.join("\n") : "No todos yet for this session.");
308714
+ return;
308715
+ }
308162
308716
  case "compact":
308163
308717
  case "summarize": {
308164
308718
  if (busy) {
@@ -308285,7 +308839,10 @@ Edit it, then /verify-extension ${arg.trim()} to check it loads, /trust (project
308285
308839
  pendingImages.push(await compressToDataUrl2(bytes, "image/png"));
308286
308840
  sys(`\u{1F4CE} image attached from clipboard \u2014 send a message to include it`, "info");
308287
308841
  } catch (err) {
308288
- sys(`Could not attach clipboard image: ${err instanceof Error ? err.message : String(err)}`, "error");
308842
+ sys(
308843
+ `Could not attach clipboard image: ${err instanceof Error ? err.message : String(err)}`,
308844
+ "error"
308845
+ );
308289
308846
  }
308290
308847
  }
308291
308848
  async function submit(raw) {
@@ -308301,7 +308858,10 @@ Edit it, then /verify-extension ${arg.trim()} to check it loads, /trust (project
308301
308858
  pendingImages.push(await readImageFileAsDataUrl2(p29));
308302
308859
  attached++;
308303
308860
  } catch (err) {
308304
- sys(`Could not attach ${p29}: ${err instanceof Error ? err.message : String(err)}`, "error");
308861
+ sys(
308862
+ `Could not attach ${p29}: ${err instanceof Error ? err.message : String(err)}`,
308863
+ "error"
308864
+ );
308305
308865
  }
308306
308866
  }
308307
308867
  text3 = cleanedText;
@@ -308352,10 +308912,7 @@ Edit it, then /verify-extension ${arg.trim()} to check it loads, /trust (project
308352
308912
  busy = false;
308353
308913
  ui2.requestRender();
308354
308914
  if (autoSubmit) {
308355
- setTimeout(() => {
308356
- teardown();
308357
- exit(0);
308358
- }, 25);
308915
+ continueAutoSubmitOrExit(25);
308359
308916
  }
308360
308917
  }
308361
308918
  return;
@@ -308422,6 +308979,40 @@ Edit it, then /verify-extension ${arg.trim()} to check it loads, /trust (project
308422
308979
  push({ kind: "system", text: plan, tone: "info" });
308423
308980
  ui2.requestRender();
308424
308981
  }
308982
+ } else if (e15.type === "todo-list-updated") {
308983
+ const data2 = e15.data;
308984
+ if (data2?.persistent === false) {
308985
+ if (!data2.turnId || endedTransientTurns.includes(data2.turnId)) return;
308986
+ if (!todoTransient || todoTransient.turnId !== data2.turnId) {
308987
+ if (todoTransient) {
308988
+ endedTransientTurns = [...endedTransientTurns, todoTransient.turnId].slice(-32);
308989
+ }
308990
+ todoTransient = {
308991
+ turnId: data2.turnId,
308992
+ previous: todoTransient?.previous ?? todoList
308993
+ };
308994
+ todoList = reduceTodoListUpdate(EMPTY_TODO_LIST, data2.snapshot);
308995
+ } else {
308996
+ todoList = reduceTodoListUpdate(todoList, data2.snapshot);
308997
+ }
308998
+ todoPersistent = false;
308999
+ todoExpiredView = false;
309000
+ if (todoExpiryTimer) clearTimeout(todoExpiryTimer);
309001
+ todoExpiryTimer = null;
309002
+ gotOutput = true;
309003
+ ui2.requestRender();
309004
+ return;
309005
+ }
309006
+ todoTransient = null;
309007
+ const next = reduceTodoListUpdate(todoList, data2?.snapshot);
309008
+ if (next !== todoList) {
309009
+ gotOutput = true;
309010
+ todoList = next;
309011
+ todoPersistent = true;
309012
+ todoExpiredView = false;
309013
+ scheduleTodoExpiry();
309014
+ ui2.requestRender();
309015
+ }
308425
309016
  } else if (e15.type === "compaction") {
308426
309017
  const data2 = e15.data;
308427
309018
  finalize();
@@ -308449,6 +309040,14 @@ ${data2.summary}` : `${head}.`;
308449
309040
  sys(`Error: ${err instanceof Error ? err.message : String(err)}`, "error");
308450
309041
  } finally {
308451
309042
  finalize();
309043
+ if (todoTransient) {
309044
+ endedTransientTurns = [...endedTransientTurns, todoTransient.turnId].slice(-32);
309045
+ todoList = todoTransient.previous;
309046
+ todoPersistent = true;
309047
+ todoExpiredView = false;
309048
+ todoTransient = null;
309049
+ scheduleTodoExpiry();
309050
+ }
308452
309051
  if (!gotOutput) {
308453
309052
  push({ kind: "system", text: "(no response \u2014 see ~/.usable/usable.log)", tone: "error" });
308454
309053
  }
@@ -308458,16 +309057,23 @@ ${data2.summary}` : `${head}.`;
308458
309057
  notifyWarp("stop");
308459
309058
  ui2.requestRender();
308460
309059
  if (autoSubmit) {
308461
- setTimeout(() => {
308462
- teardown();
308463
- exit(0);
308464
- }, 100);
309060
+ continueAutoSubmitOrExit(100);
308465
309061
  }
308466
309062
  }
308467
309063
  }
308468
309064
  editor.onSubmit = (text3) => {
308469
309065
  void submit(text3);
308470
309066
  };
309067
+ function continueAutoSubmitOrExit(delay2) {
309068
+ const next = autoSubmitQueue.shift();
309069
+ setTimeout(() => {
309070
+ if (next) void submit(next);
309071
+ else {
309072
+ teardown();
309073
+ exit(0);
309074
+ }
309075
+ }, delay2);
309076
+ }
308471
309077
  const reasoningCompletions = REASONING_OPTIONS.map((o21) => ({
308472
309078
  value: o21.value,
308473
309079
  label: o21.label,
@@ -308497,6 +309103,7 @@ ${data2.summary}` : `${head}.`;
308497
309103
  getArgumentCompletions: (prefix) => reasoningCompletions.filter((o21) => o21.value.startsWith(prefix.toLowerCase()))
308498
309104
  },
308499
309105
  { name: "sessions", description: "list sessions for this project" },
309106
+ { name: "todos", description: "show the complete agent progress checklist" },
308500
309107
  { name: "compact", description: "summarize the conversation into a checkpoint" },
308501
309108
  { name: "summarize", description: "alias of /compact" },
308502
309109
  { name: "provider", description: "switch model provider" },
@@ -308598,11 +309205,16 @@ ${data2.summary}` : `${head}.`;
308598
309205
  body.push("");
308599
309206
  }
308600
309207
  for (const it7 of items) body.push(...renderItem(it7, width, toolsExpanded));
309208
+ const todoLines = renderTodoChecklist(todoExpiredView ? EMPTY_TODO_LIST : todoList, width, {
309209
+ persistent: todoPersistent
309210
+ });
309211
+ if (todoLines.length > 0) body.push(...todoLines, "");
308601
309212
  const statusLine = picker ? "" : dim3(short(statusText, width));
308602
309213
  return [...body, statusLine, ...editorLines];
308603
309214
  }
308604
309215
  };
308605
309216
  function teardown() {
309217
+ if (todoExpiryTimer) clearTimeout(todoExpiryTimer);
308606
309218
  try {
308607
309219
  ui2.stop();
308608
309220
  } catch {
@@ -308635,7 +309247,9 @@ ${data2.summary}` : `${head}.`;
308635
309247
  terminal.setTitle(appTitle());
308636
309248
  notifyWarp("session_start");
308637
309249
  ui2.requestRender();
308638
- if (autoSubmit) setTimeout(() => void submit(autoSubmit), 10);
309250
+ scheduleTodoExpiry();
309251
+ const firstAutoSubmit = autoSubmitQueue.shift();
309252
+ if (firstAutoSubmit) setTimeout(() => void submit(firstAutoSubmit), 10);
308639
309253
  return new Promise(() => {
308640
309254
  });
308641
309255
  }
@@ -308644,9 +309258,11 @@ var init_tui2 = __esm({
308644
309258
  "src/cli/tui.ts"() {
308645
309259
  "use strict";
308646
309260
  init_dist8();
309261
+ init_types2();
308647
309262
  init_tui_select();
308648
309263
  init_tools();
308649
309264
  init_tui_markdown();
309265
+ init_todo_checklist();
308650
309266
  init_warp_agent();
308651
309267
  BANNER_LINES = [
308652
309268
  " _ _ ___ __ _| |__ | | ___",
@@ -308661,6 +309277,7 @@ var init_tui2 = __esm({
308661
309277
  "/model [<id>] pick from a list, or switch directly by id",
308662
309278
  "/reasoning [lvl] set reasoning effort (low|medium|high)",
308663
309279
  "/sessions list sessions for this project",
309280
+ "/todos show the complete agent progress checklist",
308664
309281
  "/new-extension <n> scaffold a starter extension in .usable/extensions/",
308665
309282
  "/verify-extension <n> check an extension loads (no tsc needed)",
308666
309283
  "/compact, /summarize summarize the conversation into a checkpoint (frees context)",
@@ -308793,6 +309410,7 @@ function loadCliConfig(env2 = process.env) {
308793
309410
  }
308794
309411
 
308795
309412
  // src/adapters/cli/store.ts
309413
+ init_types2();
308796
309414
  import { existsSync as existsSync2, mkdirSync } from "node:fs";
308797
309415
  import { createRequire } from "node:module";
308798
309416
  import { dirname, join as join2 } from "node:path";
@@ -308811,7 +309429,7 @@ function allRows(db3, sql2, ...params) {
308811
309429
  return db3.prepare(sql2).all(...params);
308812
309430
  }
308813
309431
  function runSql(db3, sql2, params = []) {
308814
- db3.prepare(sql2).run(...params);
309432
+ return Number(db3.prepare(sql2).run(...params).changes);
308815
309433
  }
308816
309434
  var SCHEMA = `
308817
309435
  CREATE TABLE IF NOT EXISTS project (
@@ -308846,7 +309464,23 @@ CREATE TABLE IF NOT EXISTS working_memory (
308846
309464
  files TEXT NOT NULL,
308847
309465
  updated_at INTEGER NOT NULL
308848
309466
  );
309467
+ CREATE TABLE IF NOT EXISTS todo_snapshot (
309468
+ conversation_id TEXT NOT NULL,
309469
+ owner_id TEXT NOT NULL,
309470
+ snapshot TEXT NOT NULL,
309471
+ updated_at INTEGER NOT NULL,
309472
+ PRIMARY KEY (conversation_id, owner_id)
309473
+ );
308849
309474
  `;
309475
+ function parseTodoSnapshot2(value) {
309476
+ try {
309477
+ const candidate = typeof value === "string" ? JSON.parse(value) : value;
309478
+ const parsed = todoSnapshotSchema.safeParse(candidate);
309479
+ return parsed.success ? parsed.data : null;
309480
+ } catch {
309481
+ return null;
309482
+ }
309483
+ }
308850
309484
  function now() {
308851
309485
  return Date.now();
308852
309486
  }
@@ -308861,6 +309495,7 @@ var CliStore = class {
308861
309495
  this.db.exec("PRAGMA foreign_keys = ON;");
308862
309496
  this.db.exec(SCHEMA);
308863
309497
  this.chatStore = this.buildChatStore();
309498
+ this.todoStore = this.buildTodoStore();
308864
309499
  }
308865
309500
  /** Resolve (or create) the project row for a git root, returning its id. */
308866
309501
  resolveProject(gitRoot2, name18) {
@@ -308932,7 +309567,15 @@ var CliStore = class {
308932
309567
  this.db,
308933
309568
  `INSERT INTO message (id, session_id, parent_uuid, role, created_at, seq, data)
308934
309569
  VALUES (?, ?, ?, ?, ?, ?, ?)`,
308935
- [id, input.sessionId, input.parentUuid ?? null, input.role, ts3, seq, JSON.stringify(input.message)]
309570
+ [
309571
+ id,
309572
+ input.sessionId,
309573
+ input.parentUuid ?? null,
309574
+ input.role,
309575
+ ts3,
309576
+ seq,
309577
+ JSON.stringify(input.message)
309578
+ ]
308936
309579
  );
308937
309580
  runSql(this.db, "UPDATE session SET updated_at = ? WHERE id = ?", [ts3, input.sessionId]);
308938
309581
  return id;
@@ -308998,6 +309641,100 @@ var CliStore = class {
308998
309641
  }
308999
309642
  };
309000
309643
  }
309644
+ buildTodoStore() {
309645
+ const db3 = this.db;
309646
+ const load = (input) => {
309647
+ const ownerId = input.ownerId ?? "";
309648
+ const row = getRow(
309649
+ db3,
309650
+ `SELECT snapshot, updated_at FROM todo_snapshot
309651
+ WHERE conversation_id = ? AND owner_id = ? LIMIT 1`,
309652
+ input.conversationId,
309653
+ ownerId
309654
+ );
309655
+ if (!row) return createEmptyTodoSnapshot();
309656
+ const currentTime = (input.now ?? /* @__PURE__ */ new Date()).getTime();
309657
+ const parsed = parseTodoSnapshot2(row.snapshot);
309658
+ if (!parsed) {
309659
+ const recovered = {
309660
+ ...createEmptyTodoSnapshot(),
309661
+ revision: 1,
309662
+ updatedAt: new Date(currentTime).toISOString()
309663
+ };
309664
+ const changed = runSql(
309665
+ db3,
309666
+ "UPDATE todo_snapshot SET snapshot = ?, updated_at = ? WHERE conversation_id = ? AND owner_id = ? AND updated_at = ?",
309667
+ [JSON.stringify(recovered), currentTime, input.conversationId, ownerId, row.updated_at]
309668
+ );
309669
+ return changed > 0 ? recovered : load(input);
309670
+ }
309671
+ if (isTodoSnapshotExpired(parsed, new Date(currentTime))) {
309672
+ const expired = createExpiredTodoSnapshot(parsed, new Date(currentTime));
309673
+ const changed = runSql(
309674
+ db3,
309675
+ "UPDATE todo_snapshot SET snapshot = ?, updated_at = ? WHERE conversation_id = ? AND owner_id = ? AND updated_at = ?",
309676
+ [JSON.stringify(expired), currentTime, input.conversationId, ownerId, row.updated_at]
309677
+ );
309678
+ return changed > 0 ? expired : load(input);
309679
+ }
309680
+ return parsed;
309681
+ };
309682
+ return {
309683
+ async loadTodoSnapshot(input) {
309684
+ return load(input);
309685
+ },
309686
+ async compareAndSwapTodoSnapshot(input) {
309687
+ const candidate = parseTodoSnapshot(input.candidate);
309688
+ if (candidate.revision !== input.expectedRevision + 1) {
309689
+ throw new Error("Todo candidate revision must advance exactly once");
309690
+ }
309691
+ const candidateCommand = candidate.recentCommands.find(
309692
+ (entry) => entry.id === input.commandId
309693
+ );
309694
+ if (!candidateCommand || candidateCommand.fingerprint !== input.fingerprint || candidateCommand.revision !== candidate.revision) {
309695
+ throw new Error("Todo candidate must contain its command fingerprint");
309696
+ }
309697
+ db3.exec("BEGIN IMMEDIATE");
309698
+ try {
309699
+ const current = load(input);
309700
+ const priorCommand = current.recentCommands.find((entry) => entry.id === input.commandId);
309701
+ if (priorCommand) {
309702
+ db3.exec("COMMIT");
309703
+ return {
309704
+ status: priorCommand.fingerprint === input.fingerprint ? "idempotent" : "conflict",
309705
+ snapshot: current
309706
+ };
309707
+ }
309708
+ if (current.revision !== input.expectedRevision) {
309709
+ db3.exec("COMMIT");
309710
+ return { status: "conflict", snapshot: current };
309711
+ }
309712
+ runSql(
309713
+ db3,
309714
+ `INSERT INTO todo_snapshot (conversation_id, owner_id, snapshot, updated_at)
309715
+ VALUES (?, ?, ?, ?)
309716
+ ON CONFLICT(conversation_id, owner_id) DO UPDATE SET
309717
+ snapshot = excluded.snapshot,
309718
+ updated_at = excluded.updated_at`,
309719
+ [
309720
+ input.conversationId,
309721
+ input.ownerId ?? "",
309722
+ JSON.stringify(candidate),
309723
+ Date.parse(candidate.updatedAt)
309724
+ ]
309725
+ );
309726
+ db3.exec("COMMIT");
309727
+ return { status: "committed", snapshot: candidate };
309728
+ } catch (error41) {
309729
+ try {
309730
+ db3.exec("ROLLBACK");
309731
+ } catch {
309732
+ }
309733
+ throw error41;
309734
+ }
309735
+ }
309736
+ };
309737
+ }
309001
309738
  };
309002
309739
 
309003
309740
  // src/cli/index.ts
@@ -309297,7 +310034,7 @@ init_tui_select();
309297
310034
  init_model_registry();
309298
310035
 
309299
310036
  // package.json
309300
- var version2 = "1.178.3";
310037
+ var version2 = "1.180.0";
309301
310038
 
309302
310039
  // src/adapters/cli/model-catalog.ts
309303
310040
  init_codex_auth();
@@ -310051,9 +310788,14 @@ async function resolveProvider(cfg) {
310051
310788
  const { getCodexAuth: getCodexAuth2 } = await Promise.resolve().then(() => (init_codex_auth(), codex_auth_exports));
310052
310789
  const auth2 = await getCodexAuth2(cfg);
310053
310790
  if (!auth2) {
310054
- throw new Error("Codex provider selected but not signed in \u2014 run `usable-chat login` and choose Codex.");
310791
+ throw new Error(
310792
+ "Codex provider selected but not signed in \u2014 run `usable-chat login` and choose Codex."
310793
+ );
310055
310794
  }
310056
- return { name: "codex", codexAuth: { accessToken: auth2.accessToken, accountId: auth2.accountId } };
310795
+ return {
310796
+ name: "codex",
310797
+ codexAuth: { accessToken: auth2.accessToken, accountId: auth2.accountId }
310798
+ };
310057
310799
  }
310058
310800
  return { name: "openrouter", zdr: cfg.ai.zdr };
310059
310801
  }
@@ -310130,7 +310872,11 @@ async function compactSession(history, opts) {
310130
310872
  }
310131
310873
  const checkpoint = buildSummaryCheckpoint(summary, history.length, extractFileOps(history));
310132
310874
  if (!opts.noSave) opts.store.replaceSessionMessages(opts.sessionId, [checkpoint]);
310133
- return { ok: true, note: `\u{1F9F9} Compacted ${history.length} messages into a checkpoint.`, checkpoint };
310875
+ return {
310876
+ ok: true,
310877
+ note: `\u{1F9F9} Compacted ${history.length} messages into a checkpoint.`,
310878
+ checkpoint
310879
+ };
310134
310880
  }
310135
310881
  function printUsage() {
310136
310882
  process.stderr.write(
@@ -310256,13 +311002,17 @@ function silenceConsole(logFile) {
310256
311002
  console.error = append;
310257
311003
  }
310258
311004
  function readScriptedLlm() {
310259
- if (process.env.E2E_ALLOW_TEST_SEAMS !== "true" || !process.env.USABLE_CLI_SCRIPT) return void 0;
311005
+ if (process.env.E2E_ALLOW_TEST_SEAMS !== "true" || !process.env.USABLE_CLI_SCRIPT)
311006
+ return void 0;
310260
311007
  try {
310261
311008
  return JSON.parse(process.env.USABLE_CLI_SCRIPT);
310262
311009
  } catch {
310263
311010
  return void 0;
310264
311011
  }
310265
311012
  }
311013
+ function scriptedSession() {
311014
+ return { user: { id: "cli-e2e-user", accessToken: "" } };
311015
+ }
310266
311016
  async function resolveImageArgs(specs) {
310267
311017
  const images = [];
310268
311018
  const errors = [];
@@ -310281,7 +311031,8 @@ async function resolveImageArgs(specs) {
310281
311031
  async function runTui(cfg, store, projectId, values2) {
310282
311032
  applyEnv(cfg);
310283
311033
  silenceConsole(join21(cfg.paths.dataDir, "usable.log"));
310284
- let session = await loadSession(cfg);
311034
+ const scriptedLlm = readScriptedLlm();
311035
+ let session = scriptedLlm ? scriptedSession() : await loadSession(cfg);
310285
311036
  let model = values2.model ?? cfg.ai.defaultModel;
310286
311037
  let reasoning = cfg.ai.reasoningEffort;
310287
311038
  let provider = cfg.ai.provider;
@@ -310306,7 +311057,6 @@ async function runTui(cfg, store, projectId, values2) {
310306
311057
  } else {
310307
311058
  sessionId2 = store.createSession({ projectId, directory: process.cwd(), model });
310308
311059
  }
310309
- const scriptedLlm = readScriptedLlm();
310310
311060
  const { runCliTurn: runCliTurn2 } = await Promise.resolve().then(() => (init_run(), run_exports));
310311
311061
  const { launchTui: launchTui2 } = await Promise.resolve().then(() => (init_tui2(), tui_exports));
310312
311062
  const extLogFile = join21(cfg.paths.dataDir, "usable.log");
@@ -310327,7 +311077,7 @@ async function runTui(cfg, store, projectId, values2) {
310327
311077
  const ext2 = await setupExtensions(process.cwd(), extUi, extLog);
310328
311078
  ext2.current.fireSessionStart({ sessionId: sessionId2 });
310329
311079
  if (resumedExisting) ext2.current.fireSessionSwitch({ sessionId: sessionId2 });
310330
- let providerRoute = await resolveProvider(cfg);
311080
+ let providerRoute = scriptedLlm ? void 0 : await resolveProvider(cfg);
310331
311081
  const runTurn = async (prompt, hist, { onText, onEvent, abortSignal, images }) => {
310332
311082
  const result = await runCliTurn2({
310333
311083
  prompt,
@@ -310339,8 +311089,10 @@ async function runTui(cfg, store, projectId, values2) {
310339
311089
  skills: { enabled: cfg.ai.skills !== false, workspaceId: cfg.mcp.defaultWorkspaceId },
310340
311090
  usableApiHost: cfg.ai.usableApiHost,
310341
311091
  conversationId: sessionId2,
311092
+ ephemeral: values2["no-save"] === true,
310342
311093
  session,
310343
311094
  chatStore: store.chatStore,
311095
+ todoStore: store.todoStore,
310344
311096
  scriptedLlm,
310345
311097
  enableMcp: true,
310346
311098
  extensions: ext2.current,
@@ -310424,6 +311176,10 @@ async function runTui(cfg, store, projectId, values2) {
310424
311176
  kind: m33.role === "assistant" ? "assistant" : "user",
310425
311177
  text: typeof m33.content === "string" ? m33.content : JSON.stringify(m33.content)
310426
311178
  }));
311179
+ const initialTodos = values2["no-save"] ? void 0 : await store.todoStore.loadTodoSnapshot({
311180
+ conversationId: sessionId2,
311181
+ ownerId: session.user?.id
311182
+ });
310427
311183
  const updateNotice = getUpdateNotice({
310428
311184
  currentVersion: version2,
310429
311185
  dataDir: cfg.paths.dataDir
@@ -310435,6 +311191,7 @@ async function runTui(cfg, store, projectId, values2) {
310435
311191
  runTurn,
310436
311192
  actions,
310437
311193
  initialHistory,
311194
+ initialTodos,
310438
311195
  initialImages: initialImages.length > 0 ? initialImages : void 0,
310439
311196
  loggedIn: Boolean(session.user?.accessToken),
310440
311197
  user: session.user?.id,
@@ -310526,7 +311283,8 @@ async function main() {
310526
311283
  }
310527
311284
  applyEnv(cfg);
310528
311285
  reserveStdout();
310529
- const session = await loadSession(cfg);
311286
+ const scriptedLlm = readScriptedLlm();
311287
+ const session = scriptedLlm ? scriptedSession() : await loadSession(cfg);
310530
311288
  let sessionId2;
310531
311289
  let history = [];
310532
311290
  if (values2.resume) {
@@ -310550,13 +311308,6 @@ async function main() {
310550
311308
  model: values2.model ?? cfg.ai.defaultModel
310551
311309
  });
310552
311310
  }
310553
- let scriptedLlm;
310554
- if (process.env.E2E_ALLOW_TEST_SEAMS === "true" && process.env.USABLE_CLI_SCRIPT) {
310555
- try {
310556
- scriptedLlm = JSON.parse(process.env.USABLE_CLI_SCRIPT);
310557
- } catch {
310558
- }
310559
- }
310560
311311
  const { runCliTurn: runCliTurn2 } = await Promise.resolve().then(() => (init_run(), run_exports));
310561
311312
  const headlessUi = {
310562
311313
  notify: (m33) => process.stderr.write(`${m33}
@@ -310592,8 +311343,10 @@ async function main() {
310592
311343
  skills: { enabled: cfg.ai.skills !== false, workspaceId: cfg.mcp.defaultWorkspaceId },
310593
311344
  usableApiHost: cfg.ai.usableApiHost,
310594
311345
  conversationId: sessionId2,
311346
+ ephemeral: values2["no-save"] === true,
310595
311347
  session,
310596
311348
  chatStore: store.chatStore,
311349
+ todoStore: store.todoStore,
310597
311350
  scriptedLlm,
310598
311351
  enableMcp: true,
310599
311352
  extensions: ext2?.current,