@skrr-ai/cli 0.1.132 → 0.1.134

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.
@@ -181,7 +181,15 @@ function createNodeAdapter(opts = {}) {
181
181
  // (and per retry: proofs are single-use). No-op for every other token.
182
182
  Object.assign(headers, await (0, dpop_auth_1.dpopHeadersFor)(currentCredential().token, method, finalUrl));
183
183
  // E6: bounded retry on 429 for a GET or an idempotency-keyed write.
184
- const { response, retryAfterMs } = await (0, rate_limit_retry_1.fetchWithRateLimitRetry)(() => fetch(finalUrl, { method, headers, body: finalBody, ...(0, task_runtime_headers_1.taskRuntimeRedirectOptions)(headers) }), { method, headers });
184
+ // Callers that explicitly set a timeout need a real fetch deadline. The
185
+ // Node adapter otherwise silently drops the data-provider request option.
186
+ const timeoutMs = Number(options?.timeout);
187
+ const timeoutSignal = Number.isFinite(timeoutMs) && timeoutMs > 0 ? AbortSignal.timeout(timeoutMs) : undefined;
188
+ const callerSignal = options?.signal;
189
+ const signal = timeoutSignal && callerSignal
190
+ ? AbortSignal.any([timeoutSignal, callerSignal])
191
+ : timeoutSignal || callerSignal;
192
+ const { response, retryAfterMs } = await (0, rate_limit_retry_1.fetchWithRateLimitRetry)(() => fetch(finalUrl, { method, headers, body: finalBody, ...(signal ? { signal } : {}), ...(0, task_runtime_headers_1.taskRuntimeRedirectOptions)(headers) }), { method, headers });
185
193
  lastRetryAfterMs = retryAfterMs;
186
194
  return response;
187
195
  }
@@ -19,6 +19,7 @@ export interface Todo {
19
19
  rationale?: string;
20
20
  confidence?: TodoConfidence;
21
21
  triggerId?: string;
22
+ updatedAt?: string;
22
23
  firedAt?: string;
23
24
  result?: {
24
25
  conversationId?: string;
@@ -63,6 +64,7 @@ export interface SmartScheduleProposal {
63
64
  workingHours: WorkingHoursWindow;
64
65
  contextSummary: {
65
66
  spaceName?: string;
67
+ taskScope?: 'lead_space' | 'no_lead_space' | 'unavailable';
66
68
  goalCount: number;
67
69
  openTaskCount: number;
68
70
  truncated: boolean;
@@ -179,6 +181,11 @@ export declare const scheduleApi: {
179
181
  listTodos: (agentId: string, date?: string, timezone?: string) => Promise<ListTodosResponse>;
180
182
  getTodo: (todoId: string) => Promise<Todo>;
181
183
  createTodo: (input: CreateTodoInput) => Promise<Todo>;
184
+ updateTodo: (todoId: string, patch: {
185
+ name?: string;
186
+ runAt?: string;
187
+ expectedUpdatedAt?: string;
188
+ }) => Promise<Todo>;
182
189
  /** Cancel a To-Do (DELETE soft-cancels pending; see todos.js). */
183
190
  cancelTodo: (todoId: string) => Promise<Todo>;
184
191
  proposeSmartSchedule: (input: {
@@ -61,6 +61,7 @@ exports.scheduleApi = {
61
61
  listTodos: (agentId, date, timezone) => data_provider_1.request.get(todoListPath(agentId, date, timezone)),
62
62
  getTodo: (todoId) => data_provider_1.request.get(`/api/todos/${encodeURIComponent(todoId)}`),
63
63
  createTodo: (input) => data_provider_1.request.post('/api/todos', input),
64
+ updateTodo: (todoId, patch) => data_provider_1.request.patch(`/api/todos/${encodeURIComponent(todoId)}`, patch),
64
65
  /** Cancel a To-Do (DELETE soft-cancels pending; see todos.js). */
65
66
  cancelTodo: (todoId) => data_provider_1.request.delete(`/api/todos/${encodeURIComponent(todoId)}`),
66
67
  proposeSmartSchedule: (input) => data_provider_1.request.post('/api/todos/smart-schedule/propose', input),
@@ -159,6 +160,14 @@ function renderProposal(proposal, log) {
159
160
  const ctx = proposal.contextSummary;
160
161
  log(`Context: ${ctx.openTaskCount} open task(s), ${ctx.goalCount} goal(s)` +
161
162
  `${ctx.spaceName ? ` · ${ctx.spaceName}` : ''}${ctx.truncated ? ' (truncated)' : ''}`);
163
+ const taskScope = ctx.taskScope === 'lead_space'
164
+ ? 'Tasks were checked only in the lead Space; assigned tasks in other Spaces were not checked.'
165
+ : ctx.taskScope === 'no_lead_space'
166
+ ? 'No lead Space was found; assigned tasks in other Spaces were not checked.'
167
+ : ctx.taskScope === 'unavailable'
168
+ ? 'Task context was unavailable; the count may be incomplete.'
169
+ : 'Task search scope was not recorded for this proposal.';
170
+ log(`Task scope: ${taskScope}`);
162
171
  if (proposal.expiresAt)
163
172
  log(`Expires: ${formatStamp(proposal.expiresAt)}`);
164
173
  renderCapacityWarning(proposal.capacity, log);
@@ -82,6 +82,16 @@ export declare function parseMemoryApiError(error: ApiFetchError): {
82
82
  retryable: boolean;
83
83
  details?: Record<string, unknown>;
84
84
  };
85
+ /** A gateway timeout can arrive after the canonical mutation committed. */
86
+ export declare function uncertainMemoryMutationRecovery(path: string, options: ApiFetchOptions, status: number): {
87
+ message: string;
88
+ details: {
89
+ spaceId?: string | undefined;
90
+ outcome: string;
91
+ targetStoreId: string;
92
+ idempotencyKey: string;
93
+ };
94
+ } | null;
85
95
  export declare abstract class ScopedMemoryCommand extends BaseCommand {
86
96
  protected endpoint(agentId: string, suffix?: string): string;
87
97
  protected memoryApi<T>(agentId: string, suffix: string, options?: ApiFetchOptions): Promise<T>;
@@ -7,6 +7,7 @@ exports.memoryNotMountedDiagnostic = memoryNotMountedDiagnostic;
7
7
  exports.parseMemoryMount = parseMemoryMount;
8
8
  exports.parseMountedMemoryStore = parseMountedMemoryStore;
9
9
  exports.parseMemoryApiError = parseMemoryApiError;
10
+ exports.uncertainMemoryMutationRecovery = uncertainMemoryMutationRecovery;
10
11
  const core_1 = require("@oclif/core");
11
12
  const node_crypto_1 = require("node:crypto");
12
13
  const base_command_1 = require("../base-command");
@@ -191,6 +192,23 @@ function parseMemoryApiError(error) {
191
192
  };
192
193
  }
193
194
  }
195
+ /** A gateway timeout can arrive after the canonical mutation committed. */
196
+ function uncertainMemoryMutationRecovery(path, options, status) {
197
+ if (status !== 504 || options.method !== 'POST' || !/\/mutations\/[^/?]+$/.test(path))
198
+ return null;
199
+ const body = options.body;
200
+ if (!body || body.dryRun === true || typeof body.idempotencyKey !== 'string' || typeof body.targetStoreId !== 'string')
201
+ return null;
202
+ return {
203
+ message: `The write may have been saved. Inspect the canonical Store with skrr memory topics list using the same --agent and --store ${body.targetStoreId}${body.spaceId ? ' and --space ' + body.spaceId : ''}. If retrying the identical operation, use --idempotency-key ${body.idempotencyKey}.`,
204
+ details: {
205
+ outcome: 'unknown',
206
+ targetStoreId: body.targetStoreId,
207
+ idempotencyKey: body.idempotencyKey,
208
+ ...(typeof body.spaceId === 'string' ? { spaceId: body.spaceId } : {}),
209
+ },
210
+ };
211
+ }
194
212
  class ScopedMemoryCommand extends base_command_1.BaseCommand {
195
213
  endpoint(agentId, suffix = '') {
196
214
  return `/api/agents/${encodeURIComponent(agentId)}/memories/workspace${suffix}`;
@@ -229,13 +247,14 @@ class ScopedMemoryCommand extends base_command_1.BaseCommand {
229
247
  catch (error) {
230
248
  if (error instanceof api_fetch_1.ApiFetchError) {
231
249
  const parsed = parseMemoryApiError(error);
250
+ const recovery = uncertainMemoryMutationRecovery(path, options, error.status);
232
251
  this.failWithCliError({
233
- message: `${parsed.code}: ${parsed.message}`,
252
+ message: `${parsed.code}: ${parsed.message}${recovery ? ` ${recovery.message}` : ''}`,
234
253
  code: parsed.code,
235
254
  status: error.status,
236
255
  exit: ERROR_EXITS[parsed.code] || (error.status === 404 ? 4 : error.status === 403 ? 5 : 2),
237
256
  retryable: parsed.retryable,
238
- details: parsed.details,
257
+ details: recovery ? { ...parsed.details, ...recovery.details } : parsed.details,
239
258
  });
240
259
  }
241
260
  this.handleApiError(error);