Browse Source

fix: handle image routing validation fallback and logging

Alvin Unreal 1 month ago
parent
commit
2c6a2963f2

+ 4 - 1
src/config/constants.ts

@@ -97,7 +97,10 @@ export const DEFAULT_READ_CONTEXT_MAX_FILES = 8;
 
 export type ImageRouting = 'auto' | 'direct';
 
-/** Default image routing mode, preserving Observer's existing behavior. */
+/**
+ * Used when image_routing is omitted, preserving legacy conditional Observer
+ * routing. Explicit "auto" is validated separately after config layers merge.
+ */
 export const DEFAULT_IMAGE_ROUTING: ImageRouting = 'auto';
 
 export function resolveImageRouting(

+ 16 - 3
src/config/loader.test.ts

@@ -106,7 +106,7 @@ describe('loadPluginConfig', () => {
     expect(config.disabled_agents).toEqual([]);
   });
 
-  test('rejects auto image routing when final config disables Observer', () => {
+  test('warns but preserves config when final auto routing disables Observer', () => {
     const userConfigPath = path.join(userConfigDir, 'opencode');
     const projectDir = path.join(tempDir, 'project');
     const projectConfigDir = path.join(projectDir, '.opencode');
@@ -118,10 +118,23 @@ describe('loadPluginConfig', () => {
     );
     fs.writeFileSync(
       path.join(projectConfigDir, 'oh-my-opencode-slim.json'),
-      JSON.stringify({ disabled_agents: ['observer'] }),
+      JSON.stringify({
+        autoUpdate: false,
+        disabled_agents: ['observer'],
+      }),
     );
 
-    expect(loadPluginConfig(projectDir, { silent: true })).toEqual({});
+    const warnings: ConfigLoadWarning[] = [];
+    const config = loadPluginConfig(projectDir, {
+      silent: true,
+      onWarning: (warning) => warnings.push(warning),
+    });
+    expect(config.image_routing).toBe('auto');
+    expect(config.autoUpdate).toBe(false);
+    expect(warnings).toHaveLength(1);
+    expect(warnings[0]?.message).toContain(
+      'image_routing "auto" requires observer to be enabled',
+    );
   });
 
   test('ignores invalid config (schema violation or malformed JSON)', () => {

+ 5 - 9
src/config/loader.ts

@@ -358,15 +358,11 @@ export function loadPluginConfig(
     };
   }
 
-  if (
-    !validateFinalImageRouting(
-      config,
-      projectConfigPath ?? userConfigPath ?? '',
-      options,
-    )
-  ) {
-    return {};
-  }
+  validateFinalImageRouting(
+    config,
+    projectConfigPath ?? userConfigPath ?? '',
+    options,
+  );
 
   return config;
 }

+ 5 - 1
src/hooks/image-hook.test.ts

@@ -143,15 +143,19 @@ describe('processImageAttachments image routing', () => {
     const message = makeUserMsg([
       { type: 'image', url: 'https://example.com/image.png' },
     ]);
+    const logs: string[] = [];
     processImageAttachments({
       messages: [message],
       workDir: path.join(TEST_DIR, 'unsaved'),
       imageRouting: 'auto',
       disabledAgents: new Set<string>(),
-      log: () => {},
+      log: (message) => logs.push(message),
     });
     expect(imagePartCount(message)).toBe(1);
     expect(message.parts).toHaveLength(1);
+    expect(logs.some((message) => message.includes('[image-routing]'))).toBe(
+      false,
+    );
   });
 
   it('strips only attachments saved successfully', () => {

+ 6 - 7
src/hooks/image-hook.ts

@@ -257,13 +257,6 @@ export function processImageAttachments(args: {
       }
     }
 
-    const pathsText =
-      savedPaths.length > 0 ? ` Saved to: ${savedPaths.join(', ')}` : '';
-    log(`[image-hook] saved image/file parts to disk${pathsText}`);
-    log(
-      `[image-routing] auto mode: intercepted ${savedImageParts.size} image(s), delegating to @observer`,
-    );
-
     // If no image could be saved, do not strip the parts: the orchestrator
     // would receive a nudge with no usable path and the bytes would be lost.
     if (savedPaths.length === 0) {
@@ -271,6 +264,12 @@ export function processImageAttachments(args: {
       continue;
     }
 
+    const pathsText = ` Saved to: ${savedPaths.join(', ')}`;
+    log(`[image-hook] saved image/file parts to disk${pathsText}`);
+    log(
+      `[image-routing] auto mode: intercepted ${savedImageParts.size} image(s), delegating to @observer`,
+    );
+
     msg.parts = msg.parts
       .filter((p) => !savedImageParts.has(p as ImagePart))
       .concat([