shraga 0.1.26 → 0.1.27

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.26",
3
+ "version": "0.1.27",
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",
@@ -74,7 +74,7 @@ export type WsEvent =
74
74
  | { type: 'text_delta'; text: string }
75
75
  | { type: 'tool_use'; tool: string; toolUseId: string; input: unknown }
76
76
  | { type: 'tool_use_input'; toolUseId: string; input: unknown }
77
- | { type: 'tool_result'; toolUseId: string; output: string }
77
+ | { type: 'tool_result'; toolUseId: string; output: string; isError?: boolean }
78
78
  | { type: 'tool_result_image'; toolUseId: string; dataUrl: string }
79
79
  | { type: 'permission_request'; id: string; tool: string; input: Record<string, unknown> }
80
80
  | { type: 'question_request'; id: string; questions: AskQuestion[] }
@@ -446,7 +446,9 @@ export class ClaudeCodeEngine implements AgentEngine {
446
446
  const output = contentArr.length > 0
447
447
  ? contentArr.filter((c: any) => c.type === 'text').map((c: any) => c.text ?? '').join('')
448
448
  : String(block.content ?? '');
449
- yield { type: 'tool_result', toolUseId: String(block.tool_use_id), output };
449
+ // `is_error` is the ONLY signal that a tool failed — the text alone is indistinguishable
450
+ // from a successful result. Scheduled `bash` tasks rely on it to notice a non-zero exit.
451
+ yield { type: 'tool_result', toolUseId: String(block.tool_use_id), output, isError: block.is_error === true };
450
452
 
451
453
  let foundImage = false;
452
454
  for (const c of contentArr) {
@@ -194,12 +194,21 @@ export async function runSchedule(
194
194
  let status: ScheduleRunSummary['status'] = 'ok';
195
195
  let error: string | undefined;
196
196
 
197
+ // A `bash` task is not exec'd — it's handed to the agent as a prompt, so the command's exit code
198
+ // reaches nobody: the agent reports the failure in prose, its own turn succeeds, the run is stored
199
+ // `ok`, and the failure notifier never fires. Track the tool_result of the task's OWN Bash call and
200
+ // fail the run with it, so a broken scheduled script alerts like a `job` does.
201
+ const taskBashToolUseIds = new Set<string>();
202
+ let bashFailure: string | undefined;
203
+
197
204
  onEvent({ type: 'schedule:run_started', scheduleId: schedule.id, sessionId, at: now });
198
205
 
199
206
  try {
200
207
  for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
201
208
  status = 'ok';
202
209
  error = undefined;
210
+ bashFailure = undefined;
211
+ taskBashToolUseIds.clear();
203
212
  try {
204
213
  for await (const ev of streamChat({
205
214
  prompt,
@@ -217,10 +226,12 @@ export async function runSchedule(
217
226
  } else if (ev.type === 'tool_use') {
218
227
  if (assistantText) { assistantBlocks.push({ type: 'text', text: assistantText }); assistantText = ''; }
219
228
  assistantBlocks.push({ type: 'tool_use', tool: ev.tool, toolUseId: ev.toolUseId, input: ev.input });
229
+ if (allowedCmd && ev.tool === 'Bash' && (ev.input as any)?.command === allowedCmd) taskBashToolUseIds.add(ev.toolUseId);
220
230
  } else if (ev.type === 'thinking_delta') {
221
231
  producedThinking = true;
222
232
  } else if (ev.type === 'tool_result') {
223
233
  assistantBlocks.push({ type: 'tool_result', toolUseId: ev.toolUseId, output: ev.output });
234
+ if (ev.isError && taskBashToolUseIds.has(ev.toolUseId)) bashFailure = ev.output;
224
235
  } else if (ev.type === 'done') {
225
236
  break;
226
237
  } else if (ev.type === 'error') {
@@ -238,6 +249,12 @@ export async function runSchedule(
238
249
  }
239
250
  }
240
251
 
252
+ // The agent turn can succeed while the command it was asked to run failed — that's the run failing.
253
+ if (status === 'ok' && bashFailure) {
254
+ status = 'error';
255
+ error = `Scheduled command failed:\n${bashFailure.slice(0, 4000)}`;
256
+ }
257
+
241
258
  if (status !== 'error') break;
242
259
 
243
260
  // The side-effect boundary. This is NOT an exact `ttft=-1` test — the engine emits events we