@gakim-digital/dexter-bridge 0.11.12 → 0.11.13

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": "@gakim-digital/dexter-bridge",
3
- "version": "0.11.12",
3
+ "version": "0.11.13",
4
4
  "description": "Local bridge for InstaWebAI and Dexter — runs Codex, Claude Code, or OpenCode on your machine.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/agent.js CHANGED
@@ -405,7 +405,7 @@ function argsWithClaudeIsolation(args, definition, options = {}) {
405
405
  (name) => `mcp__instawebai__${name}`,
406
406
  );
407
407
  const nativeTools = options.framerHarness
408
- ? harnessTools
408
+ ? [...(options.nativeSkillsEnabled ? ['Skill'] : []), ...harnessTools]
409
409
  : [
410
410
  'Read',
411
411
  'Write',
@@ -442,7 +442,12 @@ function argsWithClaudeIsolation(args, definition, options = {}) {
442
442
  configured = argsWithoutFlagValue(configured, '--mcp-config');
443
443
  configured.push('--mcp-config', JSON.stringify(options.mcpConfig));
444
444
  }
445
- if (options.harnessMode && !argsIncludeFlag(configured, '--restricted')) {
445
+ // Restricted mode ignores the skills written into the run workspace, so a
446
+ // Framer run with skills leaves it off. The explicit tool list above is what
447
+ // keeps the shell and file tools out; it grants the Framer tools and Skill
448
+ // only, and settings stay limited to the workspace the bridge created.
449
+ const framerRunWithSkills = Boolean(options.framerHarness && options.nativeSkillsEnabled);
450
+ if (options.harnessMode && !framerRunWithSkills && !argsIncludeFlag(configured, '--restricted')) {
446
451
  configured.push('--restricted');
447
452
  }
448
453
  return configured;
@@ -2345,6 +2350,26 @@ async function executeOutcomeRun(run, send, agent, options = {}) {
2345
2350
  });
2346
2351
  return;
2347
2352
  }
2353
+ if (event.kind === 'skill_used') {
2354
+ // The harness loaded a skill on its own. Dexter shows which one; the
2355
+ // aggregate of picked skills is already known to it.
2356
+ const skill = (materializedNativeSkills?.skills || []).find(
2357
+ (candidate) => candidate.nativeName === event.skillName,
2358
+ );
2359
+ if (!skill) return;
2360
+ void postActivity({
2361
+ stage: 'outcome',
2362
+ outcomeId: run?.outcome?.outcomeId,
2363
+ source: 'harness',
2364
+ meaningful: true,
2365
+ kind: 'skill_used',
2366
+ provider: definition.id,
2367
+ skillId: skill.skillId,
2368
+ occurredAt: event.occurredAt || new Date().toISOString(),
2369
+ message: `Using ${skill.name}`,
2370
+ });
2371
+ return;
2372
+ }
2348
2373
  const meaningful =
2349
2374
  Boolean(event.message || event.name) ||
2350
2375
  event.kind === 'turn_started';
@@ -397,6 +397,11 @@ export function createClaudeProgressParser(onProgress, options = {}) {
397
397
  });
398
398
  continue;
399
399
  }
400
+ if (part.name === 'Skill' && progressText(input.skill)) {
401
+ tools.set(part.id, { suppressResult: true });
402
+ emit({ kind: 'skill_used', skillName: progressText(input.skill) });
403
+ continue;
404
+ }
400
405
  const progress = claudeToolProgress(part.name, input, options.cwd);
401
406
  tools.set(part.id, { name: part.name, ...progress });
402
407
  emit({
@@ -429,11 +434,45 @@ export function createClaudeProgressParser(onProgress, options = {}) {
429
434
  }
430
435
  };
431
436
 
437
+ // A large edit can take the model a minute to write, and nothing else is
438
+ // reported until it has finished writing. Report the moment it starts, and
439
+ // close that report when the finished tool call arrives.
440
+ const drafting = new Map();
441
+ const DRAFTING_MESSAGE = 'Preparing changes for the Framer project';
442
+ const processStreamEvent = (row) => {
443
+ const event = row.event;
444
+ const block = event?.type === 'content_block_start' ? event.content_block : null;
445
+ if (block?.type !== 'tool_use' || !block.id || drafting.has(block.id)) return;
446
+ const shortName = String(block.name || '').replace(/^mcp__instawebai__/, '');
447
+ if (!['framer_apply_changes', 'framer_update_code_file', 'framer_write'].includes(shortName)) return;
448
+ drafting.set(block.id, true);
449
+ emit({
450
+ kind: 'item_started',
451
+ phase: 'started',
452
+ activity: `tool-draft:${block.id}`,
453
+ message: DRAFTING_MESSAGE,
454
+ });
455
+ };
456
+ const finishDrafting = (row) => {
457
+ for (const part of Array.isArray(row.message?.content) ? row.message.content : []) {
458
+ if (part?.type !== 'tool_use' || drafting.get(part.id) !== true) continue;
459
+ drafting.set(part.id, false);
460
+ emit({
461
+ kind: 'item_completed',
462
+ phase: 'completed',
463
+ activity: `tool-draft:${part.id}`,
464
+ message: DRAFTING_MESSAGE,
465
+ });
466
+ }
467
+ };
468
+
432
469
  const processLine = (line) => {
433
470
  const trimmed = line.trim();
434
471
  if (!trimmed) return;
435
472
  try {
436
473
  const row = JSON.parse(trimmed);
474
+ if (row?.type === 'stream_event') processStreamEvent(row);
475
+ if (row?.type === 'assistant') finishDrafting(row);
437
476
  if (row?.type === 'assistant') processAssistant(row);
438
477
  if (row?.type === 'user') processUser(row);
439
478
  } catch {
@@ -174,7 +174,7 @@ function renderAggregateClaudeSkill(skills, nativeName) {
174
174
  return [
175
175
  '---',
176
176
  `name: ${yamlString(nativeName)}`,
177
- `description: ${yamlString('Apply every selected InstaWebAI skill for this model turn.')}`,
177
+ `description: ${yamlString('Apply every skill the user selected for this request.')}`,
178
178
  'user-invocable: true',
179
179
  'disable-model-invocation: true',
180
180
  '---',
@@ -231,7 +231,7 @@ export function materializeNativeSkills(
231
231
  ['always', 'explicit', 'command'].includes(skill.trigger),
232
232
  );
233
233
  if (provider === 'claude-code' && requiredSkills.length > 0) {
234
- aggregateName = `instawebai-active-${digest.slice(0, 8)}`;
234
+ aggregateName = `dexter-active-${digest.slice(0, 8)}`;
235
235
  const aggregateDirectory = path.join(skillsRoot, aggregateName);
236
236
  fs.mkdirSync(aggregateDirectory, { recursive: true, mode: 0o700 });
237
237
  aggregatePath = path.join(aggregateDirectory, 'SKILL.md');
@@ -260,7 +260,7 @@ export function claudeNativeSkillPrompt(prompt, materialized) {
260
260
  return [
261
261
  materialized?.aggregateName ? `/${materialized.aggregateName}` : '',
262
262
  catalog
263
- ? `Available optional skills; invoke one only when it is relevant:\n${catalog}`
263
+ ? `Available optional skills; invoke one only when it is relevant, and do not use any skill that is not listed here:\n${catalog}`
264
264
  : '',
265
265
  String(prompt || ''),
266
266
  ].filter(Boolean).join('\n\n');