tuna-agent 0.1.157 → 0.1.159

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.
@@ -407,6 +407,15 @@ Rules:
407
407
  }
408
408
  // Single source of truth for the master-cast prompt block (used by Phase-1
409
409
  // and the post-Phase-2 cast reconciliation so the format never drifts).
410
+ //
411
+ // The [CHARACTER CAST LIST] header carries CRITICAL RULES that tell the
412
+ // downstream image generator (Veo3 / FlowKit) how to render the cast. Rule
413
+ // #5 specifically prevents identity-morphing — even when the source video
414
+ // has siblings or look-alike characters, the generated reference sheet
415
+ // must give each character distinctly different facial structures so the
416
+ // per-scene Veo3 renders don't drift into the same face. This rule got
417
+ // lost in a prior refactor; restored to match the reference clone-tool
418
+ // (~/Downloads/if is_character_driven.txt).
410
419
  function buildMasterCastPrompt(videoStyle, characters) {
411
420
  if (!characters.length)
412
421
  return '';
@@ -414,8 +423,15 @@ function buildMasterCastPrompt(videoStyle, characters) {
414
423
  const castList = characters.map(c => `- ${c.name}: ${c.description}`).join('\n');
415
424
  return (`[AESTHETIC & STYLE]\n${styleLine}\n` +
416
425
  `[COMPOSITION & LAYOUT]\nCharacter Reference Sheet. Full-body side-by-side.\n` +
417
- `[CHARACTER CAST LIST]\n${castList}\n` +
418
- `[TECHNICAL SPECIFICATIONS]\nHigh detail, 8k resolution, consistent facial structures across all frames. Each character has a completely distinct face, hairstyle, body type and age — no two characters look alike.`);
426
+ `[CHARACTER CAST LIST] (CRITICAL RULES: ` +
427
+ `1. Max 5 INDIVIDUAL characters. ` +
428
+ `2. STRICTLY PROHIBIT collective nouns, groups, crowds, or plurals. ` +
429
+ `3. If a group exists, pick ONLY the most prominent 1 or 2 individuals. ` +
430
+ `4. Detail unique physical appearance and clothing. ` +
431
+ `5. FORCE UNIQUE facial and physical structures for EVERY character ` +
432
+ `(assign distinct body types, face shapes, wrinkles, or accessories) ` +
433
+ `to prevent identity morphing. List with '- '. English.)\n${castList}\n` +
434
+ `[TECHNICAL SPECIFICATIONS]\nHigh detail, 8k resolution.`);
419
435
  }
420
436
  // Post-Phase-2 cast RECONCILE. Phase-1 only sees a 30-frame sample so it can
421
437
  // miss a recurring character; the per-scene visionDescribe pass, however,
@@ -1,2 +1,3 @@
1
+ import './load-env.js';
1
2
  import type { AgentConfig } from '../types/index.js';
2
3
  export declare function startDaemon(config: AgentConfig): Promise<void>;
@@ -1,17 +1,10 @@
1
+ // ⚠ MUST be first: side-effect import that populates process.env from
2
+ // ~/.tuna-agent/.env BEFORE any other module captures env at its top
3
+ // level. See load-env.ts for the long-form explanation.
4
+ import './load-env.js';
1
5
  import fs from 'fs';
2
6
  import path from 'path';
3
7
  import os from 'os';
4
- // Load .env from ~/.tuna-agent/.env if exists (ensure OPENAI_API_KEY etc. available)
5
- const envPath = path.join(os.homedir(), '.tuna-agent', '.env');
6
- if (fs.existsSync(envPath)) {
7
- const envContent = fs.readFileSync(envPath, 'utf8');
8
- for (const line of envContent.split('\n')) {
9
- const match = line.match(/^\s*([A-Z_][A-Z0-9_]*)\s*=\s*(.*)\s*$/);
10
- if (match && !process.env[match[1]]) {
11
- process.env[match[1]] = match[2].replace(/^["']|["']$/g, '');
12
- }
13
- }
14
- }
15
8
  import { AgentWebSocketClient } from './ws-client.js';
16
9
  import { removePid, loadConfig, saveConfig } from '../config/store.js';
17
10
  import { validateMessage } from '../utils/message-schemas.js';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,28 @@
1
+ // Side-effect module: load ~/.tuna-agent/.env into process.env BEFORE any
2
+ // other daemon module reads process.env at its top level.
3
+ //
4
+ // daemon/index.ts must import this FIRST so the side-effect runs before
5
+ // modules like analyze-video-handler.ts capture `process.env.OPENAI_API_KEY`
6
+ // into a module-top const. ESM imports execute in dependency order, so
7
+ // putting this import line above the analyze-video-handler import (directly
8
+ // or transitively) guarantees the env is populated when those consts are
9
+ // evaluated.
10
+ //
11
+ // Previously the env loader lived inline in daemon/index.ts AFTER the
12
+ // imports — but ES module imports are processed BEFORE any executable code
13
+ // in the file, so analyze-video-handler captured OPENAI_API_KEY='' and the
14
+ // analyze flow died with 'OPENAI_API_KEY not set' even though the .env
15
+ // file was correct.
16
+ import fs from 'fs';
17
+ import path from 'path';
18
+ import os from 'os';
19
+ const envPath = path.join(os.homedir(), '.tuna-agent', '.env');
20
+ if (fs.existsSync(envPath)) {
21
+ const envContent = fs.readFileSync(envPath, 'utf8');
22
+ for (const line of envContent.split('\n')) {
23
+ const match = line.match(/^\s*([A-Z_][A-Z0-9_]*)\s*=\s*(.*)\s*$/);
24
+ if (match && !process.env[match[1]]) {
25
+ process.env[match[1]] = match[2].replace(/^["']|["']$/g, '');
26
+ }
27
+ }
28
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tuna-agent",
3
- "version": "0.1.157",
3
+ "version": "0.1.159",
4
4
  "description": "Tuna Agent - Run AI coding tasks on your machine",
5
5
  "bin": {
6
6
  "tuna-agent": "dist/cli/index.js"