shraga 0.1.43 → 0.1.45

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.
@@ -39,7 +39,11 @@ The workspace (`data/workspace/`) has two scopes. Full architecture: `defaults/w
39
39
 
40
40
  - Be direct and concise. Answer succinctly.
41
41
  - Always respond with a brief verbal acknowledgment before making tool calls. For example: "Let me check that" or "Looking into it." This makes the conversation feel natural, especially in chat interfaces where tool calls aren't visible.
42
- - Do NOT spawn sub-agents.
42
+ - Do NOT spawn sub-agents. This gates the `Agent`/`Task` tool ONLY. External CLI processes you launch
43
+ with `Bash` (`agentx`, `claude -p`, `cursor-agent`, …) are not sub-agents and are never blocked by this
44
+ rule — when a skill documents a Bash launch, use it.
45
+ - Never abort a task on a *suspected* capability or permission block. Load the relevant skill and actually
46
+ attempt the documented path first; report the real error, not an assumed one.
43
47
  - Do NOT read large dump files — use targeted queries with limits.
44
48
  - When using MCP tools, prefer small queries (limitToLast=5) over broad fetches.
45
49
  - When running scripts or shell commands, always show the output (or a meaningful summary if very long) as text in your response. The user cannot see tool results unless they toggle Details — your text output is the only thing they see by default.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shraga",
3
- "version": "0.1.43",
3
+ "version": "0.1.45",
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",
@@ -2,6 +2,7 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync, statSync } from 'no
2
2
  import path from 'node:path';
3
3
  import { DATA_DIR } from './paths.ts';
4
4
  import { emitEvent } from './events/bus.ts';
5
+ import { runTextQuery } from './sdk-utils.ts';
5
6
 
6
7
  const TAG = '[data-sync]';
7
8
  const DEPLOYMENT_ID_FILE = '.deployment-id';
@@ -340,7 +341,7 @@ export class DataSync {
340
341
  const msg = await this.askClaude(
341
342
  'Write a concise git commit message (max 72 chars, no quotes, no prefix like "feat:" or "sync:") for this change to an AI agent\'s behavioral config.\n' +
342
343
  `Files: ${files.join(', ')}\nStats: ${diff}\n\nDiff:\n${truncated}`,
343
- 'claude-haiku-4-5-20251001', 100,
344
+ 'haiku',
344
345
  );
345
346
  const line = msg.split('\n')[0].trim().slice(0, 72);
346
347
  return line || fallback;
@@ -382,7 +383,7 @@ export class DataSync {
382
383
  'Output format:\n' +
383
384
  '<file path="<path>" complexity="trivial|ambiguous" reason="short explanation">\n<resolved content>\n</file>\n' +
384
385
  'Output ONLY the resolved files in this format.\n\n' + sections,
385
- 'claude-opus-4-6',
386
+ 'opus',
386
387
  );
387
388
 
388
389
  const parsed = this.parseResolvedFiles(resolved);
@@ -462,7 +463,7 @@ export class DataSync {
462
463
  'Output format:\n' +
463
464
  '<file path="<path>" complexity="trivial|ambiguous" reason="short explanation">\n<resolved content>\n</file>\n' +
464
465
  'Output ONLY the resolved files.\n\n' + sections,
465
- 'claude-opus-4-6',
466
+ 'opus',
466
467
  );
467
468
 
468
469
  const parsed = this.parseResolvedFiles(resolved);
@@ -564,24 +565,15 @@ export class DataSync {
564
565
  emitEvent('data-sync', { kind: 'deploy', owners, text });
565
566
  }
566
567
 
567
- private async askClaude(prompt: string, model = 'claude-sonnet-5', maxTokens = 8192): Promise<string> {
568
- const apiKey = process.env.ANTHROPIC_API_KEY;
569
- if (!apiKey) throw new Error('ANTHROPIC_API_KEY not set');
570
- const resp = await fetch('https://api.anthropic.com/v1/messages', {
571
- method: 'POST',
572
- headers: {
573
- 'x-api-key': apiKey,
574
- 'anthropic-version': '2023-06-01',
575
- 'content-type': 'application/json',
576
- },
577
- body: JSON.stringify({
578
- model, max_tokens: maxTokens,
579
- messages: [{ role: 'user', content: prompt }],
580
- }),
581
- });
582
- if (!resp.ok) throw new Error(`Anthropic API ${resp.status}: ${await resp.text()}`);
583
- const data = await resp.json() as { content: { type: string; text: string }[] };
584
- return data.content.find(b => b.type === 'text')?.text || '';
568
+ /**
569
+ * Routed through the Claude Code SDK (runTextQuery), same as the rest of the platform —
570
+ * authenticates via the CC subscription, no ANTHROPIC_API_KEY required. Past incident:
571
+ * this used to hit the raw Anthropic Messages API directly with process.env.ANTHROPIC_API_KEY,
572
+ * which is unset on subscription-auth deployments — every merge-conflict resolution failed and
573
+ * spammed owners via notifyOwners().
574
+ */
575
+ private async askClaude(prompt: string, model: 'haiku' | 'sonnet' | 'opus' = 'sonnet'): Promise<string> {
576
+ return runTextQuery({ prompt, model, maxTurns: 1 });
585
577
  }
586
578
 
587
579
  private async getConflictedFiles(): Promise<string[]> {
@@ -23,12 +23,42 @@ import { MODEL_ALIASES } from './model-aliases.ts';
23
23
 
24
24
  const DIRECTIVE_RE = /^\s*\[([^\]]*)\]\s*([\s\S]*)/;
25
25
 
26
+ const DIRECTIVE_KEYS = ['model', 'turns', 'thinking', 'think', 'effort', 'engine'];
27
+
28
+ /** Does a bracket group look like directives (vs. prompt text that happens to start with `[`)?
29
+ * Every token must be a known key:value or a known positional, else we leave the group alone. */
30
+ function isDirectiveGroup(raw: string): boolean {
31
+ const tokens = raw.split(',').map((t) => t.trim()).filter(Boolean);
32
+ if (!tokens.length) return false;
33
+ return tokens.every((t) => {
34
+ const colonIdx = t.indexOf(':');
35
+ if (colonIdx !== -1) return DIRECTIVE_KEYS.includes(t.slice(0, colonIdx).trim().toLowerCase());
36
+ const v = t.toLowerCase();
37
+ return !!MODEL_ALIASES[v] || /^\d+$/.test(v) || ['think', 'adaptive', 'nothink', 'nothinking'].includes(v);
38
+ });
39
+ }
40
+
26
41
  export function parseDirectives(text: string): ParsedPrompt {
27
- const match = text.match(DIRECTIVE_RE);
28
- if (!match) return { prompt: text, directives: {} };
42
+ // Consume EVERY consecutive leading [..] group, not just the first. runner.ts prepends
43
+ // `[model] ` onto prompts that may already open with `[turns:120]`, so a single-group parse
44
+ // silently dropped the second — a schedule pinned to opus quietly ran on the config default
45
+ // for three days. Groups that don't parse as directives are left as prompt text.
46
+ let rest = text;
47
+ const groups: string[] = [];
48
+ for (;;) {
49
+ const m = rest.match(DIRECTIVE_RE);
50
+ if (!m) break;
51
+ const g = m[1].trim();
52
+ // The first group is always consumed (long-standing contract: `[unknown] hi` strips and warns).
53
+ // Later groups must actually look like directives, so prompt text such as `[WARN] …` survives.
54
+ if (groups.length && g && !isDirectiveGroup(g)) break;
55
+ groups.push(g);
56
+ rest = m[2];
57
+ }
58
+ if (!groups.length) return { prompt: text, directives: {} };
29
59
 
30
- const raw = match[1].trim();
31
- const prompt = match[2].trim();
60
+ const raw = groups.filter(Boolean).join(',');
61
+ const prompt = rest.trim();
32
62
  if (!raw) return { prompt, directives: {} };
33
63
 
34
64
  const directives: Directives = {};