@bash0816/copilot-termux 1.0.63 → 1.0.65

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.
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "wrapperVersion": "2.0.0",
3
3
  "copilot": {
4
- "version": "1.0.63",
5
- "integrity": "sha512-e8DRYiWJQc4kepVXsXjC8vpDU2FXS/TfR+Z6p/KAojfcwIUZzKMAfCV5D1lD25hV4CryVH1Z9t7mHqChickj0Q=="
4
+ "package": "@github/copilot-linuxmusl-arm64",
5
+ "version": "1.0.64",
6
+ "integrity": "sha512-C+EYoMvmlUxR0YYxLkD3nwn940y0zId8z+pPl9rFO6f9heMGXYCwCZL2i2c3GW6CvGgZF6Wbzw1Kk0Gvw46F2w=="
6
7
  }
7
8
  }
Binary file
@@ -19,6 +19,10 @@ const Module = require('module');
19
19
  const fs = require('fs');
20
20
  const path = require('path');
21
21
 
22
+ const _pkgVersion = (() => {
23
+ try { return require(path.join(__dirname, '..', 'package.json')).version; } catch (_) { return '1.0.65'; }
24
+ })();
25
+
22
26
  // Bundled Termux-native pty.node (built against bionic, not glibc).
23
27
  const NATIVE_PTY = path.join(__dirname, 'native', 'pty.node');
24
28
 
@@ -58,19 +62,86 @@ Module._load = function (request, parent, isMain) {
58
62
  const result = origLoad(request, parent, isMain);
59
63
  if (typeof request === 'string' &&
60
64
  path.basename(request) === 'runtime.node') {
65
+ if (result.__copilotTermuxPatched) return result;
66
+ result.__copilotTermuxPatched = true;
61
67
  // Rust tokio を使う関数群を no-op に差し替え。
62
68
  // sessionStore*/sessionSqlite* は非同期 SQLite (tokio)、
63
69
  // modelHttp*/networkFetch*/ahpRelay*/websocketResponses* は Rust HTTP (tokio)。
70
+ // jsonrpcServer* は拡張 JSON-RPC サーバー (ThreadsafeFunction)、
71
+ // lspClient* は LSP クライアント (ThreadsafeFunction)。
64
72
  // featureFlagService* は同期 Rust のため除外(no-op にすると .handle クラッシュ)。
65
- const TOKIO_PATTERN = /^(modelHttp|networkFetch|ahpRelay|websocketResponses|sessionStore|sessionSqlite)/;
73
+ const TOKIO_PATTERN = /^(modelHttp|networkFetch|ahpRelay|websocketResponses|sessionStore|sessionSqlite|jsonrpcServer|lspClient)/;
66
74
  for (const key of Object.keys(result)) {
67
75
  if (TOKIO_PATTERN.test(key) && typeof result[key] === 'function') {
68
76
  result[key] = () => undefined;
69
77
  }
70
78
  }
79
+ // git*Async: Rust tokio async functions — type-safe stubs to prevent SIGSEGV.
80
+ // Returns empty/null values matching what app.js callers expect.
81
+ const GIT_ASYNC_STUBS = {
82
+ gitMutateAsync: async () => undefined,
83
+ gitCommandAsync: async () => '',
84
+ gitRemotesAsync: async () => [],
85
+ gitDiffFileAsync: async () => '',
86
+ gitHashFileAsync: async () => [],
87
+ gitMergeBaseAsync: async () => null,
88
+ gitDiffForRefAsync: async () => '',
89
+ gitCurrentBranchAsync: async () => null,
90
+ gitDefaultBranchAsync: async () => null,
91
+ gitListWorktreesAsync: async () => [],
92
+ gitBranchAndHeadAsync: async () => null,
93
+ gitSubmodulePathsAsync: async () => [],
94
+ gitUntrackedPathsAsync: async () => [],
95
+ gitDiffNameStatusAsync: async () => '',
96
+ gitStatusPorcelainAsync: async () => '',
97
+ gitWorkingTreeStatusAsync: async () => ({ hasUnstagedChanges: false, hasStagedChanges: false, hasUntrackedFiles: false }),
98
+ gitStatusPorcelainAllAsync: async () => null,
99
+ gitCurrentBranchRemoteAsync: async () => null,
100
+ gitWorkingTreeDiffStatsAsync: async () => ({ linesAdded: 0, linesRemoved: 0 }),
101
+ };
102
+ for (const [key, stub] of Object.entries(GIT_ASYNC_STUBS)) {
103
+ if (typeof result[key] === 'function') result[key] = stub;
104
+ }
105
+ if (typeof result.registerLogSink === 'function') {
106
+ result.registerLogSink = () => { throw new Error('[copilot-termux] registerLogSink disabled on bionic (no tokio thread)'); };
107
+ }
71
108
  if (typeof result.networkFetchGetExtraCaPems === 'function') {
72
109
  result.networkFetchGetExtraCaPems = () => ({ errors: [], pems: [] });
73
110
  }
111
+ // modelsFilterToPicker: native-first。native が空のとき(model_picker_enabled=true のモデルが
112
+ // 全くない場合)のみ全インデックス fallback。
113
+ // free アカウントでは全モデルが model_picker_enabled=false → native 空 → fallback で全通し
114
+ //(auto モードが enterprise 向けモデルを選んで 400 になる副作用は AUTH-001 解決後に再評価)。
115
+ // enterprise アカウントでは native が正しくフィルタする。
116
+ if (typeof result.modelsFilterToPicker === 'function') {
117
+ const _nativeModelsFilterToPicker = result.modelsFilterToPicker;
118
+ result.modelsFilterToPicker = function(modelsJson) {
119
+ const nativeResult = _nativeModelsFilterToPicker(modelsJson);
120
+ _dbg('modelsFilterToPicker', { nativeResult, modelCount: (() => { try { return JSON.parse(modelsJson).length; } catch(_) { return -1; } })() });
121
+ if (Array.isArray(nativeResult) && nativeResult.length > 0) return nativeResult;
122
+ try {
123
+ const models = JSON.parse(modelsJson);
124
+ return Array.isArray(models) ? models.map((_, i) => i) : [];
125
+ } catch(_) { return []; }
126
+ };
127
+ }
128
+ // authGetCopilotApiUrl: type=token/env/user/gh-cli/api-key では native が null を返す。
129
+ // _b() はこれを見て models=[] を返しモデル選択が "No supported model" になる。
130
+ // OAuth token でも標準 copilot API URL は固定のため、null 時はデフォルト URL を返す。
131
+ if (typeof result.authGetCopilotApiUrl === 'function') {
132
+ const _nativeGetCopilotApiUrl = result.authGetCopilotApiUrl;
133
+ result.authGetCopilotApiUrl = function(authInfoJson, token) {
134
+ const r = _nativeGetCopilotApiUrl(authInfoJson, token);
135
+ if (r != null) return r;
136
+ try {
137
+ const info = JSON.parse(authInfoJson);
138
+ if (info && info.type !== 'hmac') {
139
+ return process.env.COPILOT_API_URL || 'https://api.githubcopilot.com';
140
+ }
141
+ } catch (_) {}
142
+ return r;
143
+ };
144
+ }
74
145
  // capiClientListModels を Node.js fetch で実装(Rust tokio SIGSEGV 回避)
75
146
  if (typeof result.capiClientListModels === 'function') {
76
147
  result.capiClientListModels = async function(handle, _includeHidden, _skipCache, _applyModelLimitCaps, _networkingConfigId) {
@@ -84,7 +155,8 @@ Module._load = function (request, parent, isMain) {
84
155
  } catch (e) {
85
156
  throw new Error(JSON.stringify({kind: 'network', message: `prepareHeaders failed: ${e.message}`}));
86
157
  }
87
- const baseUrl = 'https://api.githubcopilot.com';
158
+ const baseUrl = process.env.COPILOT_API_URL || 'https://api.githubcopilot.com';
159
+ _dbg('capiClientListModels:fetch', { baseUrl });
88
160
  let res;
89
161
  try {
90
162
  res = await globalThis.fetch(`${baseUrl}/models`, {method: 'GET', headers: authHeaders});
@@ -315,6 +387,7 @@ Module._load = function (request, parent, isMain) {
315
387
  if (typeof result.anthropicMessageStreamAccumulatorFinish === 'function') {
316
388
  result.anthropicMessageStreamAccumulatorFinish = function(id) {
317
389
  const acc = _jsAccs.get(id) || { message: null };
390
+ _dbg('accumulatorFinish', { id, message: acc.message ? { stop_reason: acc.message.stop_reason, contentTypes: (acc.message.content||[]).map(b=>b.type+':'+(b.text||'').slice(0,30)) } : null });
318
391
  _jsAccs.delete(id);
319
392
  return { json: JSON.stringify({ message: acc.message }) };
320
393
  };
@@ -330,6 +403,7 @@ Module._load = function (request, parent, isMain) {
330
403
  // 4. modelHttpStreamStart → fetch full SSE body, parse events
331
404
  result.modelHttpStreamStart = async function(jsonArg) {
332
405
  const req = JSON.parse(jsonArg);
406
+ _dbg('modelHttpStreamStart', { url: req.url, method: req.method });
333
407
  let body = req.body;
334
408
  if (body !== null && body !== undefined && typeof body === 'object') {
335
409
  if (body.type === 'Buffer' && Array.isArray(body.data)) {
@@ -355,12 +429,42 @@ Module._load = function (request, parent, isMain) {
355
429
  return { json: JSON.stringify({ bodyText, status: res.status, statusText: res.statusText, headers, streamId: null }) };
356
430
  }
357
431
  const events = _parseAnthropicSSE(bodyText);
432
+ _dbg('modelHttpStreamStart:parsed', { status: res.status, eventCount: events.length, bodySnippet: bodyText.slice(0, 300) });
358
433
  const finalMessage = _reconstructFinalMessage(events);
359
434
  const streamId = 'js-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2);
360
435
  _jsStreams.set(streamId, { events, index: 0, finalMessage });
361
436
  return { json: JSON.stringify({ bodyText: null, status: res.status, statusText: res.statusText, headers, streamId }) };
362
437
  };
363
438
 
439
+ // Helper: convert Anthropic SSE event → chunkContext for processAnthropicStreamingChunkContext
440
+ function _toChunkContext(event, streamId) {
441
+ const base = { content: '', size: 0, chunkBoundary: false, messageStart: false, streamingId: streamId };
442
+ if (!event || !event.type) return base;
443
+ switch (event.type) {
444
+ case 'message_start':
445
+ return { ...base, messageStart: true };
446
+ case 'content_block_delta': {
447
+ const d = event.delta;
448
+ if (!d) return base;
449
+ if (d.type === 'text_delta') {
450
+ const t = d.text || '';
451
+ return { ...base, content: t, size: Buffer.byteLength(t, 'utf8') };
452
+ }
453
+ if (d.type === 'thinking_delta') {
454
+ const r = d.thinking || '';
455
+ return { ...base, reasoningContent: r, size: Buffer.byteLength(r, 'utf8') };
456
+ }
457
+ return base;
458
+ }
459
+ case 'content_block_stop':
460
+ case 'message_delta':
461
+ case 'message_stop':
462
+ return { ...base, chunkBoundary: true };
463
+ default:
464
+ return base;
465
+ }
466
+ }
467
+
364
468
  // 5. modelHttpStreamNextAnthropicMessageEvent → yield events one by one
365
469
  result.modelHttpStreamNextAnthropicMessageEvent = async function(streamId, accId) {
366
470
  const st = _jsStreams.get(streamId);
@@ -372,7 +476,10 @@ Module._load = function (request, parent, isMain) {
372
476
  }
373
477
  return null;
374
478
  }
375
- return { json: JSON.stringify({ kind: 'ok', processResult: { event: st.events[st.index++] } }) };
479
+ const event = st.events[st.index++];
480
+ const chunkContext = _toChunkContext(event, streamId);
481
+ const tokenEvent = event.type === 'content_block_delta' && !!(event.delta && event.delta.type === 'text_delta');
482
+ return { json: JSON.stringify({ kind: 'ok', processResult: { event, chunkContext, tokenEvent, copilotUsage: null } }) };
376
483
  };
377
484
 
378
485
  // 6. modelHttpStreamCancel → cleanup
@@ -417,6 +524,7 @@ Module._load = function (request, parent, isMain) {
417
524
  if (!st) {
418
525
  throw new Error(`Native mod HTTP stream was not found: ${streamId}`);
419
526
  }
527
+ _dbg('responsesStreamDrive:start', { streamId, eventCount: st.events.length, hasProcessors, sample: st.events.slice(0,2) });
420
528
  _jsStreams.delete(streamId);
421
529
  let copilotUsage = null;
422
530
  for (const event of st.events) {
@@ -427,6 +535,7 @@ Module._load = function (request, parent, isMain) {
427
535
  if (parsed && parsed.copilotUsage !== undefined && parsed.copilotUsage !== null)
428
536
  copilotUsage = parsed.copilotUsage;
429
537
  const cc = parsed && parsed.chunkContext;
538
+ _dbg('responsesStreamDrive:chunk', { eventType: event.type, cc: cc ? { content: cc.content, size: cc.size, messageStart: !!cc.messageStart, chunkBoundary: !!cc.chunkBoundary } : null });
430
539
  if (hasProcessors && typeof onChunkCallback === 'function' && cc &&
431
540
  (cc.content || cc.messageStart || cc.reportIntentArguments || cc.chunkBoundary || cc.size > 0)) {
432
541
  try { onChunkCallback(JSON.stringify(cc)); } catch (_) {}
@@ -434,14 +543,459 @@ Module._load = function (request, parent, isMain) {
434
543
  }
435
544
  return { json: JSON.stringify({ kind: 'ok', copilotUsage, ttftMs: null, interTokenLatencyMs: null }) };
436
545
  };
546
+ // === authManager* JS stubs (1.0.64: tokio thread spawn → SIGSEGV on bionic) ===
547
+ const _authMgr = new Map(); // uuid → { cachedInfo, pendingInfo, cachedToken, cachedHost, gen }
548
+
549
+ result.authManagerCreate = function(uuid, hostUri, userAgent, path, normSpec, header, envVar, disableAutoLogin) {
550
+ _authMgr.set(uuid, { cachedInfo: null, pendingInfo: null, cachedToken: null, cachedHost: null, gen: 0 });
551
+ // native 非呼び出し: tokio runtime 生成を阻止
552
+ };
553
+
554
+ async function _buildAuthInfo(token, hostUri) {
555
+ let login = null;
556
+ let copilotUser = null;
557
+ _dbg('buildAuthInfo:start', { tokenHash: _tokenHash(token), hostUri });
558
+ try {
559
+ const apiHost = hostUri.replace('https://github.com', 'https://api.github.com');
560
+ const res = await globalThis.fetch(`${apiHost}/user`, {
561
+ headers: { Authorization: `token ${token}`, 'User-Agent': `copilot-termux/${_pkgVersion}` },
562
+ signal: AbortSignal.timeout(5000),
563
+ });
564
+ if (res.ok) login = (await res.json()).login;
565
+ _dbg('buildAuthInfo:/user', { status: res.status, login });
566
+ } catch (e) {
567
+ _dbg('buildAuthInfo:/user:error', { err: e.message });
568
+ }
569
+ // copilot_internal/user から copilotUser 全体を取得して authInfo に含める。
570
+ // app.js の Wa(authInfo) は authInfo.copilotUser.endpoints.api から CAPI base URL を導く。
571
+ // copilotUser: null のままでは is_mcp_enabled・quota・plan 情報も失われる。
572
+ // env var は後続の capiClientListModels stub / authGetCopilotApiUrl stub でも参照するため並記する。
573
+ try {
574
+ const apiHost = hostUri.replace('https://github.com', 'https://api.github.com');
575
+ const r = await globalThis.fetch(`${apiHost}/copilot_internal/user`, {
576
+ headers: { Authorization: `token ${token}`, 'User-Agent': `copilot-termux/${_pkgVersion}`, 'Copilot-Integration-Id': 'copilot-chat' },
577
+ signal: AbortSignal.timeout(5000),
578
+ });
579
+ if (r.ok) {
580
+ const info = await r.json();
581
+ copilotUser = info;
582
+ const apiUrl = info?.endpoints?.api;
583
+ if (apiUrl && typeof apiUrl === 'string') process.env.COPILOT_API_URL = apiUrl;
584
+ _dbg('buildAuthInfo:/copilot_internal/user', {
585
+ status: r.status,
586
+ access_type_sku: info?.access_type_sku ?? null,
587
+ copilot_plan: info?.copilot_plan ?? null,
588
+ endpoints_api: apiUrl ?? null,
589
+ COPILOT_API_URL: process.env.COPILOT_API_URL ?? null,
590
+ });
591
+ } else {
592
+ _dbg('buildAuthInfo:/copilot_internal/user', { status: r.status, ok: false });
593
+ }
594
+ } catch (e) {
595
+ _dbg('buildAuthInfo:/copilot_internal/user:error', { err: e.message });
596
+ }
597
+ return JSON.stringify({
598
+ authInfo: { type: 'token', host: hostUri, token, login, copilotUser },
599
+ token,
600
+ });
601
+ }
602
+
603
+ async function _resolveOrCache(uuid, token, env) {
604
+ const entry = _authMgr.get(uuid);
605
+ if (!entry) return null;
606
+ const hostUri = ((typeof result.githubGetUri === 'function'
607
+ ? result.githubGetUri(
608
+ (env && env.COPILOT_GH_HOST) || undefined,
609
+ (env && env.GH_HOST) || undefined
610
+ )
611
+ : null) || 'https://github.com').replace(/\/+$/, '');
612
+ if (entry.cachedInfo !== null && (entry.cachedToken !== token || entry.cachedHost !== hostUri)) {
613
+ _dbg('resolveOrCache:cache-invalidate', {
614
+ reason: entry.cachedToken !== token ? 'token-changed' : 'host-changed',
615
+ oldTokenHash: _tokenHash(entry.cachedToken), newTokenHash: _tokenHash(token),
616
+ });
617
+ entry.cachedInfo = null;
618
+ entry.pendingInfo = null;
619
+ }
620
+ if (entry.cachedInfo !== null) {
621
+ _dbg('resolveOrCache:cache-hit', { tokenHash: _tokenHash(token) });
622
+ return entry.cachedInfo;
623
+ }
624
+ if (!entry.pendingInfo) {
625
+ const gen = entry.gen;
626
+ entry.pendingInfo = _buildAuthInfo(token, hostUri).then(info => {
627
+ if (entry.gen !== gen) return info; // stale: a newer switch superseded this fetch
628
+ entry.cachedInfo = info;
629
+ entry.cachedToken = token;
630
+ entry.cachedHost = hostUri;
631
+ entry.pendingInfo = null;
632
+ try {
633
+ const parsed = JSON.parse(info);
634
+ if (parsed && parsed.authInfo &&
635
+ typeof parsed.authInfo.login === 'string' && parsed.authInfo.login.length > 0) {
636
+ _loginTokens.set(`${hostUri}:${parsed.authInfo.login}`, token);
637
+ }
638
+ } catch (_) {}
639
+ return info;
640
+ }).catch(err => {
641
+ if (entry.gen === gen) entry.pendingInfo = null;
642
+ throw err;
643
+ });
644
+ }
645
+ return entry.pendingInfo;
646
+ }
647
+
648
+ result.authManagerLoadAuthInfo = async function(uuid, env, _storeTokenPlaintext) {
649
+ const token = await _readGhToken(env);
650
+ if (!token) return null;
651
+ return _resolveOrCache(uuid, token, env);
652
+ };
653
+ result.authManagerGetCurrentAuthInfo = async function(uuid, env, _storeTokenPlaintext) {
654
+ const entry = _authMgr.get(uuid);
655
+ if (!entry) return null;
656
+ if (entry.cachedInfo !== null) return entry.cachedInfo;
657
+ if (entry.pendingInfo) return entry.pendingInfo;
658
+ const token = await _readGhToken(env);
659
+ if (!token) return null;
660
+ return _resolveOrCache(uuid, token, env);
661
+ };
662
+ result.authManagerGetAllAuthAvailable = async function(uuid, env, _storeTokenPlaintext) {
663
+ const token = await _readGhToken(env);
664
+ if (!token) return [];
665
+ const info = await _resolveOrCache(uuid, token, env);
666
+ return info ? [info] : [];
667
+ };
668
+ result.authManagerGetLastAuthErrors = function(uuid) { return []; };
669
+ result.authManagerClearCache = function(uuid) {
670
+ const e = _authMgr.get(uuid);
671
+ if (e) { e.gen++; e.cachedInfo = null; e.pendingInfo = null; e.cachedToken = null; e.cachedHost = null; }
672
+ };
673
+ result.authManagerSwitchToAuth = async function(uuid, authInfoJson, token) {
674
+ const entry = _authMgr.get(uuid);
675
+ if (!entry) return;
676
+ // Clear stale enterprise endpoint and cache regardless of token presence
677
+ delete process.env.COPILOT_API_URL;
678
+ const gen = ++entry.gen;
679
+ entry.cachedInfo = null;
680
+ entry.cachedToken = null;
681
+ entry.cachedHost = null;
682
+ entry.pendingInfo = null;
683
+ if (!token) return;
684
+ const hostUri = (() => {
685
+ try { return (JSON.parse(authInfoJson)?.host || 'https://github.com').replace(/\/+$/, ''); }
686
+ catch (_) { return 'https://github.com'; }
687
+ })();
688
+ entry.pendingInfo = _buildAuthInfo(token, hostUri).then(info => {
689
+ if (entry.gen !== gen) return info; // stale: a newer switch superseded this fetch
690
+ entry.cachedInfo = info;
691
+ entry.cachedToken = token;
692
+ entry.cachedHost = hostUri;
693
+ entry.pendingInfo = null;
694
+ return info;
695
+ }).catch(err => {
696
+ if (entry.gen === gen) entry.pendingInfo = null;
697
+ _dbg('authManagerSwitchToAuth:error', { err: err.message });
698
+ });
699
+ await entry.pendingInfo;
700
+ _dbg('authManagerSwitchToAuth:done', {
701
+ tokenHash: _tokenHash(token), hostUri,
702
+ COPILOT_API_URL_after: process.env.COPILOT_API_URL ?? null,
703
+ });
704
+ };
705
+ result.authManagerLoginUser = async function(uuid, host, login, token) {
706
+ _dbg('authManagerLoginUser:start', { host, login, tokenHash: _tokenHash(token) });
707
+ const entry = _authMgr.get(uuid);
708
+ if (!entry || !token) return;
709
+ const hostUri = (host || 'https://github.com').replace(/\/+$/, '');
710
+ _loginTokens.set(`${hostUri}:${login || ''}`, token);
711
+ const gen = ++entry.gen;
712
+ entry.pendingInfo = _buildAuthInfo(token, hostUri).then(info => {
713
+ if (entry.gen !== gen) return info; // stale: a newer switch superseded this fetch
714
+ entry.cachedInfo = info;
715
+ entry.cachedToken = token;
716
+ entry.cachedHost = hostUri;
717
+ entry.pendingInfo = null;
718
+ try {
719
+ const parsed = JSON.parse(info);
720
+ if (parsed && parsed.authInfo &&
721
+ typeof parsed.authInfo.login === 'string' && parsed.authInfo.login.length > 0) {
722
+ _loginTokens.set(`${hostUri}:${parsed.authInfo.login}`, token);
723
+ }
724
+ } catch (_) {}
725
+ return info;
726
+ }).catch(err => { if (entry.gen === gen) entry.pendingInfo = null; return null; });
727
+ await entry.pendingInfo; // ensure cachedInfo is set before app.js continues
728
+ };
729
+ result.authManagerLogout = async function(uuid, authInfoJson) {
730
+ const e = _authMgr.get(uuid);
731
+ if (e) { e.cachedInfo = null; e.pendingInfo = null; e.cachedToken = null; e.cachedHost = null; }
732
+ return true;
733
+ };
734
+ result.authManagerRefreshCopilotUser = async function(uuid) {
735
+ return null; // null → JS側 authInfoWithTokenPromise フォールバック
736
+ };
737
+ result.authManagerDestroy = function(uuid) { _authMgr.delete(uuid); };
738
+
739
+ result.authResolveAuthInfoFromToken = async function(token, hostUri, skipCache, userAgent) {
740
+ if (!token) return JSON.stringify(null);
741
+ let login = null;
742
+ try {
743
+ const apiHost = (hostUri || 'https://github.com').replace('://github.com', '://api.github.com');
744
+ const res = await globalThis.fetch(`${apiHost}/user`, {
745
+ headers: { Authorization: `token ${token}`, 'User-Agent': userAgent || `copilot-termux/${_pkgVersion}` },
746
+ signal: AbortSignal.timeout(5000),
747
+ });
748
+ if (res.ok) login = (await res.json()).login;
749
+ } catch (_) {}
750
+ const host = hostUri || 'https://github.com';
751
+ return JSON.stringify({ type: 'token', host, token, login, copilotUser: null });
752
+ };
753
+ // === end authManager* stubs ===
754
+ // === tokenStore* JS stubs (bionic: tokio ThreadsafeFunction crash) ===
755
+ // Verified tokens: set by authManagerLoginUser / _resolveOrCache after /user API check
756
+ const _loginTokens = new Map(); // "host:login" → oauthToken
757
+
758
+ const _tokStore = new Map();
759
+ let _tokSeq = 9e6;
760
+ result.tokenStoreCreate = function() { const id = ++_tokSeq; _tokStore.set(id, new Map()); return id; };
761
+ result.tokenStoreDestroy = function(h) { _tokStore.delete(h); };
762
+ result.tokenStoreGetToken = async function(h, host, login) {
763
+ const m = _tokStore.get(h);
764
+ const key = `${(host || 'https://github.com').replace(/\/+$/, '')}:${login || ''}`;
765
+ if (m) { const v = m.get(key); if (v != null) return v; }
766
+ const lt = _loginTokens.get(key);
767
+ if (lt != null) { if (m) m.set(key, lt); return lt; }
768
+ if (login) return null; // login specified: refuse unverified fallback
769
+ return _readGhToken(null);
770
+ };
771
+ result.tokenStoreStoreToken = function(h, token, host, login) {
772
+ const m = _tokStore.get(h);
773
+ if (m && token) m.set(`${(host || 'https://github.com').replace(/\/+$/, '')}:${login || ''}`, token);
774
+ };
775
+ result.tokenStoreRemoveToken = function(h, host, login) {
776
+ const m = _tokStore.get(h);
777
+ if (m) m.delete(`${(host || 'https://github.com').replace(/\/+$/, '')}:${login || ''}`);
778
+ };
779
+ result.tokenStoreGetAnyToken = async function(h) {
780
+ const m = _tokStore.get(h);
781
+ if (m && m.size > 0) return [...m.values()][0];
782
+ return _readGhToken(null);
783
+ };
784
+ result.tokenStoreStoreCurrentTokenInConfig = async function() {};
785
+ // === end tokenStore* stubs ===
786
+
787
+ // === urlManager* JS stubs ===
788
+ const _urlMgr = new Map();
789
+ let _urlSeq = 8e6;
790
+ result.urlManagerCreate = function(urls, unrestricted) {
791
+ const id = ++_urlSeq;
792
+ _urlMgr.set(id, { urls: Array.isArray(urls) ? [...urls] : [], unrestricted: !!unrestricted });
793
+ return id;
794
+ };
795
+ result.urlManagerAddUrl = function(h, url) { const e = _urlMgr.get(h); if (e) e.urls.push(url); };
796
+ result.urlManagerDispose = function(h) { _urlMgr.delete(h); };
797
+ result.urlManagerGetUrls = function(h) { return (_urlMgr.get(h) || {}).urls || []; };
798
+ result.urlManagerIsUrlAllowed = function(h, url) { return true; };
799
+ result.urlManagerIsUnrestrictedMode = function(h) { return true; };
800
+ result.urlManagerSetUnrestrictedMode = function(h, v) { const e = _urlMgr.get(h); if (e) e.unrestricted = v; };
801
+ // === end urlManager* stubs ===
802
+
803
+ // === pathManager* JS stubs ===
804
+ const _pathMgr = new Map();
805
+ let _pathSeq = 7e6;
806
+ result.pathManagerCreateRestricted = async function(dirs, primary) {
807
+ const id = ++_pathSeq;
808
+ _pathMgr.set(id, { dirs: Array.isArray(dirs) ? [...dirs] : [], primary: primary || null });
809
+ return id;
810
+ };
811
+ result.pathManagerCreateUnrestricted = async function(primary) {
812
+ const id = ++_pathSeq;
813
+ _pathMgr.set(id, { dirs: [], primary: primary || null });
814
+ return id;
815
+ };
816
+ result.pathManagerDispose = function(h) { _pathMgr.delete(h); };
817
+ result.pathManagerAddDirectory = function(h, dir) { const e = _pathMgr.get(h); if (e) e.dirs.push(dir); };
818
+ result.pathManagerGetDirectories = function(h) { return (_pathMgr.get(h) || {}).dirs || []; };
819
+ result.pathManagerGetPrimaryDirectory = function(h) { return (_pathMgr.get(h) || {}).primary || null; };
820
+ result.pathManagerIsPathWithinWorkspace = function(h, p) {
821
+ const e = _pathMgr.get(h);
822
+ if (!e || !e.primary) return true;
823
+ return p === e.primary || p.startsWith(e.primary + '/');
824
+ };
825
+ result.pathManagerUpdatePrimaryDirectory = function(h, dir) { const e = _pathMgr.get(h); if (e) e.primary = dir; };
826
+ result.pathManagerIsPathWithinAllowedDirectories = function(h, p) {
827
+ const e = _pathMgr.get(h);
828
+ if (!e || e.dirs.length === 0) return true;
829
+ return e.dirs.some(dir => p === dir || p.startsWith(dir + '/'));
830
+ };
831
+ // === end pathManager* stubs ===
832
+
833
+ // === telemetryQueue* JS stubs ===
834
+ let _telSeq = 6e6;
835
+ result.telemetryQueueCreate = function(name, _config) { return ++_telSeq; };
836
+ result.telemetryQueueDispose = function(h) {};
837
+ result.telemetryQueueEnqueue = function(h, event) {};
838
+ result.telemetryQueueSetDebugLogPayload = function(h, v) {};
839
+ // === end telemetryQueue* stubs ===
840
+
841
+ // === telemetryAppInsightsServiceState* JS stubs (1.0.65+: Azure AppInsights HTTP I/O → tokio SIGSEGV on bionic) ===
842
+ if (typeof result.telemetryAppInsightsServiceStateCreate === 'function') {
843
+ result.telemetryAppInsightsServiceStateCreate = function() { return ++_telSeq; };
844
+ result.telemetryAppInsightsServiceStateDispose = function(h) {};
845
+ result.telemetryAppInsightsServiceStateEnqueue = function(h, e, n, r) { return JSON.stringify({}); };
846
+ result.telemetryAppInsightsServiceStateAuthSucceeded = function(h, e) { return JSON.stringify({}); };
847
+ result.telemetryAppInsightsServiceStateLogout = function(h) { return JSON.stringify({}); };
848
+ }
849
+ // === end telemetryAppInsightsServiceState* stubs ===
850
+
851
+ // === telemetryDelegatingSender* JS stubs (1.0.65+: fire-and-forget HTTP send → tokio SIGSEGV on bionic) ===
852
+ if (typeof result.telemetryDelegatingSenderCreate === 'function') {
853
+ result.telemetryDelegatingSenderCreate = function() { return ++_telSeq; };
854
+ result.telemetryDelegatingSenderDispose = function(h) { return JSON.stringify({ disposeDelegate: false }); };
855
+ result.telemetryDelegatingSenderConfigure = function(h, hasDelegate) {
856
+ return JSON.stringify({ disposePreviousDelegate: false, disposeNewDelegate: false });
857
+ };
858
+ result.telemetryDelegatingSenderIsConfigured = function(h) { return false; };
859
+ result.telemetryDelegatingSenderRequiresDelegate = function(h) {};
860
+ result.telemetryDelegatingSenderSetInternalCorrelationIds = function(h, ids) {
861
+ return JSON.stringify({ applyToDelegate: false });
862
+ };
863
+ }
864
+ // === end telemetryDelegatingSender* stubs ===
865
+
866
+ // === telemetrySessionTelemetryState* JS stubs (1.0.65+) ===
867
+ if (typeof result.telemetrySessionTelemetryStateCreate === 'function') {
868
+ result.telemetrySessionTelemetryStateCreate = function(h, e) { return ++_telSeq; };
869
+ result.telemetrySessionTelemetryStateDispose = function(h) {};
870
+ result.telemetrySessionTelemetryStateSnapshot = function(h) { return JSON.stringify({ telemetryEvents: [] }); };
871
+ result.telemetrySessionTelemetryStateProcessSessionEvent = function(h, e, n) {
872
+ return JSON.stringify({ telemetryEvents: [] });
873
+ };
874
+ result.telemetrySessionTelemetryStateProcessToolsUpdated = function(h, e) {};
875
+ }
876
+ // === end telemetrySessionTelemetryState* stubs ===
877
+
878
+ // === telemetryLegacyUsageHandler* JS stubs (1.0.65+) ===
879
+ if (typeof result.telemetryLegacyUsageHandlerCreate === 'function') {
880
+ result.telemetryLegacyUsageHandlerCreate = function(h, e) { return ++_telSeq; };
881
+ result.telemetryLegacyUsageHandlerDispose = function(h) {};
882
+ result.telemetryLegacyUsageHandlerProcessEvent = function(h, e) { return JSON.stringify([]); };
883
+ }
884
+ // === end telemetryLegacyUsageHandler* stubs ===
885
+
886
+ // === permissionService* JS stubs ===
887
+ const _permSvc = new Map();
888
+ let _permSeq = 5e6;
889
+ result.permissionServiceCreate = function(config) {
890
+ const id = ++_permSeq;
891
+ _permSvc.set(id, { approveAll: !!(config && config.approveAllTool) });
892
+ return id;
893
+ };
894
+ result.permissionServiceDispose = function(h) { _permSvc.delete(h); };
895
+ result.permissionServiceRequest = async function(h, reqJson) {
896
+ return JSON.stringify({ kind: 'approved' });
897
+ };
898
+ result.permissionServiceComplete = function(h, token, ok, resultJson) {};
899
+ result.permissionServiceConfigure = function(h, approveAllTool, approveAllRead, approvedRules, deniedRules, pathMgr, urlMgr) {};
900
+ result.permissionServiceAddApprovedRules = function(h, rules) {};
901
+ result.permissionServiceGetApproveAllTool = function(h) { return (_permSvc.get(h) || {}).approveAll ?? false; };
902
+ result.permissionServiceSetApproveAllTool = function(h, v) { const e = _permSvc.get(h); if (e) e.approveAll = v; };
903
+ result.permissionServiceRemoveApprovedRules = function(h, rules) {};
904
+ result.permissionServiceCheckSamplingApproval = function(h, k) { return true; };
905
+ result.permissionServiceResetSessionApprovals = function(h) {};
906
+ result.permissionServiceAddLocationApprovedRules = function(h, rules) {};
907
+ result.permissionServiceRemoveLocationApprovedRules = function(h, rules) {};
908
+ // === end permissionService* stubs ===
909
+
910
+ // === lspManager* JS stubs ===
911
+ let _lspMgrSeq = 4e6;
912
+ result.lspManagerCreate = function() { return ++_lspMgrSeq; };
913
+ result.lspManagerClear = function(h) {};
914
+ result.lspManagerPlanForFile = function(h, file, lang, config) { return null; };
915
+ result.lspManagerRemoveClient = function(h, key) {};
916
+ result.lspManagerShutdownKeys = function(h) { return []; };
917
+ result.lspManagerPlanForServerId = function(h, id) { return null; };
918
+ result.lspManagerCachedClientCount = function(h) { return 0; };
919
+ result.lspManagerRelevantServerIds = function(h, file, lang) { return []; };
920
+ // === end lspManager* stubs ===
921
+
922
+ // === ifcEngine* JS stubs ===
923
+ const _ifc = new Map();
924
+ let _ifcSeq = 3e6;
925
+ result.ifcEngineCreate = function(config) { const id = ++_ifcSeq; _ifc.set(id, {}); return id; };
926
+ result.ifcEngineDispose = function(h) { _ifc.delete(h); };
927
+ result.ifcEngineToJson = function(h) { return '{}'; };
928
+ result.ifcEnginePreToolHook = async function(h, tool, argsJson, fetchHandler) { return { converged: true }; };
929
+ result.ifcEngineFetchComplete = function(h, resp) {};
930
+ result.ifcEngineGetContextLabel = function(h) { return null; };
931
+ result.ifcEngineSetContextLabel = function(h, label) {};
932
+ result.ifcEnginePostToolExecution = async function(h, tool, argsJson, fetchHandler) {
933
+ return { converged: true, applied: false, updated: false };
934
+ };
935
+ // === end ifcEngine* stubs ===
437
936
  // --- end JS model HTTP implementation ---
438
937
  }
938
+
939
+ // === cli-native.node stubs (1.0.64+: color scheme fns use Rust tokio → SIGSEGV on bionic) ===
940
+ if (typeof request === 'string' && path.basename(request) === 'cli-native.node') {
941
+ if (!result.__copilotTermuxCliPatched) {
942
+ result.__copilotTermuxCliPatched = true;
943
+ // Rust tokio background thread → SIGSEGV on bionic. Return null so callers
944
+ // fall back to "unspecified" color scheme (uses default theme).
945
+ if (typeof result.getColorScheme === 'function') {
946
+ result.getColorScheme = () => null;
947
+ }
948
+ if (typeof result.startColorSchemeListener === 'function') {
949
+ result.startColorSchemeListener = (_cb) => undefined;
950
+ }
951
+ if (typeof result.stopColorSchemeListener === 'function') {
952
+ result.stopColorSchemeListener = () => undefined;
953
+ }
954
+ }
955
+ }
956
+ // === end cli-native.node stubs ===
957
+
439
958
  return result;
440
959
  };
441
960
 
442
961
  // [Android bionic 対応] app.js の oPt() が globalThis.fetch を Rust ベースの B7 に
443
962
  // 差し替えるのを阻止する。linuxmusl-arm64/runtime.node の Rust ネットワークスタックは
444
963
  // bionic 上の実 I/O で動作しないため、Node.js ビルトイン fetch(動作確認済み)に固定する。
964
+ // GitHub OAuth token via env var or gh CLI (keychain unavailable on bionic)
965
+ // --- AUTH デバッグ計装 ---
966
+ // COPILOT_TERMUX_DEBUG_AUTH=1 を設定すると認証の各ステップを stderr に JSON で出力する。
967
+ // token はセキュリティのため先頭8文字のみ表示。
968
+ const _authDebug = process.env.COPILOT_TERMUX_DEBUG_AUTH === '1';
969
+ function _dbg(event, data) {
970
+ if (!_authDebug) return;
971
+ process.stderr.write('[copilot-termux-auth] ' + JSON.stringify({ event, ...data, ts: new Date().toISOString() }) + '\n');
972
+ }
973
+ function _tokenHash(t) { return t ? t.slice(0, 8) + '...' : null; }
974
+ // --- end 計装 ---
975
+
976
+ async function _readGhToken(env) {
977
+ const envToken =
978
+ (env && (env.GITHUB_TOKEN || env.GH_TOKEN || env.COPILOT_GITHUB_TOKEN)) ||
979
+ process.env.GITHUB_TOKEN || process.env.GH_TOKEN || process.env.COPILOT_GITHUB_TOKEN;
980
+ if (envToken) {
981
+ _dbg('readGhToken', { source: 'env', tokenHash: _tokenHash(envToken) });
982
+ return envToken;
983
+ }
984
+ try {
985
+ const { execFile } = require('child_process');
986
+ const ghToken = await new Promise(resolve => {
987
+ execFile('gh', ['auth', 'token'], { encoding: 'utf8', timeout: 5000 }, (err, stdout) => {
988
+ resolve(err ? null : (stdout.trim() || null));
989
+ });
990
+ });
991
+ _dbg('readGhToken', { source: ghToken ? 'gh-cli' : 'null', tokenHash: _tokenHash(ghToken) });
992
+ return ghToken;
993
+ } catch (_) {
994
+ _dbg('readGhToken', { source: 'error', tokenHash: null });
995
+ return null;
996
+ }
997
+ }
998
+
445
999
  const _nativeFetch = globalThis.fetch;
446
1000
  Object.defineProperty(globalThis, 'fetch', {
447
1001
  configurable: true,
package/lib/setup.js CHANGED
@@ -48,20 +48,20 @@ async function fetchWithIntegrity(url, expectedIntegrity) {
48
48
  }
49
49
 
50
50
  async function setup() {
51
- const { version, integrity } = manifest.copilot;
51
+ const { package: pkg = '@github/copilot-linuxmusl-arm64', version, integrity } = manifest.copilot;
52
52
  const versionDir = path.join(CACHE_DIR, version);
53
53
  const stagingDir = `${versionDir}.staging`;
54
54
 
55
55
  if (fs.existsSync(path.join(versionDir, 'index.js'))) {
56
56
  fs.rmSync(stagingDir, { recursive: true, force: true });
57
- console.log(`@github/copilot@${version} already installed, refreshing symlink...`);
57
+ console.log(`${pkg}@${version} already installed, refreshing symlink...`);
58
58
  } else {
59
- console.log(`Fetching @github/copilot@${version} metadata...`);
60
- const metaBuf = await httpsGet(`${REGISTRY}/@github/copilot/${version}`, { Accept: 'application/json' });
59
+ console.log(`Fetching ${pkg}@${version} metadata...`);
60
+ const metaBuf = await httpsGet(`${REGISTRY}/${pkg}/${version}`, { Accept: 'application/json' });
61
61
  const meta = JSON.parse(metaBuf.toString());
62
62
  const tarballUrl = meta.dist.tarball;
63
63
 
64
- console.log(`Downloading @github/copilot@${version}...`);
64
+ console.log(`Downloading ${pkg}@${version}...`);
65
65
  const tarball = await fetchWithIntegrity(tarballUrl, integrity);
66
66
 
67
67
  const tarballPath = path.join(CACHE_DIR, `copilot-${version}.tgz`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bash0816/copilot-termux",
3
- "version": "1.0.63",
3
+ "version": "1.0.65",
4
4
  "description": "GitHub Copilot CLI for Termux (Android aarch64)",
5
5
  "license": "GPL-3.0-only",
6
6
  "readme": "README.md",
@@ -9,16 +9,39 @@
9
9
  "url": "https://github.com/bash0816/Github-Copilot-Termux.git"
10
10
  },
11
11
  "homepage": "https://github.com/bash0816/Github-Copilot-Termux#readme",
12
- "keywords": ["termux", "android", "github-copilot", "copilot-cli", "aarch64"],
12
+ "keywords": [
13
+ "termux",
14
+ "android",
15
+ "github-copilot",
16
+ "copilot-cli",
17
+ "aarch64"
18
+ ],
13
19
  "bin": {
14
20
  "copilot": "bin/copilot",
15
21
  "copilot-termux": "bin/copilot-termux"
16
22
  },
17
23
  "scripts": {},
18
- "files": ["bin", "lib/platform-patch.js", "lib/setup.js", "lib/bionic-compat.so", "lib/native/pty.node", "scripts/bionic-compat.c", "config", "LICENSE", "THIRD-PARTY-NOTICES.md", "THIRD-PARTY-LICENSES/", "README.md"],
24
+ "files": [
25
+ "bin",
26
+ "lib/platform-patch.js",
27
+ "lib/setup.js",
28
+ "lib/bionic-compat.so",
29
+ "lib/native/pty.node",
30
+ "scripts/bionic-compat.c",
31
+ "config",
32
+ "LICENSE",
33
+ "THIRD-PARTY-NOTICES.md",
34
+ "THIRD-PARTY-LICENSES/",
35
+ "README.md"
36
+ ],
19
37
  "engines": {
20
38
  "node": ">=18"
21
39
  },
22
- "os": ["android", "linux"],
23
- "cpu": ["arm64"]
40
+ "os": [
41
+ "android",
42
+ "linux"
43
+ ],
44
+ "cpu": [
45
+ "arm64"
46
+ ]
24
47
  }
@@ -58,6 +58,7 @@ typedef float (*fn_ff)(float);
58
58
  static fn_ddd _pow;
59
59
  static fn_dd _log;
60
60
  static fn_dd _log2;
61
+ static fn_dd _exp2;
61
62
  static fn_ff _expf;
62
63
  static fn_ff _log10f;
63
64
  static fn_ff _sinf;
@@ -67,6 +68,7 @@ static void compat_init(void) {
67
68
  _pow = (fn_ddd)dlsym(RTLD_NEXT, "pow");
68
69
  _log = (fn_dd) dlsym(RTLD_NEXT, "log");
69
70
  _log2 = (fn_dd) dlsym(RTLD_NEXT, "log2");
71
+ _exp2 = (fn_dd) dlsym(RTLD_NEXT, "exp2");
70
72
  _expf = (fn_ff) dlsym(RTLD_NEXT, "expf");
71
73
  _log10f = (fn_ff) dlsym(RTLD_NEXT, "log10f");
72
74
  _sinf = (fn_ff) dlsym(RTLD_NEXT, "sinf");
@@ -77,6 +79,7 @@ static void compat_init(void) {
77
79
  double pow(double x, double y) { if (!_pow) abort(); return _pow(x, y); }
78
80
  double log(double x) { if (!_log) abort(); return _log(x); }
79
81
  double log2(double x) { if (!_log2) abort(); return _log2(x); }
82
+ double exp2(double x) { if (!_exp2) abort(); return _exp2(x); }
80
83
  float expf(float x) { if (!_expf) abort(); return _expf(x); }
81
84
  float log10f(float x) { if (!_log10f) abort(); return _log10f(x); }
82
85
  float sinf(float x) { if (!_sinf) abort(); return _sinf(x); }