opencode-pilot 0.16.4 → 0.16.5

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": "opencode-pilot",
3
- "version": "0.16.4",
3
+ "version": "0.16.5",
4
4
  "type": "module",
5
5
  "main": "plugin/index.js",
6
6
  "description": "Automation daemon for OpenCode - polls for work and spawns sessions",
@@ -525,19 +525,40 @@ export async function createSessionViaApi(serverUrl, directory, prompt, options
525
525
  messageBody.modelID = modelID;
526
526
  }
527
527
 
528
- const messageResponse = await fetchFn(messageUrl.toString(), {
529
- method: 'POST',
530
- headers: { 'Content-Type': 'application/json' },
531
- body: JSON.stringify(messageBody),
532
- });
528
+ // Use AbortController with timeout for the message POST
529
+ // The /session/{id}/message endpoint returns a chunked/streaming response
530
+ // that stays open until the agent completes. We only need to verify the
531
+ // request was accepted (2xx status), not wait for the full response.
532
+ const controller = new AbortController();
533
+ const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout
533
534
 
534
- if (!messageResponse.ok) {
535
- const errorText = await messageResponse.text();
536
- throw new Error(`Failed to send message: ${messageResponse.status} ${errorText}`);
535
+ try {
536
+ const messageResponse = await fetchFn(messageUrl.toString(), {
537
+ method: 'POST',
538
+ headers: { 'Content-Type': 'application/json' },
539
+ body: JSON.stringify(messageBody),
540
+ signal: controller.signal,
541
+ });
542
+
543
+ clearTimeout(timeoutId);
544
+
545
+ if (!messageResponse.ok) {
546
+ const errorText = await messageResponse.text();
547
+ throw new Error(`Failed to send message: ${messageResponse.status} ${errorText}`);
548
+ }
549
+
550
+ debug(`createSessionViaApi: sent message to session ${session.id}`);
551
+ } catch (abortErr) {
552
+ clearTimeout(timeoutId);
553
+ // AbortError is expected - we intentionally abort after verifying the request started
554
+ // The server accepted our message, we just don't need to wait for the response
555
+ if (abortErr.name === 'AbortError') {
556
+ debug(`createSessionViaApi: message request started for session ${session.id} (response aborted as expected)`);
557
+ } else {
558
+ throw abortErr;
559
+ }
537
560
  }
538
561
 
539
- debug(`createSessionViaApi: sent message to session ${session.id}`);
540
-
541
562
  return {
542
563
  success: true,
543
564
  sessionId: session.id,
@@ -352,7 +352,12 @@ export function resolveRepoForItem(source, item) {
352
352
  if (resolvedRepo) {
353
353
  return source.repos.includes(resolvedRepo) ? [resolvedRepo] : [];
354
354
  }
355
- // No repo template - return empty (can't match without item context)
355
+ // No repo template - if exactly one repo, use it as default
356
+ // (e.g., Linear issues don't have repo context, user explicitly configures one repo)
357
+ if (source.repos.length === 1) {
358
+ return source.repos;
359
+ }
360
+ // Multiple repos but can't match without item context
356
361
  return [];
357
362
  }
358
363
 
@@ -769,6 +769,46 @@ sources:
769
769
  assert.deepStrictEqual(resolveRepoForItem(source, filteredItem), []);
770
770
  });
771
771
 
772
+ test('single-repo allowlist uses repo as default when no template', async () => {
773
+ // Linear issues don't have repository context - when exactly one repo is configured,
774
+ // use it as the default for all items from that source
775
+ writeFileSync(configPath, `
776
+ sources:
777
+ - preset: linear/my-issues
778
+ repos:
779
+ - 0din-ai/odin
780
+ `);
781
+
782
+ const { loadRepoConfig, getSources, resolveRepoForItem } = await import('../../service/repo-config.js');
783
+ loadRepoConfig(configPath);
784
+ const source = getSources()[0];
785
+
786
+ // Linear items don't have repository field
787
+ const linearItem = { id: 'linear:abc123', title: 'Fix bug', state: { name: 'In Progress' } };
788
+ assert.deepStrictEqual(resolveRepoForItem(source, linearItem), ['0din-ai/odin'],
789
+ 'single-repo allowlist should use repo as default');
790
+ });
791
+
792
+ test('multi-repo allowlist returns empty when no template match', async () => {
793
+ // With multiple repos and no way to determine which one, return empty
794
+ writeFileSync(configPath, `
795
+ sources:
796
+ - preset: linear/my-issues
797
+ repos:
798
+ - org/repo-a
799
+ - org/repo-b
800
+ `);
801
+
802
+ const { loadRepoConfig, getSources, resolveRepoForItem } = await import('../../service/repo-config.js');
803
+ loadRepoConfig(configPath);
804
+ const source = getSources()[0];
805
+
806
+ // Can't determine which of the 2 repos to use
807
+ const linearItem = { id: 'linear:abc123', title: 'Fix bug' };
808
+ assert.deepStrictEqual(resolveRepoForItem(source, linearItem), [],
809
+ 'multi-repo allowlist should return empty when no template');
810
+ });
811
+
772
812
  test('github presets include semantic session names', async () => {
773
813
  writeFileSync(configPath, `
774
814
  sources: