shraga 0.1.160 → 0.1.162

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.
@@ -13,7 +13,7 @@
13
13
  <link rel="preconnect" href="https://fonts.googleapis.com" />
14
14
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
15
15
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet" />
16
- <script type="module" crossorigin src="/assets/index-yBsLZ3yV.js"></script>
16
+ <script type="module" crossorigin src="/assets/index-B9Ydbjyv.js"></script>
17
17
  <link rel="stylesheet" crossorigin href="/assets/index-NtN1I_dh.css">
18
18
  </head>
19
19
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shraga",
3
- "version": "0.1.160",
3
+ "version": "0.1.162",
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",
@@ -313,6 +313,11 @@ async function* runTurn(opts: StreamChatOpts, guard?: TurnGuard): AsyncGenerator
313
313
 
314
314
  const sessionMeta = opts.sessionId ? getSession(opts.sessionId) : undefined;
315
315
  const directives: Directives = { ...sessionMeta?.directives, ...parsed };
316
+ // A two-model mode (delegate/advisor) is sticky like the model: `[off]` clears it, and a new mode
317
+ // replaces the other one. `modeOff` is a command, not a setting — never stored.
318
+ if (parsed.modeOff || parsed.advisor) delete directives.delegate;
319
+ if (parsed.modeOff || parsed.delegate) delete directives.advisor;
320
+ delete directives.modeOff;
316
321
  // Persist ONLY what was explicitly chosen (inline directives, or the Agent Config panel's
317
322
  // per-conversation save). Gaps stay gaps: every engine resolves `directives.x ?? config.x` at
318
323
  // send time, so the global agent-config keeps applying live to sessions nobody pinned —
@@ -6,6 +6,12 @@ export interface Directives {
6
6
  engine?: string;
7
7
  /** claude-code SDK session resume for this conversation (overrides agent-config `sdkResume`). */
8
8
  resume?: boolean;
9
+ /** agentx DELEGATE mode `[planner->worker]` (key `delegate`): the worker model; `model` (left side) plans. */
10
+ delegate?: string;
11
+ /** agentx ADVISOR mode `[worker<-advisor]` (key `advisor`): the advisor model; `model` (left side) works. */
12
+ advisor?: string;
13
+ /** `[off]` / `[mode:off]`: clears a sticky delegate/advisor mode. Consumed by streamChat, never stored. */
14
+ modeOff?: boolean;
9
15
  }
10
16
 
11
17
  export interface ParsedPrompt {
@@ -28,7 +34,15 @@ import { MODEL_ALIASES } from './model-aliases.ts';
28
34
 
29
35
  const DIRECTIVE_RE = /^\s*\[([^\]]*)\]\s*([\s\S]*)/;
30
36
 
31
- const DIRECTIVE_KEYS = ['model', 'turns', 'thinking', 'think', 'effort', 'engine', 'resume'];
37
+ const DIRECTIVE_KEYS = ['model', 'turns', 'thinking', 'think', 'effort', 'engine', 'resume', 'delegate', 'advisor', 'mode'];
38
+
39
+ /** `a->b` (delegate) / `a<-b` (advisor) — agentx's two-model mode grammar; the left side may be empty. */
40
+ function splitModePair(token: string): { left: string; op: '->' | '<-'; right: string } | null {
41
+ const i = token.search(/->|<-/);
42
+ if (i === -1) return null;
43
+ const right = token.slice(i + 2).trim();
44
+ return right ? { left: token.slice(0, i).trim(), op: token.slice(i, i + 2) as '->' | '<-', right } : null;
45
+ }
32
46
 
33
47
  /** MODEL_ALIASES only covers the bare Anthropic shorthands. A `provider/model` id
34
48
  * (`cursor/composer-2.5`, `openai/gpt-5.6`) is already concrete — gating it on the alias table
@@ -72,6 +86,9 @@ export function isDirectiveGroup(raw: string): boolean {
72
86
  const colonIdx = t.indexOf(':');
73
87
  if (colonIdx !== -1) return DIRECTIVE_KEYS.includes(t.slice(0, colonIdx).trim().toLowerCase());
74
88
  const v = t.toLowerCase();
89
+ const pair = splitModePair(v);
90
+ if (pair) return (!pair.left || !!resolveModelAlias(pair.left)) && !!resolveModelAlias(pair.right);
91
+ if (v === 'off') return true;
75
92
  return !!MODEL_ALIASES[v] || !!modelResolver?.(v) || /^\d+$/.test(v) || ['think', 'adaptive', 'nothink', 'nothinking'].includes(v);
76
93
  });
77
94
  }
@@ -132,6 +149,17 @@ export function parseDirectives(text: string): ParsedPrompt {
132
149
  unresolvedModel = applyDirective(directives, key, val) ?? unresolvedModel;
133
150
  } else {
134
151
  const val = t.toLowerCase();
152
+ const pair = splitModePair(val);
153
+ if (pair) {
154
+ // Both sides must resolve: a half-resolved pair would silently run a mode on the wrong model.
155
+ const worker = resolveModelAlias(pair.right);
156
+ if (worker && (!pair.left || resolveModelToken(directives, pair.left))) {
157
+ if (pair.op === '->') directives.delegate = worker.model; else directives.advisor = worker.model;
158
+ } else unresolvedModel ??= t;
159
+ positionalIndex++;
160
+ continue;
161
+ }
162
+ if (val === 'off') { directives.modeOff = true; positionalIndex++; continue; }
135
163
  if (positionalIndex === 0 && resolveModelToken(directives, val)) {
136
164
  // handled
137
165
  } else if (positionalIndex <= 1 && /^\d+$/.test(val)) {
@@ -179,6 +207,18 @@ function applyDirective(d: Directives, key: string, val: string): string | undef
179
207
  case 'engine':
180
208
  d.engine = val;
181
209
  break;
210
+ case 'delegate':
211
+ case 'advisor': {
212
+ const hit = resolveModelAlias(val);
213
+ if (hit) d[key] = hit.model;
214
+ else if (isQualifiedModel(val)) d[key] = val;
215
+ else return val;
216
+ break;
217
+ }
218
+ case 'mode':
219
+ if (val === 'off') d.modeOff = true;
220
+ else console.warn(`[directives] Invalid mode value: "${val}"`);
221
+ break;
182
222
  case 'resume':
183
223
  if (['on', 'true', '1'].includes(val)) d.resume = true;
184
224
  else if (['off', 'false', '0'].includes(val)) d.resume = false;
@@ -352,6 +352,12 @@ export class ClaudeCodeEngine implements AgentEngine {
352
352
  if (opts.userEmail) sdkEnv.SHRAGA_USER_EMAIL = sdkEnv.UNCLAW_USER_EMAIL = opts.userEmail;
353
353
  sdkEnv.SHRAGA_SESSION_ID = sdkEnv.UNCLAW_SESSION_ID = opts.sessionId ?? '';
354
354
  Object.assign(sdkEnv, offloadEnv());
355
+ // No CLAUDE.md, ever. settingSources:['project'] does NOT stop it: CC walks UP from cwd and loads
356
+ // every ancestor's CLAUDE.md / .claude/CLAUDE.md as a "Project" file — with the app under $HOME
357
+ // that is the host's ~/.claude/CLAUDE.md (an operator's personal dev rules: "plan first, let me
358
+ // review"), which made the agent and its sub-agents stop for approval (feedox, 2026-10-25). The
359
+ // agent's instructions are ours (system prompt + DATA_DIR/skills); a repo CLAUDE.md is dev docs.
360
+ sdkEnv.CLAUDE_CODE_DISABLE_CLAUDE_MDS = '1';
355
361
  // Per-command wall-clock cap for the SDK's Bash tool. Without it a single unbounded command
356
362
  // (e.g. `curl` with no `-m` against an SSE endpoint that streams nothing) eats the whole turn and
357
363
  // the agent answers with "reads were interrupted". The model still sees `[exit 124]` + partial