shraga 0.1.79 → 0.1.80

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shraga",
3
- "version": "0.1.79",
3
+ "version": "0.1.80",
4
4
  "description": "The teammate you delegate coding to — a self-hostable, multi-user AI coding agent web UI (Claude Code, with a pluggable engine seam).",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -208,7 +208,7 @@ export async function runSchedule(
208
208
  // Tell the run how to report its own truthful outcome (scheduler/outcome.ts). Prompt tasks only:
209
209
  // a `bash` task's permission handler allows nothing but the task's own command, so such a run
210
210
  // could not write the file even if it wanted to — its exit code is already the truth there.
211
- if (task.kind === 'prompt') {
211
+ if ((task.kind ?? 'prompt') === 'prompt') {
212
212
  // Cleared on RESUME too: a resume reuses the interrupted run's session id, so a declaration left
213
213
  // by the attempt that crashed would be adopted as this attempt's verdict. The contract is
214
214
  // re-stated for the same reason — the resumed turn must be able to declare for itself.
@@ -401,7 +401,7 @@ export async function runSchedule(
401
401
  // The run's OWN verdict beats "the turn returned" — see scheduler/outcome.ts. Deliberately after
402
402
  // the finally: the session lock is released by now, so a run that declared `pending` can be closed
403
403
  // by a later turn in this session (a background job's wake, a follow-up message) while we wait.
404
- if (status === 'ok' && task.kind === 'prompt') {
404
+ if (status === 'ok' && (task.kind ?? 'prompt') === 'prompt') {
405
405
  const declared = await resolveDeclaredOutcome(sessionId, abortController, schedule.id);
406
406
  if (declared) {
407
407
  status = declared.status;
@@ -27,11 +27,28 @@ export function saveThrottleState(state: Record<string, number>): void {
27
27
  renameSync(tmp, THROTTLE_FILE);
28
28
  }
29
29
 
30
+ /**
31
+ * Schedules persisted before `task.kind` existed carry only the payload fields. Everything
32
+ * downstream branches on the discriminant, so infer it from the shape once, on load — a task
33
+ * missing its kind silently skipped the outcome contract and reported every run as `ok`.
34
+ * Only `prompt` is inferable: `bash` and `job` are both `{ command }`, and guessing between them
35
+ * would hand a run the wrong permission handler, so a kindless command task is left alone.
36
+ */
37
+ function normalizeTask(task: Record<string, unknown>): void {
38
+ if (task.kind) return;
39
+ if (typeof task.prompt === 'string' || typeof task.promptFile === 'string') task.kind = 'prompt';
40
+ }
41
+
42
+ function normalizeSchedule(s: Schedule): Schedule {
43
+ if (s.task && typeof s.task === 'object') normalizeTask(s.task as unknown as Record<string, unknown>);
44
+ return s;
45
+ }
46
+
30
47
  export function loadSchedules(): Schedule[] {
31
48
  if (!existsSync(FILE)) return [];
32
49
  try {
33
50
  const parsed = JSON.parse(readFileSync(FILE, 'utf-8'));
34
- return Array.isArray(parsed) ? parsed : [];
51
+ return Array.isArray(parsed) ? parsed.map((s) => normalizeSchedule(s as Schedule)) : [];
35
52
  } catch (err) {
36
53
  console.error('[scheduler] failed to parse schedules.json:', err);
37
54
  return [];