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.
|
|
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",
|
package/src/server/claude.ts
CHANGED
|
@@ -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
|
-
|
|
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
|