shraga 0.1.28 → 0.1.30

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.28",
3
+ "version": "0.1.30",
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",
@@ -7,3 +7,15 @@ const PLACEHOLDER = /^['"]?\$\{[A-Za-z0-9_]+\}['"]?$/;
7
7
  for (const [key, value] of Object.entries(process.env)) {
8
8
  if (typeof value === 'string' && PLACEHOLDER.test(value.trim())) delete process.env[key];
9
9
  }
10
+
11
+ // A PEM key stored UNQUOTED in an EnvironmentFile loses its backslashes — systemd does not process
12
+ // escapes there, so `KEY=-----BEGIN…\nMHc…` arrives as `-----BEGIN…nMHc…`: the `\n`s become stray `n`
13
+ // characters INSIDE the base64. The value still looks like a key, so it fails far away and much later
14
+ // (`NO_START_LINE` from OpenSSL, mid-job), and an inherited env beats any later `--env-file`. Warning
15
+ // at boot turns a day-long silent data gap into a line in the log. Warn, never delete — a broken
16
+ // credential is still better than a missing one for diagnosing.
17
+ for (const [key, value] of Object.entries(process.env)) {
18
+ if (typeof value !== 'string' || !value.includes('-----BEGIN')) continue;
19
+ if (value.includes('\n') || value.includes('\\n')) continue; // real or escaped newlines: fine
20
+ console.warn(`[env] ⚠️ ${key} looks like a PEM key with its newlines eaten (no \\n at all) — quote it in the env file ('single quotes'), else it will fail at use time with NO_START_LINE.`);
21
+ }
@@ -198,7 +198,10 @@ export async function runSchedule(
198
198
  // reaches nobody: the agent reports the failure in prose, its own turn succeeds, the run is stored
199
199
  // `ok`, and the failure notifier never fires. Track the tool_result of the task's OWN Bash call and
200
200
  // fail the run with it, so a broken scheduled script alerts like a `job` does.
201
- const taskBashToolUseIds = new Set<string>();
201
+ // Correlation is by toolUseId, decided at tool_result time: `tool_use` is yielded at
202
+ // content_block_start, when the input is still EMPTY (it streams in afterwards as `tool_use_input`),
203
+ // so matching the command there matches nothing.
204
+ const toolUses = new Map<string, { tool: string; input: unknown }>();
202
205
  let bashFailure: string | undefined;
203
206
 
204
207
  onEvent({ type: 'schedule:run_started', scheduleId: schedule.id, sessionId, at: now });
@@ -208,7 +211,7 @@ export async function runSchedule(
208
211
  status = 'ok';
209
212
  error = undefined;
210
213
  bashFailure = undefined;
211
- taskBashToolUseIds.clear();
214
+ toolUses.clear();
212
215
  try {
213
216
  for await (const ev of streamChat({
214
217
  prompt,
@@ -226,12 +229,20 @@ export async function runSchedule(
226
229
  } else if (ev.type === 'tool_use') {
227
230
  if (assistantText) { assistantBlocks.push({ type: 'text', text: assistantText }); assistantText = ''; }
228
231
  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);
232
+ toolUses.set(ev.toolUseId, { tool: ev.tool, input: ev.input });
233
+ } else if (ev.type === 'tool_use_input') {
234
+ // The tool's real input arrives here, after the block opened — keep both the persisted
235
+ // block and the correlation map in sync, else the transcript shows an empty `{}` call.
236
+ const entry = toolUses.get(ev.toolUseId);
237
+ if (entry) entry.input = ev.input;
238
+ const block = assistantBlocks.find((b) => b.type === 'tool_use' && b.toolUseId === ev.toolUseId) as any;
239
+ if (block) block.input = ev.input;
230
240
  } else if (ev.type === 'thinking_delta') {
231
241
  producedThinking = true;
232
242
  } else if (ev.type === 'tool_result') {
233
243
  assistantBlocks.push({ type: 'tool_result', toolUseId: ev.toolUseId, output: ev.output });
234
- if (ev.isError && taskBashToolUseIds.has(ev.toolUseId)) bashFailure = ev.output;
244
+ const use = toolUses.get(ev.toolUseId);
245
+ if (ev.isError && allowedCmd && use?.tool === 'Bash' && (use.input as any)?.command === allowedCmd) bashFailure = ev.output;
235
246
  } else if (ev.type === 'done') {
236
247
  break;
237
248
  } else if (ev.type === 'error') {