open-research 1.0.0 → 1.1.0
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/dist/chunk-3GZIDCV2.js +185 -0
- package/dist/{chunk-TQSQRNX6.js → chunk-3KZN54JZ.js} +23 -34
- package/dist/{chunk-I5NVYKG7.js → chunk-4HCPHCC2.js} +4 -0
- package/dist/chunk-77Q5B5H7.js +27 -0
- package/dist/chunk-GVEVKDGV.js +68 -0
- package/dist/{chunk-ZUSIRA5S.js → chunk-HRVDYJEC.js} +1 -1
- package/dist/cli.js +1451 -489
- package/dist/gemini-login-EYY3EFH4.js +94 -0
- package/dist/{manager-queue-F4VVZMTE.js → manager-queue-FBAUCAGI.js} +4 -1
- package/dist/{query-agent-LRUUJR4F.js → query-agent-WM6UNZ37.js} +5 -2
- package/dist/{relevance-agent-CCN7JGTM.js → relevance-agent-H3U6TROD.js} +4 -1
- package/dist/{sessions-GRES2MUV.js → sessions-KL4LUGD7.js} +2 -2
- package/dist/{web-search-B7D5WMHU.js → web-search-TKBFSU3M.js} +4 -2
- package/package.json +3 -1
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,16 @@
|
|
|
2
2
|
import {
|
|
3
3
|
appendSessionEvent,
|
|
4
4
|
loadSessionHistory
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-HRVDYJEC.js";
|
|
6
|
+
import {
|
|
7
|
+
GEMINI_CODE_ASSIST_URL,
|
|
8
|
+
clearStoredAuth,
|
|
9
|
+
loadGeminiAuth,
|
|
10
|
+
loadStoredAuth,
|
|
11
|
+
refreshGeminiAccessToken,
|
|
12
|
+
saveGeminiAuth,
|
|
13
|
+
saveStoredAuth
|
|
14
|
+
} from "./chunk-3GZIDCV2.js";
|
|
6
15
|
import {
|
|
7
16
|
ensureOpenResearchConfig,
|
|
8
17
|
executeFetchUrl,
|
|
@@ -15,25 +24,32 @@ import {
|
|
|
15
24
|
getOpenAlexApiKey,
|
|
16
25
|
getSemanticScholarApiKey,
|
|
17
26
|
loadOpenResearchConfig,
|
|
18
|
-
readJsonFile,
|
|
19
27
|
saveOpenResearchConfig,
|
|
20
|
-
themeValues
|
|
28
|
+
themeValues
|
|
29
|
+
} from "./chunk-3KZN54JZ.js";
|
|
30
|
+
import {
|
|
31
|
+
readJsonFile,
|
|
21
32
|
writeJsonFile
|
|
22
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-77Q5B5H7.js";
|
|
23
34
|
import {
|
|
24
|
-
getOpenResearchAuthFile,
|
|
25
35
|
getOpenResearchRoot,
|
|
26
36
|
getOpenResearchSkillsDir,
|
|
27
37
|
getWorkspaceMetaDir,
|
|
28
38
|
getWorkspaceProjectFile,
|
|
29
39
|
getWorkspaceSessionsDir
|
|
30
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-4HCPHCC2.js";
|
|
41
|
+
import {
|
|
42
|
+
getAvailableModels,
|
|
43
|
+
getDefaultModel,
|
|
44
|
+
getProviderCatalog,
|
|
45
|
+
selectModelForTask
|
|
46
|
+
} from "./chunk-GVEVKDGV.js";
|
|
31
47
|
import {
|
|
32
48
|
__require
|
|
33
49
|
} from "./chunk-3RG5ZIWI.js";
|
|
34
50
|
|
|
35
51
|
// src/cli.ts
|
|
36
|
-
import
|
|
52
|
+
import React7 from "react";
|
|
37
53
|
import path19 from "path";
|
|
38
54
|
import { Command } from "commander";
|
|
39
55
|
import { render } from "ink";
|
|
@@ -164,7 +180,7 @@ ${markdown}`;
|
|
|
164
180
|
}
|
|
165
181
|
|
|
166
182
|
// src/lib/auth/import-codex.ts
|
|
167
|
-
import
|
|
183
|
+
import fs3 from "fs/promises";
|
|
168
184
|
import path3 from "path";
|
|
169
185
|
|
|
170
186
|
// src/lib/storage/credential-types.ts
|
|
@@ -193,30 +209,6 @@ function getBootstrapCredentialValidation() {
|
|
|
193
209
|
};
|
|
194
210
|
}
|
|
195
211
|
|
|
196
|
-
// src/lib/auth/store.ts
|
|
197
|
-
import fs3 from "fs/promises";
|
|
198
|
-
var AUTH_FILE_MODE = 384;
|
|
199
|
-
async function ensureCliHome(options) {
|
|
200
|
-
const root = getOpenResearchRoot(options);
|
|
201
|
-
await fs3.mkdir(root, { recursive: true, mode: 448 });
|
|
202
|
-
return root;
|
|
203
|
-
}
|
|
204
|
-
async function saveStoredAuth(auth2, options) {
|
|
205
|
-
await ensureCliHome(options);
|
|
206
|
-
const authFile = getOpenResearchAuthFile(options);
|
|
207
|
-
await writeJsonFile(authFile, auth2, AUTH_FILE_MODE);
|
|
208
|
-
await fs3.chmod(authFile, AUTH_FILE_MODE);
|
|
209
|
-
return authFile;
|
|
210
|
-
}
|
|
211
|
-
async function loadStoredAuth(options) {
|
|
212
|
-
const authFile = getOpenResearchAuthFile(options);
|
|
213
|
-
return readJsonFile(authFile, null);
|
|
214
|
-
}
|
|
215
|
-
async function clearStoredAuth(options) {
|
|
216
|
-
const authFile = getOpenResearchAuthFile(options);
|
|
217
|
-
await fs3.rm(authFile, { force: true });
|
|
218
|
-
}
|
|
219
|
-
|
|
220
212
|
// src/lib/auth/openai-oauth.ts
|
|
221
213
|
import { createHash, randomBytes } from "crypto";
|
|
222
214
|
var OPENAI_AUTH_URL = "https://auth.openai.com/oauth/authorize";
|
|
@@ -309,7 +301,7 @@ async function importCodexAuth(options = {}) {
|
|
|
309
301
|
const now = options.now ?? Date.now;
|
|
310
302
|
const codexAuthPath = options.codexAuthFilePath ?? path3.join(options.homeDir ?? process.env.HOME ?? "", ".codex", "auth.json");
|
|
311
303
|
const parsed = JSON.parse(
|
|
312
|
-
await
|
|
304
|
+
await fs3.readFile(codexAuthPath, "utf8")
|
|
313
305
|
);
|
|
314
306
|
if (parsed.auth_mode !== "chatgpt") {
|
|
315
307
|
throw new Error("Codex is not signed in with OpenAI on this device.");
|
|
@@ -478,55 +470,6 @@ async function loginWithBrowser(options) {
|
|
|
478
470
|
// src/lib/llm/config.ts
|
|
479
471
|
var OPENAI_AUTH_ONLY = process.env.OPENAI_AUTH_ONLY !== "false";
|
|
480
472
|
var CODEX_RESPONSES_URL = process.env.CODEX_RESPONSES_URL ?? "https://chatgpt.com/backend-api/codex/responses";
|
|
481
|
-
var OPENAI_VALIDATION_STALE_MS = 15 * 60 * 1e3;
|
|
482
|
-
|
|
483
|
-
// src/lib/llm/provider-catalog.ts
|
|
484
|
-
var OPENAI_PROVIDER_MODELS = [
|
|
485
|
-
"gpt-5.4",
|
|
486
|
-
"gpt-5.4-mini",
|
|
487
|
-
"o3",
|
|
488
|
-
"o4-mini"
|
|
489
|
-
];
|
|
490
|
-
var OPENAI_CATALOG = {
|
|
491
|
-
family: "openai",
|
|
492
|
-
displayName: "OpenAI",
|
|
493
|
-
models: OPENAI_PROVIDER_MODELS,
|
|
494
|
-
defaultModel: "gpt-5.4",
|
|
495
|
-
backgroundModel: "gpt-5.4-mini"
|
|
496
|
-
};
|
|
497
|
-
function getProviderCatalog(providerKind) {
|
|
498
|
-
switch (providerKind) {
|
|
499
|
-
case "openai_auth":
|
|
500
|
-
case "openai_api_key":
|
|
501
|
-
default:
|
|
502
|
-
return OPENAI_CATALOG;
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
function getAvailableModels(providerKind) {
|
|
506
|
-
return getProviderCatalog(providerKind).models;
|
|
507
|
-
}
|
|
508
|
-
function isSupportedModel(model, providerKind) {
|
|
509
|
-
if (!model) return false;
|
|
510
|
-
return getAvailableModels(providerKind).includes(model);
|
|
511
|
-
}
|
|
512
|
-
function getDefaultModel(providerKind) {
|
|
513
|
-
return getProviderCatalog(providerKind).defaultModel;
|
|
514
|
-
}
|
|
515
|
-
function selectModelForTask(providerKind, requestedModel, task) {
|
|
516
|
-
const catalog = getProviderCatalog(providerKind);
|
|
517
|
-
const selected = isSupportedModel(requestedModel, providerKind) ? requestedModel : catalog.defaultModel;
|
|
518
|
-
switch (task) {
|
|
519
|
-
case "conversation":
|
|
520
|
-
return selected;
|
|
521
|
-
case "compaction":
|
|
522
|
-
return selected.includes("5.4") ? catalog.backgroundModel : selected;
|
|
523
|
-
case "memory":
|
|
524
|
-
case "workspace":
|
|
525
|
-
return catalog.backgroundModel;
|
|
526
|
-
default:
|
|
527
|
-
return selected;
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
473
|
|
|
531
474
|
// src/lib/llm/openai-connection.ts
|
|
532
475
|
var VALIDATION_INSTRUCTIONS = "You are validating whether this OpenAI Codex connection can execute a minimal request. Reply with the single word ok.";
|
|
@@ -645,44 +588,42 @@ function trimCredential(value) {
|
|
|
645
588
|
const trimmed = value?.trim();
|
|
646
589
|
return trimmed ? trimmed : void 0;
|
|
647
590
|
}
|
|
648
|
-
|
|
649
|
-
const [config, stored] = await Promise.all([
|
|
650
|
-
loadOpenResearchConfig({ homeDir: options?.homeDir }),
|
|
651
|
-
loadStoredAuth({ homeDir: options?.homeDir })
|
|
652
|
-
]);
|
|
591
|
+
function resolveOpenAI(stored, config) {
|
|
653
592
|
if (stored) {
|
|
654
|
-
return {
|
|
655
|
-
kind: "openai_auth",
|
|
656
|
-
source: "stored_auth",
|
|
657
|
-
stored
|
|
658
|
-
};
|
|
593
|
+
return { kind: "openai_auth", source: "stored_auth", stored };
|
|
659
594
|
}
|
|
660
595
|
const envKey = trimCredential(process.env.OPENAI_API_KEY);
|
|
661
|
-
if (envKey) {
|
|
662
|
-
return {
|
|
663
|
-
kind: "openai_api_key",
|
|
664
|
-
source: "env",
|
|
665
|
-
apiKey: envKey
|
|
666
|
-
};
|
|
667
|
-
}
|
|
596
|
+
if (envKey) return { kind: "openai_api_key", source: "env", apiKey: envKey };
|
|
668
597
|
const providerKey = trimCredential(config?.providers?.openai?.apiKey);
|
|
669
|
-
if (providerKey) {
|
|
670
|
-
return {
|
|
671
|
-
kind: "openai_api_key",
|
|
672
|
-
source: "providers.openai.apiKey",
|
|
673
|
-
apiKey: providerKey
|
|
674
|
-
};
|
|
675
|
-
}
|
|
598
|
+
if (providerKey) return { kind: "openai_api_key", source: "providers.openai.apiKey", apiKey: providerKey };
|
|
676
599
|
const legacyKey = trimCredential(config?.apiKeys?.openai);
|
|
677
|
-
if (legacyKey) {
|
|
678
|
-
return {
|
|
679
|
-
kind: "openai_api_key",
|
|
680
|
-
source: "apiKeys.openai",
|
|
681
|
-
apiKey: legacyKey
|
|
682
|
-
};
|
|
683
|
-
}
|
|
600
|
+
if (legacyKey) return { kind: "openai_api_key", source: "apiKeys.openai", apiKey: legacyKey };
|
|
684
601
|
return null;
|
|
685
602
|
}
|
|
603
|
+
function resolveGemini(stored, config) {
|
|
604
|
+
if (stored) {
|
|
605
|
+
return { kind: "gemini_auth", source: "stored_auth", stored };
|
|
606
|
+
}
|
|
607
|
+
const envKey = trimCredential(process.env.GEMINI_API_KEY);
|
|
608
|
+
if (envKey) return { kind: "gemini_api_key", source: "env", apiKey: envKey };
|
|
609
|
+
const providerKey = trimCredential(config?.providers?.gemini?.apiKey);
|
|
610
|
+
if (providerKey) return { kind: "gemini_api_key", source: "providers.gemini.apiKey", apiKey: providerKey };
|
|
611
|
+
const legacyKey = trimCredential(config?.apiKeys?.gemini);
|
|
612
|
+
if (legacyKey) return { kind: "gemini_api_key", source: "apiKeys.gemini", apiKey: legacyKey };
|
|
613
|
+
return null;
|
|
614
|
+
}
|
|
615
|
+
async function resolveConfiguredProvider(options) {
|
|
616
|
+
const [config, openaiStored, geminiStored] = await Promise.all([
|
|
617
|
+
loadOpenResearchConfig({ homeDir: options?.homeDir }),
|
|
618
|
+
loadStoredAuth({ homeDir: options?.homeDir }),
|
|
619
|
+
loadGeminiAuth({ homeDir: options?.homeDir })
|
|
620
|
+
]);
|
|
621
|
+
const activeProvider = config?.activeProvider ?? "openai";
|
|
622
|
+
if (activeProvider === "gemini") {
|
|
623
|
+
return resolveGemini(geminiStored, config) ?? resolveOpenAI(openaiStored, config);
|
|
624
|
+
}
|
|
625
|
+
return resolveOpenAI(openaiStored, config) ?? resolveGemini(geminiStored, config);
|
|
626
|
+
}
|
|
686
627
|
async function hasConfiguredProvider(options) {
|
|
687
628
|
return await resolveConfiguredProvider(options) !== null;
|
|
688
629
|
}
|
|
@@ -691,24 +632,22 @@ async function getConfiguredProviderSummary(options) {
|
|
|
691
632
|
if (!resolved) {
|
|
692
633
|
return {
|
|
693
634
|
connected: false,
|
|
694
|
-
message: "No
|
|
635
|
+
message: "No credentials configured. Run /auth (OpenAI), /auth-gemini (Google), or /config apikey <key>."
|
|
695
636
|
};
|
|
696
637
|
}
|
|
697
638
|
if (resolved.kind === "openai_auth") {
|
|
698
|
-
return {
|
|
699
|
-
connected: true,
|
|
700
|
-
kind: resolved.kind,
|
|
701
|
-
source: resolved.source,
|
|
702
|
-
message: "OpenAI account connected."
|
|
703
|
-
};
|
|
639
|
+
return { connected: true, kind: resolved.kind, source: resolved.source, message: "OpenAI account connected." };
|
|
704
640
|
}
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
source: resolved.source,
|
|
710
|
-
|
|
711
|
-
|
|
641
|
+
if (resolved.kind === "openai_api_key") {
|
|
642
|
+
return { connected: true, kind: resolved.kind, source: resolved.source, message: `OpenAI API key configured via ${resolved.source}.` };
|
|
643
|
+
}
|
|
644
|
+
if (resolved.kind === "gemini_auth") {
|
|
645
|
+
return { connected: true, kind: resolved.kind, source: resolved.source, message: `Google account connected (${resolved.stored.tokens.email}).` };
|
|
646
|
+
}
|
|
647
|
+
if (resolved.kind === "gemini_api_key") {
|
|
648
|
+
return { connected: true, kind: resolved.kind, source: resolved.source, message: `Gemini API key configured via ${resolved.source}.` };
|
|
649
|
+
}
|
|
650
|
+
return { connected: false, message: "Unknown provider state." };
|
|
712
651
|
}
|
|
713
652
|
|
|
714
653
|
// src/lib/auth/status.ts
|
|
@@ -743,7 +682,7 @@ async function getAuthStatus(options) {
|
|
|
743
682
|
}
|
|
744
683
|
|
|
745
684
|
// src/lib/skills/registry.ts
|
|
746
|
-
import
|
|
685
|
+
import fs4 from "fs/promises";
|
|
747
686
|
import fsSync from "fs";
|
|
748
687
|
import path4 from "path";
|
|
749
688
|
import matter from "gray-matter";
|
|
@@ -756,13 +695,13 @@ function normalizeSkillName(name) {
|
|
|
756
695
|
}
|
|
757
696
|
async function ensureUserSkillsDir(options) {
|
|
758
697
|
const dir = getOpenResearchSkillsDir(options);
|
|
759
|
-
await
|
|
698
|
+
await fs4.mkdir(dir, { recursive: true });
|
|
760
699
|
return dir;
|
|
761
700
|
}
|
|
762
701
|
async function readSkillSummary(skillDir, source2) {
|
|
763
702
|
const skillFile = path4.join(skillDir, "SKILL.md");
|
|
764
703
|
try {
|
|
765
|
-
const raw = await
|
|
704
|
+
const raw = await fs4.readFile(skillFile, "utf8");
|
|
766
705
|
const parsed = matter(raw);
|
|
767
706
|
const name = String(parsed.data.name ?? "").trim();
|
|
768
707
|
const description = String(parsed.data.description ?? "").trim();
|
|
@@ -781,7 +720,7 @@ async function readSkillSummary(skillDir, source2) {
|
|
|
781
720
|
}
|
|
782
721
|
}
|
|
783
722
|
async function listSkillsInDirectory(rootDir, source2) {
|
|
784
|
-
const entries = await
|
|
723
|
+
const entries = await fs4.readdir(rootDir, { withFileTypes: true }).catch(() => []);
|
|
785
724
|
const results = await Promise.all(
|
|
786
725
|
entries.filter((entry) => entry.isDirectory()).map((entry) => readSkillSummary(path4.join(rootDir, entry.name), source2))
|
|
787
726
|
);
|
|
@@ -798,7 +737,7 @@ async function listAvailableSkills(options) {
|
|
|
798
737
|
async function validateSkillDirectory(input2) {
|
|
799
738
|
const errors = [];
|
|
800
739
|
const skillFile = path4.join(input2.skillDir, "SKILL.md");
|
|
801
|
-
const raw = await
|
|
740
|
+
const raw = await fs4.readFile(skillFile, "utf8").catch(() => "");
|
|
802
741
|
if (!raw) {
|
|
803
742
|
return { ok: false, errors: ["SKILL.md is missing."] };
|
|
804
743
|
}
|
|
@@ -829,9 +768,9 @@ async function createSkillScaffold(input2) {
|
|
|
829
768
|
const skillsDir = await ensureUserSkillsDir({ homeDir: input2.homeDir });
|
|
830
769
|
const name = normalizeSkillName(input2.name);
|
|
831
770
|
const skillDir = path4.join(skillsDir, name);
|
|
832
|
-
await
|
|
833
|
-
await
|
|
834
|
-
await
|
|
771
|
+
await fs4.mkdir(path4.join(skillDir, "scripts"), { recursive: true });
|
|
772
|
+
await fs4.mkdir(path4.join(skillDir, "references"), { recursive: true });
|
|
773
|
+
await fs4.mkdir(path4.join(skillDir, "assets"), { recursive: true });
|
|
835
774
|
const body = `---
|
|
836
775
|
name: ${name}
|
|
837
776
|
description: ${input2.description}
|
|
@@ -851,7 +790,7 @@ ${input2.examples.map((example) => `- ${example}`).join("\n")}
|
|
|
851
790
|
|
|
852
791
|
${input2.workflow}
|
|
853
792
|
`;
|
|
854
|
-
await
|
|
793
|
+
await fs4.writeFile(path4.join(skillDir, "SKILL.md"), body, "utf8");
|
|
855
794
|
return skillDir;
|
|
856
795
|
}
|
|
857
796
|
|
|
@@ -889,25 +828,25 @@ function formatDateTime(value) {
|
|
|
889
828
|
}
|
|
890
829
|
|
|
891
830
|
// src/lib/cli/version.ts
|
|
892
|
-
var PACKAGE_VERSION = "1.
|
|
831
|
+
var PACKAGE_VERSION = "1.1.0";
|
|
893
832
|
function getPackageVersion() {
|
|
894
833
|
return PACKAGE_VERSION;
|
|
895
834
|
}
|
|
896
835
|
|
|
897
836
|
// src/tui/app.tsx
|
|
898
837
|
import path18 from "path";
|
|
899
|
-
import {
|
|
838
|
+
import React6, {
|
|
900
839
|
startTransition as startTransition2,
|
|
901
840
|
useDeferredValue,
|
|
902
841
|
useEffect as useEffect4,
|
|
903
|
-
useMemo as
|
|
842
|
+
useMemo as useMemo4,
|
|
904
843
|
useRef as useRef2,
|
|
905
|
-
useState as
|
|
844
|
+
useState as useState7
|
|
906
845
|
} from "react";
|
|
907
|
-
import { Box as Box5, Static, Text as Text5, useApp, useInput as
|
|
846
|
+
import { Box as Box5, Static, Text as Text5, useApp, useInput as useInput5 } from "ink";
|
|
908
847
|
|
|
909
848
|
// src/tui/text-input.tsx
|
|
910
|
-
import { useState, useEffect, useRef } from "react";
|
|
849
|
+
import { useState, useEffect, useLayoutEffect, useRef } from "react";
|
|
911
850
|
import { Box, Text, useInput, useStdout, measureElement } from "ink";
|
|
912
851
|
|
|
913
852
|
// node_modules/chalk/source/vendor/ansi-styles/index.js
|
|
@@ -2055,30 +1994,37 @@ function TextInput({
|
|
|
2055
1994
|
pasteMapRef.current.clear();
|
|
2056
1995
|
}
|
|
2057
1996
|
}, [originalValue]);
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
if (
|
|
2062
|
-
|
|
2063
|
-
}
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
}
|
|
2071
|
-
});
|
|
1997
|
+
useLayoutEffect(() => {
|
|
1998
|
+
setInputWidth((current) => {
|
|
1999
|
+
const fallbackWidth = typeof stdout.columns === "number" && stdout.columns > 0 ? stdout.columns : current;
|
|
2000
|
+
if (!containerRef.current) {
|
|
2001
|
+
return fallbackWidth > 0 && fallbackWidth !== current ? fallbackWidth : current;
|
|
2002
|
+
}
|
|
2003
|
+
const measuredWidth = measureElement(containerRef.current).width;
|
|
2004
|
+
const nextWidth = measuredWidth > 0 ? measuredWidth : fallbackWidth;
|
|
2005
|
+
if (nextWidth > 0 && nextWidth !== current) {
|
|
2006
|
+
return nextWidth;
|
|
2007
|
+
}
|
|
2008
|
+
return current;
|
|
2009
|
+
});
|
|
2010
|
+
}, [stdout.columns]);
|
|
2072
2011
|
function pasteBadge(entry) {
|
|
2073
2012
|
return applyThemeColor(source_default.dim, accentColor, `[Pasted text #${entry.id} +${entry.lineCount} lines]`);
|
|
2074
2013
|
}
|
|
2075
|
-
function
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2014
|
+
function getSlashCommandRange() {
|
|
2015
|
+
let slashIdx = originalValue.lastIndexOf("/");
|
|
2016
|
+
while (slashIdx > 0 && originalValue[slashIdx - 1] !== " ") {
|
|
2017
|
+
slashIdx = originalValue.lastIndexOf("/", slashIdx - 1);
|
|
2018
|
+
}
|
|
2019
|
+
if (slashIdx === -1) return { start: 0, end: 0 };
|
|
2020
|
+
const afterSlash = originalValue.indexOf(" ", slashIdx);
|
|
2021
|
+
const end = afterSlash === -1 ? originalValue.length : afterSlash;
|
|
2022
|
+
return { start: slashIdx, end };
|
|
2079
2023
|
}
|
|
2080
2024
|
function buildRendered() {
|
|
2081
|
-
const
|
|
2025
|
+
const cmdRange = getSlashCommandRange();
|
|
2026
|
+
const hasCmd = cmdRange.end > cmdRange.start;
|
|
2027
|
+
const inCmd = (idx) => hasCmd && idx >= cmdRange.start && idx < cmdRange.end;
|
|
2082
2028
|
if (showCursor && focus) {
|
|
2083
2029
|
if (originalValue.length === 0) return source_default.inverse(" ");
|
|
2084
2030
|
const pasteIds2 = [...pasteMapRef.current.keys()].sort((a, b) => a - b);
|
|
@@ -2097,12 +2043,12 @@ function TextInput({
|
|
|
2097
2043
|
} else if (i2 === cursorOffset) {
|
|
2098
2044
|
if (char === "\n") {
|
|
2099
2045
|
result2 += source_default.inverse(" ") + "\n";
|
|
2100
|
-
} else if (
|
|
2046
|
+
} else if (inCmd(i2)) {
|
|
2101
2047
|
result2 += applyThemeColor(source_default.inverse, accentColor, char);
|
|
2102
2048
|
} else {
|
|
2103
2049
|
result2 += source_default.inverse(char);
|
|
2104
2050
|
}
|
|
2105
|
-
} else if (
|
|
2051
|
+
} else if (inCmd(i2)) {
|
|
2106
2052
|
result2 += applyThemeColor(source_default, accentColor, char);
|
|
2107
2053
|
} else {
|
|
2108
2054
|
result2 += char;
|
|
@@ -2123,7 +2069,7 @@ function TextInput({
|
|
|
2123
2069
|
const entry = pasteIdx < pasteIds.length ? pasteMapRef.current.get(pasteIds[pasteIdx]) : void 0;
|
|
2124
2070
|
pasteIdx++;
|
|
2125
2071
|
result += entry ? pasteBadge(entry) : "";
|
|
2126
|
-
} else if (
|
|
2072
|
+
} else if (inCmd(i)) {
|
|
2127
2073
|
result += applyThemeColor(source_default, accentColor, char);
|
|
2128
2074
|
} else {
|
|
2129
2075
|
result += char;
|
|
@@ -2355,7 +2301,7 @@ function useTheme() {
|
|
|
2355
2301
|
}
|
|
2356
2302
|
|
|
2357
2303
|
// src/lib/workspace/scan.ts
|
|
2358
|
-
import
|
|
2304
|
+
import fs5 from "fs/promises";
|
|
2359
2305
|
import path5 from "path";
|
|
2360
2306
|
var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
2361
2307
|
".md",
|
|
@@ -2386,7 +2332,7 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
|
2386
2332
|
".turbo"
|
|
2387
2333
|
]);
|
|
2388
2334
|
async function walkDir(rootDir, currentDir, out) {
|
|
2389
|
-
const entries = await
|
|
2335
|
+
const entries = await fs5.readdir(currentDir, { withFileTypes: true });
|
|
2390
2336
|
for (const entry of entries) {
|
|
2391
2337
|
if (IGNORED_DIRS.has(entry.name)) {
|
|
2392
2338
|
continue;
|
|
@@ -2404,7 +2350,7 @@ async function walkDir(rootDir, currentDir, out) {
|
|
|
2404
2350
|
key: `path:${relativePath}`,
|
|
2405
2351
|
label: relativePath,
|
|
2406
2352
|
path: relativePath,
|
|
2407
|
-
content: await
|
|
2353
|
+
content: await fs5.readFile(fullPath, "utf8")
|
|
2408
2354
|
});
|
|
2409
2355
|
}
|
|
2410
2356
|
}
|
|
@@ -2434,12 +2380,7 @@ var OPENAI_MODEL_MAP = {
|
|
|
2434
2380
|
"gpt-5.4": "gpt-5.4",
|
|
2435
2381
|
"gpt-5.4-mini": "gpt-5.4-mini",
|
|
2436
2382
|
"openai/gpt-5.4": "gpt-5.4",
|
|
2437
|
-
"openai/gpt-5.4-mini": "gpt-5.4-mini"
|
|
2438
|
-
"google/gemini-3-flash-preview": "gpt-5.4",
|
|
2439
|
-
"google/gemini-3.1-pro": "gpt-5.4",
|
|
2440
|
-
"google/gemini-3.1-flash-lite-preview": "gpt-5.4-mini",
|
|
2441
|
-
"google/gemini-2.5-flash-lite": "gpt-5.4-mini",
|
|
2442
|
-
"google/gemini-2.5-flash": "gpt-5.4-mini"
|
|
2383
|
+
"openai/gpt-5.4-mini": "gpt-5.4-mini"
|
|
2443
2384
|
};
|
|
2444
2385
|
function resolveOpenAIModel(model) {
|
|
2445
2386
|
return OPENAI_MODEL_MAP[model ?? "gpt-5.4"] ?? "gpt-5.4";
|
|
@@ -2563,14 +2504,14 @@ function createOpenAIAuthProvider(credentials, onTokenRefresh, onValidationChang
|
|
|
2563
2504
|
}
|
|
2564
2505
|
return creds.accessToken;
|
|
2565
2506
|
}
|
|
2566
|
-
const
|
|
2507
|
+
const sessionId2 = crypto.randomUUID();
|
|
2567
2508
|
function buildHeaders3(token) {
|
|
2568
2509
|
const headers = {
|
|
2569
2510
|
Authorization: `Bearer ${token}`,
|
|
2570
2511
|
"Content-Type": "application/json",
|
|
2571
2512
|
originator: "open-research",
|
|
2572
2513
|
"User-Agent": `open-research/${getPackageVersion()} (${process.platform} ${process.arch})`,
|
|
2573
|
-
session_id:
|
|
2514
|
+
session_id: sessionId2
|
|
2574
2515
|
};
|
|
2575
2516
|
if (creds.accountId) {
|
|
2576
2517
|
headers["ChatGPT-Account-Id"] = creds.accountId;
|
|
@@ -3085,12 +3026,478 @@ function createOpenAIAPIKeyProvider(apiKey) {
|
|
|
3085
3026
|
};
|
|
3086
3027
|
}
|
|
3087
3028
|
|
|
3029
|
+
// src/lib/llm/gemini-format.ts
|
|
3030
|
+
function convertMessagesToGemini(messages) {
|
|
3031
|
+
const systemParts = [];
|
|
3032
|
+
const rawContents = [];
|
|
3033
|
+
const toolCallNames = /* @__PURE__ */ new Map();
|
|
3034
|
+
for (const msg of messages) {
|
|
3035
|
+
if (msg.role === "assistant" && msg.tool_calls) {
|
|
3036
|
+
for (const tc of msg.tool_calls) {
|
|
3037
|
+
toolCallNames.set(tc.id, tc.function.name);
|
|
3038
|
+
}
|
|
3039
|
+
}
|
|
3040
|
+
}
|
|
3041
|
+
for (const msg of messages) {
|
|
3042
|
+
if (msg.role === "system") {
|
|
3043
|
+
const text = typeof msg.content === "string" ? msg.content : "";
|
|
3044
|
+
if (text.trim()) systemParts.push(text);
|
|
3045
|
+
continue;
|
|
3046
|
+
}
|
|
3047
|
+
if (msg.role === "user") {
|
|
3048
|
+
const text = typeof msg.content === "string" ? msg.content : "";
|
|
3049
|
+
if (text.trim()) {
|
|
3050
|
+
rawContents.push({ role: "user", parts: [{ text }] });
|
|
3051
|
+
}
|
|
3052
|
+
continue;
|
|
3053
|
+
}
|
|
3054
|
+
if (msg.role === "assistant") {
|
|
3055
|
+
const parts = [];
|
|
3056
|
+
const text = typeof msg.content === "string" ? msg.content : "";
|
|
3057
|
+
if (text.trim()) {
|
|
3058
|
+
parts.push({ text });
|
|
3059
|
+
}
|
|
3060
|
+
if (msg.tool_calls) {
|
|
3061
|
+
for (const tc of msg.tool_calls) {
|
|
3062
|
+
let args = {};
|
|
3063
|
+
try {
|
|
3064
|
+
args = JSON.parse(tc.function.arguments);
|
|
3065
|
+
} catch {
|
|
3066
|
+
}
|
|
3067
|
+
parts.push({
|
|
3068
|
+
functionCall: { name: tc.function.name, args },
|
|
3069
|
+
thoughtSignature: "skip_thought_signature_validator"
|
|
3070
|
+
});
|
|
3071
|
+
}
|
|
3072
|
+
}
|
|
3073
|
+
if (parts.length > 0) {
|
|
3074
|
+
rawContents.push({ role: "model", parts });
|
|
3075
|
+
}
|
|
3076
|
+
continue;
|
|
3077
|
+
}
|
|
3078
|
+
if (msg.role === "tool") {
|
|
3079
|
+
const name = msg.tool_call_id ? toolCallNames.get(msg.tool_call_id) ?? "unknown" : "unknown";
|
|
3080
|
+
const content = typeof msg.content === "string" ? msg.content : "";
|
|
3081
|
+
rawContents.push({
|
|
3082
|
+
role: "user",
|
|
3083
|
+
parts: [{ functionResponse: { name, response: { content } } }]
|
|
3084
|
+
});
|
|
3085
|
+
continue;
|
|
3086
|
+
}
|
|
3087
|
+
}
|
|
3088
|
+
const merged = [];
|
|
3089
|
+
for (const content of rawContents) {
|
|
3090
|
+
const last = merged[merged.length - 1];
|
|
3091
|
+
if (last && last.role === content.role) {
|
|
3092
|
+
last.parts.push(...content.parts);
|
|
3093
|
+
} else {
|
|
3094
|
+
merged.push({ role: content.role, parts: [...content.parts] });
|
|
3095
|
+
}
|
|
3096
|
+
}
|
|
3097
|
+
return {
|
|
3098
|
+
systemInstruction: systemParts.length > 0 ? { parts: systemParts.map((text) => ({ text })) } : void 0,
|
|
3099
|
+
contents: merged
|
|
3100
|
+
};
|
|
3101
|
+
}
|
|
3102
|
+
function convertToolsToGemini(tools) {
|
|
3103
|
+
if (!tools || tools.length === 0) return void 0;
|
|
3104
|
+
return {
|
|
3105
|
+
functionDeclarations: tools.map((t) => ({
|
|
3106
|
+
name: t.function.name,
|
|
3107
|
+
description: t.function.description,
|
|
3108
|
+
parameters: t.function.parameters
|
|
3109
|
+
}))
|
|
3110
|
+
};
|
|
3111
|
+
}
|
|
3112
|
+
function mapReasoningEffort(effort, model) {
|
|
3113
|
+
if (!effort || effort === "none") return void 0;
|
|
3114
|
+
if (model.includes("gemini-3")) {
|
|
3115
|
+
const levelMap = {
|
|
3116
|
+
low: "THINKING_LEVEL_LOW",
|
|
3117
|
+
medium: "THINKING_LEVEL_MEDIUM",
|
|
3118
|
+
high: "THINKING_LEVEL_HIGH",
|
|
3119
|
+
xhigh: "THINKING_LEVEL_HIGH"
|
|
3120
|
+
};
|
|
3121
|
+
return {
|
|
3122
|
+
thinkingConfig: {
|
|
3123
|
+
thinkingLevel: levelMap[effort] ?? "THINKING_LEVEL_MEDIUM",
|
|
3124
|
+
includeThoughts: true
|
|
3125
|
+
}
|
|
3126
|
+
};
|
|
3127
|
+
}
|
|
3128
|
+
const budgetMap = {
|
|
3129
|
+
low: 1024,
|
|
3130
|
+
medium: 8192,
|
|
3131
|
+
high: 16384,
|
|
3132
|
+
xhigh: 32768
|
|
3133
|
+
};
|
|
3134
|
+
return {
|
|
3135
|
+
thinkingConfig: {
|
|
3136
|
+
thinkingBudget: budgetMap[effort] ?? 8192,
|
|
3137
|
+
includeThoughts: true
|
|
3138
|
+
}
|
|
3139
|
+
};
|
|
3140
|
+
}
|
|
3141
|
+
function mapJsonSchema(schema) {
|
|
3142
|
+
if (!schema) return void 0;
|
|
3143
|
+
return {
|
|
3144
|
+
responseMimeType: "application/json",
|
|
3145
|
+
responseSchema: schema.schema
|
|
3146
|
+
};
|
|
3147
|
+
}
|
|
3148
|
+
|
|
3149
|
+
// src/lib/llm/providers/gemini-auth.ts
|
|
3150
|
+
var REFRESH_BUFFER_MS = 6e4;
|
|
3151
|
+
var GEMINI_CLI_VERSION = "0.30.0";
|
|
3152
|
+
var sessionId = crypto.randomUUID();
|
|
3153
|
+
function createGeminiAuthProvider(credentials, onTokenRefresh) {
|
|
3154
|
+
let currentToken = credentials.accessToken;
|
|
3155
|
+
let currentExpiry = credentials.expiresAt;
|
|
3156
|
+
let refreshing = null;
|
|
3157
|
+
async function ensureValidToken() {
|
|
3158
|
+
if (Date.now() < currentExpiry - REFRESH_BUFFER_MS) {
|
|
3159
|
+
return currentToken;
|
|
3160
|
+
}
|
|
3161
|
+
if (!refreshing) {
|
|
3162
|
+
refreshing = (async () => {
|
|
3163
|
+
const result = await refreshGeminiAccessToken(credentials.refreshToken);
|
|
3164
|
+
currentToken = result.access_token;
|
|
3165
|
+
currentExpiry = Date.now() + result.expires_in * 1e3;
|
|
3166
|
+
if (result.refresh_token) {
|
|
3167
|
+
credentials.refreshToken = result.refresh_token;
|
|
3168
|
+
}
|
|
3169
|
+
await onTokenRefresh({
|
|
3170
|
+
...credentials,
|
|
3171
|
+
accessToken: currentToken,
|
|
3172
|
+
expiresAt: currentExpiry
|
|
3173
|
+
});
|
|
3174
|
+
})();
|
|
3175
|
+
}
|
|
3176
|
+
try {
|
|
3177
|
+
await refreshing;
|
|
3178
|
+
} finally {
|
|
3179
|
+
refreshing = null;
|
|
3180
|
+
}
|
|
3181
|
+
return currentToken;
|
|
3182
|
+
}
|
|
3183
|
+
function buildHeaders3(token, model) {
|
|
3184
|
+
return {
|
|
3185
|
+
Authorization: `Bearer ${token}`,
|
|
3186
|
+
"Content-Type": "application/json",
|
|
3187
|
+
"User-Agent": `GeminiCLI/${GEMINI_CLI_VERSION}/${model} (${process.platform}; ${process.arch})`,
|
|
3188
|
+
"x-activity-request-id": Math.random().toString(36).slice(2, 10)
|
|
3189
|
+
};
|
|
3190
|
+
}
|
|
3191
|
+
function buildRequestBody(options, model) {
|
|
3192
|
+
const messages = options.messages;
|
|
3193
|
+
const { systemInstruction, contents } = convertMessagesToGemini(messages);
|
|
3194
|
+
const generationConfig = {};
|
|
3195
|
+
if (options.temperature !== void 0) generationConfig.temperature = options.temperature;
|
|
3196
|
+
if (options.maxTokens !== void 0) generationConfig.maxOutputTokens = options.maxTokens;
|
|
3197
|
+
const thinking = mapReasoningEffort(options.reasoningEffort, model);
|
|
3198
|
+
if (thinking) Object.assign(generationConfig, thinking);
|
|
3199
|
+
if ("jsonSchema" in options && options.jsonSchema) {
|
|
3200
|
+
const schema = mapJsonSchema(options.jsonSchema);
|
|
3201
|
+
if (schema) Object.assign(generationConfig, schema);
|
|
3202
|
+
}
|
|
3203
|
+
const request = {
|
|
3204
|
+
contents,
|
|
3205
|
+
generationConfig,
|
|
3206
|
+
session_id: sessionId
|
|
3207
|
+
};
|
|
3208
|
+
if (systemInstruction) request.systemInstruction = systemInstruction;
|
|
3209
|
+
if ("tools" in options && options.tools) {
|
|
3210
|
+
const geminiTools = convertToolsToGemini(options.tools);
|
|
3211
|
+
if (geminiTools) request.tools = [geminiTools];
|
|
3212
|
+
}
|
|
3213
|
+
return {
|
|
3214
|
+
project: credentials.projectId,
|
|
3215
|
+
model,
|
|
3216
|
+
user_prompt_id: crypto.randomUUID(),
|
|
3217
|
+
request
|
|
3218
|
+
};
|
|
3219
|
+
}
|
|
3220
|
+
async function callLLM(options) {
|
|
3221
|
+
const model = options.model ?? "gemini-3.1-pro-preview";
|
|
3222
|
+
const token = await ensureValidToken();
|
|
3223
|
+
const body = buildRequestBody(options, model);
|
|
3224
|
+
const startTime = Date.now();
|
|
3225
|
+
const response = await fetch(
|
|
3226
|
+
`${GEMINI_CODE_ASSIST_URL}/v1internal:generateContent`,
|
|
3227
|
+
{
|
|
3228
|
+
method: "POST",
|
|
3229
|
+
headers: buildHeaders3(token, model),
|
|
3230
|
+
body: JSON.stringify(body)
|
|
3231
|
+
}
|
|
3232
|
+
);
|
|
3233
|
+
if (!response.ok) {
|
|
3234
|
+
const text = await response.text();
|
|
3235
|
+
throw new Error(`Gemini API error: ${response.status} ${text}`);
|
|
3236
|
+
}
|
|
3237
|
+
const data = await response.json();
|
|
3238
|
+
const inner = data.response ?? data;
|
|
3239
|
+
const parts = inner.candidates?.[0]?.content?.parts ?? [];
|
|
3240
|
+
const content = parts.map((p) => p.text ?? "").join("");
|
|
3241
|
+
const usage = inner.usageMetadata ?? {};
|
|
3242
|
+
return {
|
|
3243
|
+
content,
|
|
3244
|
+
model,
|
|
3245
|
+
usage: {
|
|
3246
|
+
promptTokens: usage.promptTokenCount ?? 0,
|
|
3247
|
+
completionTokens: usage.candidatesTokenCount ?? 0,
|
|
3248
|
+
totalTokens: usage.totalTokenCount ?? 0
|
|
3249
|
+
},
|
|
3250
|
+
latencyMs: Date.now() - startTime
|
|
3251
|
+
};
|
|
3252
|
+
}
|
|
3253
|
+
async function* callLLMStreaming(options) {
|
|
3254
|
+
const model = options.model ?? "gemini-3.1-pro-preview";
|
|
3255
|
+
const token = await ensureValidToken();
|
|
3256
|
+
const body = buildRequestBody(options, model);
|
|
3257
|
+
const response = await fetch(
|
|
3258
|
+
`${GEMINI_CODE_ASSIST_URL}/v1internal:streamGenerateContent?alt=sse`,
|
|
3259
|
+
{
|
|
3260
|
+
method: "POST",
|
|
3261
|
+
headers: {
|
|
3262
|
+
...buildHeaders3(token, model),
|
|
3263
|
+
Accept: "text/event-stream"
|
|
3264
|
+
},
|
|
3265
|
+
body: JSON.stringify(body),
|
|
3266
|
+
signal: options.signal
|
|
3267
|
+
}
|
|
3268
|
+
);
|
|
3269
|
+
if (!response.ok) {
|
|
3270
|
+
const text = await response.text();
|
|
3271
|
+
throw new Error(`Gemini streaming error: ${response.status} ${text}`);
|
|
3272
|
+
}
|
|
3273
|
+
const reader = response.body?.getReader();
|
|
3274
|
+
if (!reader) throw new Error("No response body");
|
|
3275
|
+
const decoder = new TextDecoder();
|
|
3276
|
+
let buffer = "";
|
|
3277
|
+
let fullText = "";
|
|
3278
|
+
const toolCalls = [];
|
|
3279
|
+
let toolCallIndex = 0;
|
|
3280
|
+
let lastUsage;
|
|
3281
|
+
while (true) {
|
|
3282
|
+
const { done, value } = await reader.read();
|
|
3283
|
+
if (done) break;
|
|
3284
|
+
buffer += decoder.decode(value, { stream: true });
|
|
3285
|
+
while (buffer.includes("\n")) {
|
|
3286
|
+
const lineEnd = buffer.indexOf("\n");
|
|
3287
|
+
const line = buffer.slice(0, lineEnd).trim();
|
|
3288
|
+
buffer = buffer.slice(lineEnd + 1);
|
|
3289
|
+
if (!line.startsWith("data: ")) continue;
|
|
3290
|
+
const jsonStr = line.slice(6);
|
|
3291
|
+
if (jsonStr === "[DONE]") continue;
|
|
3292
|
+
let parsed;
|
|
3293
|
+
try {
|
|
3294
|
+
parsed = JSON.parse(jsonStr);
|
|
3295
|
+
} catch {
|
|
3296
|
+
continue;
|
|
3297
|
+
}
|
|
3298
|
+
const inner = parsed.response ?? parsed;
|
|
3299
|
+
const candidates = inner.candidates ?? [];
|
|
3300
|
+
const candidate = candidates[0];
|
|
3301
|
+
if (!candidate) continue;
|
|
3302
|
+
const parts = candidate.content?.parts ?? [];
|
|
3303
|
+
for (const part of parts) {
|
|
3304
|
+
if (typeof part.text === "string" && part.text) {
|
|
3305
|
+
if (part.thought === true) continue;
|
|
3306
|
+
fullText += part.text;
|
|
3307
|
+
yield { type: "text_delta", content: part.text };
|
|
3308
|
+
}
|
|
3309
|
+
if (part.functionCall) {
|
|
3310
|
+
const fc = part.functionCall;
|
|
3311
|
+
const id = `call_${crypto.randomUUID().slice(0, 8)}`;
|
|
3312
|
+
const args = JSON.stringify(fc.args ?? {});
|
|
3313
|
+
toolCalls.push({ id, name: fc.name, arguments: args });
|
|
3314
|
+
yield { type: "tool_call_start", index: toolCallIndex, id, name: fc.name };
|
|
3315
|
+
yield { type: "tool_call_delta", index: toolCallIndex, arguments: args };
|
|
3316
|
+
toolCallIndex++;
|
|
3317
|
+
}
|
|
3318
|
+
}
|
|
3319
|
+
const usage = inner.usageMetadata;
|
|
3320
|
+
if (usage) {
|
|
3321
|
+
lastUsage = {
|
|
3322
|
+
promptTokens: usage.promptTokenCount ?? 0,
|
|
3323
|
+
completionTokens: usage.candidatesTokenCount ?? 0,
|
|
3324
|
+
totalTokens: usage.totalTokenCount ?? 0,
|
|
3325
|
+
cachedTokens: usage.cachedContentTokenCount ?? 0,
|
|
3326
|
+
reasoningTokens: usage.thoughtsTokenCount ?? 0
|
|
3327
|
+
};
|
|
3328
|
+
}
|
|
3329
|
+
}
|
|
3330
|
+
}
|
|
3331
|
+
yield {
|
|
3332
|
+
type: "done",
|
|
3333
|
+
content: fullText,
|
|
3334
|
+
toolCalls,
|
|
3335
|
+
usage: lastUsage
|
|
3336
|
+
};
|
|
3337
|
+
}
|
|
3338
|
+
return {
|
|
3339
|
+
kind: "gemini_auth",
|
|
3340
|
+
callLLM,
|
|
3341
|
+
callLLMStreaming
|
|
3342
|
+
};
|
|
3343
|
+
}
|
|
3344
|
+
|
|
3345
|
+
// src/lib/llm/providers/gemini-api-key.ts
|
|
3346
|
+
var GEMINI_API_BASE = "https://generativelanguage.googleapis.com/v1beta";
|
|
3347
|
+
function createGeminiAPIKeyProvider(apiKey) {
|
|
3348
|
+
function buildRequestBody(options, model) {
|
|
3349
|
+
const { systemInstruction, contents } = convertMessagesToGemini(options.messages);
|
|
3350
|
+
const generationConfig = {};
|
|
3351
|
+
if (options.temperature !== void 0) generationConfig.temperature = options.temperature;
|
|
3352
|
+
if (options.maxTokens !== void 0) generationConfig.maxOutputTokens = options.maxTokens;
|
|
3353
|
+
const thinking = mapReasoningEffort(options.reasoningEffort, model);
|
|
3354
|
+
if (thinking) Object.assign(generationConfig, thinking);
|
|
3355
|
+
if ("jsonSchema" in options && options.jsonSchema) {
|
|
3356
|
+
const schema = mapJsonSchema(options.jsonSchema);
|
|
3357
|
+
if (schema) Object.assign(generationConfig, schema);
|
|
3358
|
+
}
|
|
3359
|
+
const body = {
|
|
3360
|
+
contents,
|
|
3361
|
+
generationConfig
|
|
3362
|
+
};
|
|
3363
|
+
if (systemInstruction) body.systemInstruction = systemInstruction;
|
|
3364
|
+
if ("tools" in options && options.tools) {
|
|
3365
|
+
const geminiTools = convertToolsToGemini(options.tools);
|
|
3366
|
+
if (geminiTools) body.tools = [geminiTools];
|
|
3367
|
+
}
|
|
3368
|
+
return body;
|
|
3369
|
+
}
|
|
3370
|
+
async function callLLM(options) {
|
|
3371
|
+
const model = options.model ?? "gemini-3-flash-preview";
|
|
3372
|
+
const body = buildRequestBody(options, model);
|
|
3373
|
+
const startTime = Date.now();
|
|
3374
|
+
const response = await fetch(
|
|
3375
|
+
`${GEMINI_API_BASE}/models/${model}:generateContent`,
|
|
3376
|
+
{
|
|
3377
|
+
method: "POST",
|
|
3378
|
+
headers: {
|
|
3379
|
+
"Content-Type": "application/json",
|
|
3380
|
+
"x-goog-api-key": apiKey
|
|
3381
|
+
},
|
|
3382
|
+
body: JSON.stringify(body)
|
|
3383
|
+
}
|
|
3384
|
+
);
|
|
3385
|
+
if (!response.ok) {
|
|
3386
|
+
const text = await response.text();
|
|
3387
|
+
throw new Error(`Gemini API error: ${response.status} ${text}`);
|
|
3388
|
+
}
|
|
3389
|
+
const data = await response.json();
|
|
3390
|
+
const parts = data.candidates?.[0]?.content?.parts ?? [];
|
|
3391
|
+
const content = parts.map((p) => p.text ?? "").join("");
|
|
3392
|
+
const usage = data.usageMetadata ?? {};
|
|
3393
|
+
return {
|
|
3394
|
+
content,
|
|
3395
|
+
model,
|
|
3396
|
+
usage: {
|
|
3397
|
+
promptTokens: usage.promptTokenCount ?? 0,
|
|
3398
|
+
completionTokens: usage.candidatesTokenCount ?? 0,
|
|
3399
|
+
totalTokens: usage.totalTokenCount ?? 0
|
|
3400
|
+
},
|
|
3401
|
+
latencyMs: Date.now() - startTime
|
|
3402
|
+
};
|
|
3403
|
+
}
|
|
3404
|
+
async function* callLLMStreaming(options) {
|
|
3405
|
+
const model = options.model ?? "gemini-3-flash-preview";
|
|
3406
|
+
const body = buildRequestBody(options, model);
|
|
3407
|
+
const response = await fetch(
|
|
3408
|
+
`${GEMINI_API_BASE}/models/${model}:streamGenerateContent?alt=sse`,
|
|
3409
|
+
{
|
|
3410
|
+
method: "POST",
|
|
3411
|
+
headers: {
|
|
3412
|
+
"Content-Type": "application/json",
|
|
3413
|
+
"x-goog-api-key": apiKey,
|
|
3414
|
+
Accept: "text/event-stream"
|
|
3415
|
+
},
|
|
3416
|
+
body: JSON.stringify(body),
|
|
3417
|
+
signal: options.signal
|
|
3418
|
+
}
|
|
3419
|
+
);
|
|
3420
|
+
if (!response.ok) {
|
|
3421
|
+
const text = await response.text();
|
|
3422
|
+
throw new Error(`Gemini streaming error: ${response.status} ${text}`);
|
|
3423
|
+
}
|
|
3424
|
+
const reader = response.body?.getReader();
|
|
3425
|
+
if (!reader) throw new Error("No response body");
|
|
3426
|
+
const decoder = new TextDecoder();
|
|
3427
|
+
let buffer = "";
|
|
3428
|
+
let fullText = "";
|
|
3429
|
+
const toolCalls = [];
|
|
3430
|
+
let toolCallIndex = 0;
|
|
3431
|
+
let lastUsage;
|
|
3432
|
+
while (true) {
|
|
3433
|
+
const { done, value } = await reader.read();
|
|
3434
|
+
if (done) break;
|
|
3435
|
+
buffer += decoder.decode(value, { stream: true });
|
|
3436
|
+
while (buffer.includes("\n")) {
|
|
3437
|
+
const lineEnd = buffer.indexOf("\n");
|
|
3438
|
+
const line = buffer.slice(0, lineEnd).trim();
|
|
3439
|
+
buffer = buffer.slice(lineEnd + 1);
|
|
3440
|
+
if (!line.startsWith("data: ")) continue;
|
|
3441
|
+
const jsonStr = line.slice(6);
|
|
3442
|
+
if (jsonStr === "[DONE]") continue;
|
|
3443
|
+
let parsed;
|
|
3444
|
+
try {
|
|
3445
|
+
parsed = JSON.parse(jsonStr);
|
|
3446
|
+
} catch {
|
|
3447
|
+
continue;
|
|
3448
|
+
}
|
|
3449
|
+
const candidates = parsed.candidates ?? [];
|
|
3450
|
+
const candidate = candidates[0];
|
|
3451
|
+
if (!candidate) continue;
|
|
3452
|
+
const parts = candidate.content?.parts ?? [];
|
|
3453
|
+
for (const part of parts) {
|
|
3454
|
+
if (typeof part.text === "string" && part.text) {
|
|
3455
|
+
if (part.thought === true) continue;
|
|
3456
|
+
fullText += part.text;
|
|
3457
|
+
yield { type: "text_delta", content: part.text };
|
|
3458
|
+
}
|
|
3459
|
+
if (part.functionCall) {
|
|
3460
|
+
const fc = part.functionCall;
|
|
3461
|
+
const id = `call_${crypto.randomUUID().slice(0, 8)}`;
|
|
3462
|
+
const args = JSON.stringify(fc.args ?? {});
|
|
3463
|
+
toolCalls.push({ id, name: fc.name, arguments: args });
|
|
3464
|
+
yield { type: "tool_call_start", index: toolCallIndex, id, name: fc.name };
|
|
3465
|
+
yield { type: "tool_call_delta", index: toolCallIndex, arguments: args };
|
|
3466
|
+
toolCallIndex++;
|
|
3467
|
+
}
|
|
3468
|
+
}
|
|
3469
|
+
const usage = parsed.usageMetadata;
|
|
3470
|
+
if (usage) {
|
|
3471
|
+
lastUsage = {
|
|
3472
|
+
promptTokens: usage.promptTokenCount ?? 0,
|
|
3473
|
+
completionTokens: usage.candidatesTokenCount ?? 0,
|
|
3474
|
+
totalTokens: usage.totalTokenCount ?? 0,
|
|
3475
|
+
cachedTokens: usage.cachedContentTokenCount ?? 0,
|
|
3476
|
+
reasoningTokens: usage.thoughtsTokenCount ?? 0
|
|
3477
|
+
};
|
|
3478
|
+
}
|
|
3479
|
+
}
|
|
3480
|
+
}
|
|
3481
|
+
yield {
|
|
3482
|
+
type: "done",
|
|
3483
|
+
content: fullText,
|
|
3484
|
+
toolCalls,
|
|
3485
|
+
usage: lastUsage
|
|
3486
|
+
};
|
|
3487
|
+
}
|
|
3488
|
+
return {
|
|
3489
|
+
kind: "gemini_api_key",
|
|
3490
|
+
callLLM,
|
|
3491
|
+
callLLMStreaming
|
|
3492
|
+
};
|
|
3493
|
+
}
|
|
3494
|
+
|
|
3088
3495
|
// src/lib/llm/provider-factory.ts
|
|
3089
3496
|
async function createProviderFromStoredAuth(options) {
|
|
3090
3497
|
const resolved = await resolveConfiguredProvider({ homeDir: options?.homeDir });
|
|
3091
3498
|
if (!resolved) {
|
|
3092
3499
|
throw new Error(
|
|
3093
|
-
"No
|
|
3500
|
+
"No credentials found. Run /auth (OpenAI), /auth-gemini (Google), or /config apikey <key>."
|
|
3094
3501
|
);
|
|
3095
3502
|
}
|
|
3096
3503
|
if (resolved.kind === "openai_auth") {
|
|
@@ -3117,7 +3524,32 @@ async function createProviderFromStoredAuth(options) {
|
|
|
3117
3524
|
}
|
|
3118
3525
|
);
|
|
3119
3526
|
}
|
|
3120
|
-
|
|
3527
|
+
if (resolved.kind === "openai_api_key") {
|
|
3528
|
+
return createOpenAIAPIKeyProvider(resolved.apiKey);
|
|
3529
|
+
}
|
|
3530
|
+
if (resolved.kind === "gemini_auth") {
|
|
3531
|
+
const stored = resolved.stored;
|
|
3532
|
+
return createGeminiAuthProvider(
|
|
3533
|
+
{
|
|
3534
|
+
accessToken: stored.tokens.access,
|
|
3535
|
+
refreshToken: stored.tokens.refresh,
|
|
3536
|
+
expiresAt: stored.tokens.expires,
|
|
3537
|
+
email: stored.tokens.email,
|
|
3538
|
+
projectId: stored.tokens.projectId
|
|
3539
|
+
},
|
|
3540
|
+
async (newCreds) => {
|
|
3541
|
+
stored.tokens.access = newCreds.accessToken;
|
|
3542
|
+
stored.tokens.refresh = newCreds.refreshToken;
|
|
3543
|
+
stored.tokens.expires = newCreds.expiresAt;
|
|
3544
|
+
stored.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3545
|
+
await saveGeminiAuth(stored, { homeDir: options?.homeDir });
|
|
3546
|
+
}
|
|
3547
|
+
);
|
|
3548
|
+
}
|
|
3549
|
+
if (resolved.kind === "gemini_api_key") {
|
|
3550
|
+
return createGeminiAPIKeyProvider(resolved.apiKey);
|
|
3551
|
+
}
|
|
3552
|
+
throw new Error("Unknown provider kind.");
|
|
3121
3553
|
}
|
|
3122
3554
|
|
|
3123
3555
|
// src/lib/agent/tool-schemas.ts
|
|
@@ -3219,14 +3651,24 @@ var TOOL_SCHEMAS = [
|
|
|
3219
3651
|
type: "function",
|
|
3220
3652
|
function: {
|
|
3221
3653
|
name: "write_new_file",
|
|
3222
|
-
description:
|
|
3654
|
+
description: [
|
|
3655
|
+
"Create a new workspace file. The key determines where the file is placed:",
|
|
3656
|
+
"- `note:<descriptive-slug>` \u2192 notes/<slug>.md \u2014 analysis, summaries, briefs, memos",
|
|
3657
|
+
"- `paper:<descriptive-slug>` \u2192 papers/<slug>.tex \u2014 LaTeX drafts and manuscripts",
|
|
3658
|
+
"- `experiment:<descriptive-slug>` \u2192 experiments/<slug>.json \u2014 experiment configs and results",
|
|
3659
|
+
"- `source:<descriptive-slug>` \u2192 sources/<slug>.md \u2014 extracted source material",
|
|
3660
|
+
"- `path:<relative/path.ext>` \u2192 exact path \u2014 scripts, configs, data files, any custom location",
|
|
3661
|
+
"Use path: for code files (e.g. `path:scripts/analyze.py`, `path:data/results.csv`).",
|
|
3662
|
+
"Use descriptive slugs, not UUIDs: `note:transformer-scaling-laws` not `note:abc123`.",
|
|
3663
|
+
'Use the folder param to organize within managed directories (e.g. folder: "lit-review").'
|
|
3664
|
+
].join(" "),
|
|
3223
3665
|
parameters: {
|
|
3224
3666
|
type: "object",
|
|
3225
3667
|
properties: {
|
|
3226
|
-
key: { type: "string" },
|
|
3227
|
-
label: { type: "string" },
|
|
3668
|
+
key: { type: "string", description: "File key with prefix determining placement: note:<slug>, paper:<slug>, experiment:<slug>, source:<slug>, or path:<relative/path>" },
|
|
3669
|
+
label: { type: "string", description: "Human-readable display name for the file" },
|
|
3228
3670
|
content: { type: "string" },
|
|
3229
|
-
folder: { type: "string" }
|
|
3671
|
+
folder: { type: "string", description: "Optional subfolder within the managed directory for organization" }
|
|
3230
3672
|
},
|
|
3231
3673
|
required: ["key", "label", "content"],
|
|
3232
3674
|
additionalProperties: false
|
|
@@ -3377,32 +3819,56 @@ var TOOL_SCHEMAS = [
|
|
|
3377
3819
|
type: "function",
|
|
3378
3820
|
function: {
|
|
3379
3821
|
name: "ask_user",
|
|
3380
|
-
description: "Ask the user
|
|
3822
|
+
description: "Ask the user one or more questions and wait for their responses. Use when you need clarification, a decision, or confirmation before proceeding. You can batch up to 4 related questions in a single call \u2014 the user answers them all at once. Provide predefined options when possible. The user can arrow-key select or type a custom answer.",
|
|
3381
3823
|
parameters: {
|
|
3382
3824
|
type: "object",
|
|
3383
3825
|
properties: {
|
|
3826
|
+
questions: {
|
|
3827
|
+
type: "array",
|
|
3828
|
+
minItems: 1,
|
|
3829
|
+
maxItems: 4,
|
|
3830
|
+
items: {
|
|
3831
|
+
type: "object",
|
|
3832
|
+
properties: {
|
|
3833
|
+
question: {
|
|
3834
|
+
type: "string",
|
|
3835
|
+
description: "Clear, specific question. State what you need to know and why."
|
|
3836
|
+
},
|
|
3837
|
+
options: {
|
|
3838
|
+
type: "array",
|
|
3839
|
+
items: {
|
|
3840
|
+
type: "object",
|
|
3841
|
+
properties: {
|
|
3842
|
+
label: { type: "string", description: "Short option label (1-5 words)" },
|
|
3843
|
+
description: { type: "string", description: "One-sentence explanation of what this choice means" }
|
|
3844
|
+
},
|
|
3845
|
+
required: ["label", "description"]
|
|
3846
|
+
},
|
|
3847
|
+
description: "Predefined options. Include 2-5 choices. The user can also type a custom answer."
|
|
3848
|
+
}
|
|
3849
|
+
},
|
|
3850
|
+
required: ["question"]
|
|
3851
|
+
},
|
|
3852
|
+
description: "One or more questions to ask. Batch related questions together (max 4)."
|
|
3853
|
+
},
|
|
3854
|
+
// Legacy single-question support (backward compat)
|
|
3384
3855
|
question: {
|
|
3385
3856
|
type: "string",
|
|
3386
|
-
description: "
|
|
3857
|
+
description: "Single question (shorthand). Use 'questions' array for multiple."
|
|
3387
3858
|
},
|
|
3388
3859
|
options: {
|
|
3389
3860
|
type: "array",
|
|
3390
3861
|
items: {
|
|
3391
3862
|
type: "object",
|
|
3392
3863
|
properties: {
|
|
3393
|
-
label: { type: "string"
|
|
3394
|
-
description: { type: "string"
|
|
3864
|
+
label: { type: "string" },
|
|
3865
|
+
description: { type: "string" }
|
|
3395
3866
|
},
|
|
3396
3867
|
required: ["label", "description"]
|
|
3397
3868
|
},
|
|
3398
|
-
description: "
|
|
3399
|
-
},
|
|
3400
|
-
allow_custom: {
|
|
3401
|
-
type: "boolean",
|
|
3402
|
-
description: "Whether the user can type a custom answer. Default: true."
|
|
3869
|
+
description: "Options for single question (shorthand)."
|
|
3403
3870
|
}
|
|
3404
3871
|
},
|
|
3405
|
-
required: ["question"],
|
|
3406
3872
|
additionalProperties: false
|
|
3407
3873
|
}
|
|
3408
3874
|
}
|
|
@@ -3668,7 +4134,7 @@ Root: ${process.cwd()}`,
|
|
|
3668
4134
|
}
|
|
3669
4135
|
|
|
3670
4136
|
// src/lib/agent/tools/read-file.ts
|
|
3671
|
-
import
|
|
4137
|
+
import fs6 from "fs/promises";
|
|
3672
4138
|
import { createReadStream } from "fs";
|
|
3673
4139
|
import path6 from "path";
|
|
3674
4140
|
import os2 from "os";
|
|
@@ -3732,7 +4198,7 @@ function isBinaryByExtension(filePath) {
|
|
|
3732
4198
|
return BINARY_EXTENSIONS.has(path6.extname(filePath).toLowerCase());
|
|
3733
4199
|
}
|
|
3734
4200
|
async function isBinaryByContent(filePath) {
|
|
3735
|
-
const handle = await
|
|
4201
|
+
const handle = await fs6.open(filePath, "r");
|
|
3736
4202
|
try {
|
|
3737
4203
|
const buf = Buffer.alloc(4096);
|
|
3738
4204
|
const { bytesRead } = await handle.read(buf, 0, 4096, 0);
|
|
@@ -3797,7 +4263,7 @@ async function executeReadFile(args, ctx) {
|
|
|
3797
4263
|
const resolved = path6.isAbsolute(filePath) ? filePath : path6.resolve(filePath);
|
|
3798
4264
|
let stat;
|
|
3799
4265
|
try {
|
|
3800
|
-
stat = await
|
|
4266
|
+
stat = await fs6.stat(resolved);
|
|
3801
4267
|
} catch {
|
|
3802
4268
|
if (filePath in ctx.workspaceFiles) {
|
|
3803
4269
|
return formatFromString(filePath, ctx.workspaceFiles[filePath], offset, limit);
|
|
@@ -3805,7 +4271,7 @@ async function executeReadFile(args, ctx) {
|
|
|
3805
4271
|
return `Error: File not found: ${args.file_path}`;
|
|
3806
4272
|
}
|
|
3807
4273
|
if (stat.isDirectory()) {
|
|
3808
|
-
const entries = await
|
|
4274
|
+
const entries = await fs6.readdir(resolved, { withFileTypes: true });
|
|
3809
4275
|
const lines = entries.sort((a, b) => {
|
|
3810
4276
|
if (a.isDirectory() !== b.isDirectory()) return a.isDirectory() ? -1 : 1;
|
|
3811
4277
|
return a.name.localeCompare(b.name);
|
|
@@ -3869,7 +4335,7 @@ ${outputLines.join("\n")}${footer}
|
|
|
3869
4335
|
}
|
|
3870
4336
|
|
|
3871
4337
|
// src/lib/agent/tools/list-directory.ts
|
|
3872
|
-
import
|
|
4338
|
+
import fs7 from "fs/promises";
|
|
3873
4339
|
import path7 from "path";
|
|
3874
4340
|
var MAX_ENTRIES = 200;
|
|
3875
4341
|
var DEFAULT_IGNORE = /* @__PURE__ */ new Set([
|
|
@@ -3887,7 +4353,7 @@ var DEFAULT_IGNORE = /* @__PURE__ */ new Set([
|
|
|
3887
4353
|
".env"
|
|
3888
4354
|
]);
|
|
3889
4355
|
async function listEntries(dirPath, ignore) {
|
|
3890
|
-
const entries = await
|
|
4356
|
+
const entries = await fs7.readdir(dirPath, { withFileTypes: true });
|
|
3891
4357
|
const results = [];
|
|
3892
4358
|
for (const entry of entries) {
|
|
3893
4359
|
if (ignore.has(entry.name) || entry.name.startsWith(".") && DEFAULT_IGNORE.has(entry.name)) {
|
|
@@ -3896,7 +4362,7 @@ async function listEntries(dirPath, ignore) {
|
|
|
3896
4362
|
let size = 0;
|
|
3897
4363
|
if (!entry.isDirectory()) {
|
|
3898
4364
|
try {
|
|
3899
|
-
const stat = await
|
|
4365
|
+
const stat = await fs7.stat(path7.join(dirPath, entry.name));
|
|
3900
4366
|
size = stat.size;
|
|
3901
4367
|
} catch {
|
|
3902
4368
|
}
|
|
@@ -3954,7 +4420,7 @@ async function executeListDirectory(args) {
|
|
|
3954
4420
|
const maxDepth = Math.min(args.depth ?? 2, 5);
|
|
3955
4421
|
const ignore = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...args.ignore ?? []]);
|
|
3956
4422
|
try {
|
|
3957
|
-
const stat = await
|
|
4423
|
+
const stat = await fs7.stat(dirPath);
|
|
3958
4424
|
if (!stat.isDirectory()) {
|
|
3959
4425
|
return `Error: ${dirPath} is not a directory.`;
|
|
3960
4426
|
}
|
|
@@ -3969,7 +4435,7 @@ async function executeListDirectory(args) {
|
|
|
3969
4435
|
|
|
3970
4436
|
// src/lib/agent/tools/run-command.ts
|
|
3971
4437
|
import { spawn } from "child_process";
|
|
3972
|
-
import
|
|
4438
|
+
import fs8 from "fs/promises";
|
|
3973
4439
|
import path8 from "path";
|
|
3974
4440
|
var DEFAULT_TIMEOUT_MS = 2 * 60 * 1e3;
|
|
3975
4441
|
var MAX_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
@@ -3981,7 +4447,7 @@ async function executeRunCommand(args, signal) {
|
|
|
3981
4447
|
}
|
|
3982
4448
|
const workdir = args.workdir ? path8.isAbsolute(args.workdir) ? args.workdir : path8.resolve(args.workdir) : process.cwd();
|
|
3983
4449
|
try {
|
|
3984
|
-
const stat = await
|
|
4450
|
+
const stat = await fs8.stat(workdir);
|
|
3985
4451
|
if (!stat.isDirectory()) {
|
|
3986
4452
|
return `Error: workdir is not a directory: ${workdir}`;
|
|
3987
4453
|
}
|
|
@@ -4116,36 +4582,52 @@ function clearPendingQuestion() {
|
|
|
4116
4582
|
function resetPendingQuestions() {
|
|
4117
4583
|
pendingQuestions = [];
|
|
4118
4584
|
}
|
|
4585
|
+
function normalizeQuestions(args) {
|
|
4586
|
+
if (args.questions && args.questions.length > 0) {
|
|
4587
|
+
return args.questions.slice(0, 4);
|
|
4588
|
+
}
|
|
4589
|
+
if (args.question) {
|
|
4590
|
+
return [{ question: args.question, options: args.options }];
|
|
4591
|
+
}
|
|
4592
|
+
return [];
|
|
4593
|
+
}
|
|
4119
4594
|
async function executeAskUser(args, signal) {
|
|
4120
|
-
const
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4595
|
+
const items = normalizeQuestions(args);
|
|
4596
|
+
if (items.length === 0) {
|
|
4597
|
+
return "Error: no question provided.";
|
|
4598
|
+
}
|
|
4599
|
+
const answers = [];
|
|
4600
|
+
for (const item of items) {
|
|
4601
|
+
const questionId = crypto.randomUUID();
|
|
4602
|
+
const question = {
|
|
4603
|
+
id: questionId,
|
|
4604
|
+
question: item.question,
|
|
4605
|
+
options: (item.options ?? []).map((o) => ({
|
|
4606
|
+
label: o.label,
|
|
4607
|
+
description: o.description
|
|
4608
|
+
})),
|
|
4609
|
+
allowCustom: true
|
|
4610
|
+
};
|
|
4611
|
+
const answer = await new Promise((resolve, reject) => {
|
|
4612
|
+
pendingQuestions.push({ question, resolve });
|
|
4613
|
+
if (signal) {
|
|
4614
|
+
const onAbort = () => {
|
|
4615
|
+
pendingQuestions = pendingQuestions.filter((q) => q.question.id !== questionId);
|
|
4616
|
+
reject(new Error("Question cancelled \u2014 user interrupted."));
|
|
4617
|
+
};
|
|
4618
|
+
if (signal.aborted) {
|
|
4619
|
+
onAbort();
|
|
4620
|
+
return;
|
|
4621
|
+
}
|
|
4622
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
4140
4623
|
}
|
|
4141
|
-
|
|
4142
|
-
}
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
return `User answered: "${answer.answer}"`;
|
|
4624
|
+
});
|
|
4625
|
+
const prefix = items.length > 1 ? `Q${answers.length + 1}: "${item.question}" \u2192 ` : "";
|
|
4626
|
+
answers.push(
|
|
4627
|
+
answer.isCustom ? `${prefix}User answered: "${answer.answer}"` : `${prefix}User selected: "${answer.answer}"`
|
|
4628
|
+
);
|
|
4147
4629
|
}
|
|
4148
|
-
return
|
|
4630
|
+
return answers.join("\n");
|
|
4149
4631
|
}
|
|
4150
4632
|
|
|
4151
4633
|
// src/lib/agent/tools/search-workspace.ts
|
|
@@ -4242,8 +4724,15 @@ function normalizeDbBackedKey(key) {
|
|
|
4242
4724
|
}
|
|
4243
4725
|
return `${prefix}:${crypto.randomUUID()}`;
|
|
4244
4726
|
}
|
|
4727
|
+
var VALID_PREFIXES = ["note:", "paper:", "experiment:", "source:", "path:"];
|
|
4245
4728
|
function executeWriteNewFile(args, ctx) {
|
|
4246
4729
|
const normalizedKey = normalizeDbBackedKey(args.key);
|
|
4730
|
+
if (!VALID_PREFIXES.some((p) => normalizedKey.startsWith(p))) {
|
|
4731
|
+
return {
|
|
4732
|
+
result: `Error: Key "${normalizedKey}" is missing a required prefix. Use one of: note:<slug>, paper:<slug>, experiment:<slug>, source:<slug>, or path:<relative/path>. Example: note:${normalizedKey}`,
|
|
4733
|
+
update: null
|
|
4734
|
+
};
|
|
4735
|
+
}
|
|
4247
4736
|
if (normalizedKey in ctx.workspaceFiles) {
|
|
4248
4737
|
return {
|
|
4249
4738
|
result: `Error: File "${normalizedKey}" already exists. Use update_existing_file to modify it.`,
|
|
@@ -4274,6 +4763,7 @@ function executeUpdateExistingFile(args, ctx) {
|
|
|
4274
4763
|
};
|
|
4275
4764
|
}
|
|
4276
4765
|
const mode = args.mode ?? "rewrite";
|
|
4766
|
+
const oldContent = ctx.workspaceFiles[args.key];
|
|
4277
4767
|
if (mode === "rewrite") {
|
|
4278
4768
|
const content = args.content;
|
|
4279
4769
|
if (content == null) {
|
|
@@ -4287,6 +4777,7 @@ function executeUpdateExistingFile(args, ctx) {
|
|
|
4287
4777
|
type: "edit",
|
|
4288
4778
|
key: args.key,
|
|
4289
4779
|
content,
|
|
4780
|
+
oldContent,
|
|
4290
4781
|
summary: args.summary
|
|
4291
4782
|
};
|
|
4292
4783
|
return {
|
|
@@ -4350,6 +4841,7 @@ ${preview}`,
|
|
|
4350
4841
|
type: "edit",
|
|
4351
4842
|
key: args.key,
|
|
4352
4843
|
content: currentContent,
|
|
4844
|
+
oldContent,
|
|
4353
4845
|
summary: autoSummary
|
|
4354
4846
|
};
|
|
4355
4847
|
return {
|
|
@@ -4391,7 +4883,7 @@ function executeCreatePaper(args, ctx) {
|
|
|
4391
4883
|
|
|
4392
4884
|
// src/lib/agent/tools/read-pdf.ts
|
|
4393
4885
|
import path9 from "path";
|
|
4394
|
-
import
|
|
4886
|
+
import fs9 from "fs/promises";
|
|
4395
4887
|
var MAX_OUTPUT_BYTES3 = 5e4;
|
|
4396
4888
|
function parsePages(pages) {
|
|
4397
4889
|
const range = pages.match(/^(\d+)\s*-\s*(\d+)$/);
|
|
@@ -4402,7 +4894,7 @@ function parsePages(pages) {
|
|
|
4402
4894
|
}
|
|
4403
4895
|
async function executeReadPdf(args) {
|
|
4404
4896
|
const resolved = path9.resolve(args.file_path);
|
|
4405
|
-
const stat = await
|
|
4897
|
+
const stat = await fs9.stat(resolved).catch(() => null);
|
|
4406
4898
|
if (!stat || !stat.isFile()) {
|
|
4407
4899
|
return `Error: file not found: ${resolved}`;
|
|
4408
4900
|
}
|
|
@@ -5589,7 +6081,7 @@ ${summary}`,
|
|
|
5589
6081
|
}
|
|
5590
6082
|
|
|
5591
6083
|
// src/lib/skills/runtime.ts
|
|
5592
|
-
import
|
|
6084
|
+
import fs10 from "fs/promises";
|
|
5593
6085
|
import path10 from "path";
|
|
5594
6086
|
import matter2 from "gray-matter";
|
|
5595
6087
|
async function loadRuntimeSkillByName(input2) {
|
|
@@ -5598,7 +6090,7 @@ async function loadRuntimeSkillByName(input2) {
|
|
|
5598
6090
|
if (!match) {
|
|
5599
6091
|
return null;
|
|
5600
6092
|
}
|
|
5601
|
-
const raw = await
|
|
6093
|
+
const raw = await fs10.readFile(path10.join(match.skillDir, "SKILL.md"), "utf8");
|
|
5602
6094
|
const parsed = matter2(raw);
|
|
5603
6095
|
return {
|
|
5604
6096
|
id: match.name,
|
|
@@ -5610,14 +6102,15 @@ async function loadRuntimeSkillByName(input2) {
|
|
|
5610
6102
|
}
|
|
5611
6103
|
async function readSkillReferenceFile(skillDir, referencePath) {
|
|
5612
6104
|
const fullPath = path10.join(skillDir, "references", referencePath);
|
|
5613
|
-
return
|
|
6105
|
+
return fs10.readFile(fullPath, "utf8");
|
|
5614
6106
|
}
|
|
5615
6107
|
|
|
5616
6108
|
// src/lib/agent/subagent/configs.ts
|
|
5617
6109
|
var exploreConfig = {
|
|
5618
6110
|
id: "explore",
|
|
5619
6111
|
name: "Explore",
|
|
5620
|
-
model:
|
|
6112
|
+
model: void 0,
|
|
6113
|
+
// Resolved at runtime from provider catalog (backgroundModel)
|
|
5621
6114
|
reasoningEffort: "high",
|
|
5622
6115
|
allowedTools: /* @__PURE__ */ new Set([
|
|
5623
6116
|
"read_file",
|
|
@@ -5714,7 +6207,7 @@ ${input2.context}` : input2.goal;
|
|
|
5714
6207
|
for await (const chunk of input2.provider.callLLMStreaming({
|
|
5715
6208
|
messages,
|
|
5716
6209
|
tools,
|
|
5717
|
-
model: config.model,
|
|
6210
|
+
model: config.model ?? getProviderCatalog(input2.provider.kind).backgroundModel,
|
|
5718
6211
|
reasoningEffort: config.reasoningEffort,
|
|
5719
6212
|
signal: input2.signal
|
|
5720
6213
|
})) {
|
|
@@ -5775,7 +6268,7 @@ ${input2.context}` : input2.goal;
|
|
|
5775
6268
|
let finalText = "";
|
|
5776
6269
|
for await (const chunk of input2.provider.callLLMStreaming({
|
|
5777
6270
|
messages,
|
|
5778
|
-
model: config.model,
|
|
6271
|
+
model: config.model ?? getProviderCatalog(input2.provider.kind).backgroundModel,
|
|
5779
6272
|
reasoningEffort: config.reasoningEffort,
|
|
5780
6273
|
signal: input2.signal
|
|
5781
6274
|
})) {
|
|
@@ -5816,7 +6309,7 @@ function describeSubAgentTool(name, args) {
|
|
|
5816
6309
|
|
|
5817
6310
|
// src/lib/agent/tools/tasks.ts
|
|
5818
6311
|
import path11 from "path";
|
|
5819
|
-
import
|
|
6312
|
+
import fs11 from "fs/promises";
|
|
5820
6313
|
var tasks = [];
|
|
5821
6314
|
var storePath = null;
|
|
5822
6315
|
function shortId() {
|
|
@@ -5826,9 +6319,9 @@ async function persist() {
|
|
|
5826
6319
|
if (!storePath) return;
|
|
5827
6320
|
const live = tasks.filter((t) => t.status !== "deleted");
|
|
5828
6321
|
const tmpPath = storePath + ".tmp";
|
|
5829
|
-
await
|
|
5830
|
-
await
|
|
5831
|
-
await
|
|
6322
|
+
await fs11.mkdir(path11.dirname(storePath), { recursive: true });
|
|
6323
|
+
await fs11.writeFile(tmpPath, JSON.stringify({ version: 1, tasks: live }, null, 2));
|
|
6324
|
+
await fs11.rename(tmpPath, storePath);
|
|
5832
6325
|
}
|
|
5833
6326
|
async function initTaskStore(workspaceDir) {
|
|
5834
6327
|
storePath = path11.join(workspaceDir, ".open-research", "tasks.json");
|
|
@@ -5960,7 +6453,7 @@ async function executeTool(name, args, ctx, activeSkills, homeDir, signal, provi
|
|
|
5960
6453
|
return { result: out.result, searchResults: out.sources };
|
|
5961
6454
|
}
|
|
5962
6455
|
case "web_search": {
|
|
5963
|
-
const { executeWebSearch } = await import("./web-search-
|
|
6456
|
+
const { executeWebSearch } = await import("./web-search-TKBFSU3M.js");
|
|
5964
6457
|
const out = await executeWebSearch(
|
|
5965
6458
|
args,
|
|
5966
6459
|
provider
|
|
@@ -6039,7 +6532,7 @@ ${meta}` };
|
|
|
6039
6532
|
if (!provider) {
|
|
6040
6533
|
return { result: "Error: query_ontology requires an LLM provider." };
|
|
6041
6534
|
}
|
|
6042
|
-
const { runQueryAgent } = await import("./query-agent-
|
|
6535
|
+
const { runQueryAgent } = await import("./query-agent-WM6UNZ37.js");
|
|
6043
6536
|
const workspaceDir = ctx.workspaceDir ?? process.cwd();
|
|
6044
6537
|
const answer = await runQueryAgent({
|
|
6045
6538
|
query: String(args.query ?? ""),
|
|
@@ -6101,7 +6594,9 @@ var MODEL_CONTEXT_WINDOWS = {
|
|
|
6101
6594
|
"gpt-4o": 128e3,
|
|
6102
6595
|
"gpt-5.4-mini": 128e3,
|
|
6103
6596
|
"o3": 2e5,
|
|
6104
|
-
"o4-mini": 2e5
|
|
6597
|
+
"o4-mini": 2e5,
|
|
6598
|
+
"gemini-3.1-pro-preview": 1048576,
|
|
6599
|
+
"gemini-3-flash-preview": 1048576
|
|
6105
6600
|
};
|
|
6106
6601
|
var DEFAULT_CONTEXT_WINDOW = 128e3;
|
|
6107
6602
|
var AUTO_COMPACT_TOKEN_LIMIT = 25e4;
|
|
@@ -6315,7 +6810,7 @@ async function manualCompact(messages, model, provider, usage, customInstruction
|
|
|
6315
6810
|
}
|
|
6316
6811
|
|
|
6317
6812
|
// src/lib/memory/store.ts
|
|
6318
|
-
import
|
|
6813
|
+
import fs12 from "fs/promises";
|
|
6319
6814
|
import path12 from "path";
|
|
6320
6815
|
function getGlobalMemoryFile(options) {
|
|
6321
6816
|
return path12.join(getOpenResearchRoot(options), "memory.json");
|
|
@@ -6325,7 +6820,7 @@ function getProjectMemoryFile(workspaceDir) {
|
|
|
6325
6820
|
}
|
|
6326
6821
|
async function loadMemoryFile(filePath) {
|
|
6327
6822
|
try {
|
|
6328
|
-
const raw = await
|
|
6823
|
+
const raw = await fs12.readFile(filePath, "utf8");
|
|
6329
6824
|
const store = JSON.parse(raw);
|
|
6330
6825
|
return store.memories ?? [];
|
|
6331
6826
|
} catch {
|
|
@@ -6333,9 +6828,9 @@ async function loadMemoryFile(filePath) {
|
|
|
6333
6828
|
}
|
|
6334
6829
|
}
|
|
6335
6830
|
async function saveMemoryFile(filePath, memories) {
|
|
6336
|
-
await
|
|
6831
|
+
await fs12.mkdir(path12.dirname(filePath), { recursive: true });
|
|
6337
6832
|
const store = { version: 2, memories };
|
|
6338
|
-
await
|
|
6833
|
+
await fs12.writeFile(filePath, JSON.stringify(store, null, 2), "utf8");
|
|
6339
6834
|
}
|
|
6340
6835
|
async function loadGlobalMemories(options) {
|
|
6341
6836
|
const mems = await loadMemoryFile(getGlobalMemoryFile(options));
|
|
@@ -6667,18 +7162,18 @@ async function extractAndStoreMemories(input2) {
|
|
|
6667
7162
|
}
|
|
6668
7163
|
|
|
6669
7164
|
// src/lib/workspace/agents-md.ts
|
|
6670
|
-
import
|
|
7165
|
+
import fs13 from "fs/promises";
|
|
6671
7166
|
import path13 from "path";
|
|
6672
7167
|
var AGENTS_MD_FILENAME = "AGENTS.md";
|
|
6673
7168
|
async function readAgentsMd(workspaceDir) {
|
|
6674
7169
|
try {
|
|
6675
|
-
return await
|
|
7170
|
+
return await fs13.readFile(path13.join(workspaceDir, AGENTS_MD_FILENAME), "utf8");
|
|
6676
7171
|
} catch {
|
|
6677
7172
|
return "";
|
|
6678
7173
|
}
|
|
6679
7174
|
}
|
|
6680
7175
|
async function writeAgentsMd(workspaceDir, content) {
|
|
6681
|
-
await
|
|
7176
|
+
await fs13.writeFile(path13.join(workspaceDir, AGENTS_MD_FILENAME), content, "utf8");
|
|
6682
7177
|
}
|
|
6683
7178
|
var UPDATE_SYSTEM_PROMPT = `You maintain an AGENTS.md file for a research workspace. This file gives future agent sessions instant context about the project \u2014 what it's about, what's been done, key files, and current direction.
|
|
6684
7179
|
|
|
@@ -6857,6 +7352,17 @@ ${skill.prompt}`).join("\n\n");
|
|
|
6857
7352
|
"- Redirect large outputs to files. Read selectively \u2014 don't dump entire datasets into responses.",
|
|
6858
7353
|
"- Always wrap file paths in backticks: `notes/brief.md`, `experiments/analysis.py:42`.",
|
|
6859
7354
|
"",
|
|
7355
|
+
"## Workspace Organization",
|
|
7356
|
+
"The workspace has managed directories. When creating files with `write_new_file`, use the correct key prefix:",
|
|
7357
|
+
"- `note:<slug>` \u2192 `notes/` \u2014 analysis write-ups, literature reviews, meeting notes, briefs",
|
|
7358
|
+
"- `paper:<slug>` \u2192 `papers/` \u2014 LaTeX manuscripts and drafts",
|
|
7359
|
+
"- `experiment:<slug>` \u2192 `experiments/` \u2014 experiment definitions, configs, results",
|
|
7360
|
+
"- `source:<slug>` \u2192 `sources/` \u2014 extracted text from papers, articles, datasets",
|
|
7361
|
+
"- `path:<relative/path>` \u2192 exact location \u2014 scripts, code, CSV data, configs, anything else",
|
|
7362
|
+
"Use descriptive slugs that read naturally: `note:scaling-law-comparison`, `experiment:ablation-dropout-rates`, `path:scripts/parse-arxiv.py`.",
|
|
7363
|
+
"Use the `folder` param to group related files: e.g. key `note:gpt4-findings` with folder `lit-review` creates `notes/lit-review/gpt4-findings.md`.",
|
|
7364
|
+
"Never use bare keys without a prefix \u2014 they end up in `artifacts/` which is not user-facing.",
|
|
7365
|
+
"",
|
|
6860
7366
|
`## Workspace
|
|
6861
7367
|
Root: ${process.cwd()}`,
|
|
6862
7368
|
skillText
|
|
@@ -6881,7 +7387,7 @@ async function runAgentTurn(input2) {
|
|
|
6881
7387
|
const isPlanning = input2.mode === "planning";
|
|
6882
7388
|
const tools = isPlanning ? getToolsForMode("planning") : TOOL_SCHEMAS;
|
|
6883
7389
|
const systemPrompt = isPlanning ? buildPlanningSystemPrompt(input2.workspace, activeSkills) : buildSystemPrompt(input2.workspace, activeSkills);
|
|
6884
|
-
const model = input2.model ??
|
|
7390
|
+
const model = input2.model ?? getProviderCatalog(input2.provider.kind).defaultModel;
|
|
6885
7391
|
const usage = input2.sessionUsage ?? createSessionUsage();
|
|
6886
7392
|
const allMemories = await loadAllMemories({
|
|
6887
7393
|
homeDir: input2.homeDir,
|
|
@@ -6895,7 +7401,7 @@ async function runAgentTurn(input2) {
|
|
|
6895
7401
|
if (input2.workspace.workspaceDir) {
|
|
6896
7402
|
try {
|
|
6897
7403
|
const { loadOntology } = await import("./store-LT5EGDOI.js");
|
|
6898
|
-
const { runRelevanceAgent } = await import("./relevance-agent-
|
|
7404
|
+
const { runRelevanceAgent } = await import("./relevance-agent-H3U6TROD.js");
|
|
6899
7405
|
const { buildScaffoldingContext } = await import("./scaffolding-MSAICMWV.js");
|
|
6900
7406
|
const ontology = await loadOntology(input2.workspace.workspaceDir);
|
|
6901
7407
|
const relevantIds = await runRelevanceAgent({
|
|
@@ -6996,7 +7502,7 @@ ${agentsMd}` : null,
|
|
|
6996
7502
|
});
|
|
6997
7503
|
}
|
|
6998
7504
|
if (input2.workspace.workspaceDir) {
|
|
6999
|
-
import("./manager-queue-
|
|
7505
|
+
import("./manager-queue-FBAUCAGI.js").then(({ enqueueOntologyManager }) => {
|
|
7000
7506
|
enqueueOntologyManager({
|
|
7001
7507
|
userMessage: input2.message,
|
|
7002
7508
|
agentResponse: fullText,
|
|
@@ -7155,31 +7661,33 @@ function classifyUpdateRisk(update) {
|
|
|
7155
7661
|
}
|
|
7156
7662
|
|
|
7157
7663
|
// src/lib/workspace/apply-update.ts
|
|
7158
|
-
import
|
|
7664
|
+
import fs14 from "fs/promises";
|
|
7159
7665
|
import path14 from "path";
|
|
7160
7666
|
function resolveRelativePath(update) {
|
|
7667
|
+
const folder = update.folder;
|
|
7668
|
+
const sub = (dir, name, ext) => folder ? `${dir}/${folder}/${name}${ext}` : `${dir}/${name}${ext}`;
|
|
7161
7669
|
if (update.key.startsWith("path:")) {
|
|
7162
7670
|
return update.key.slice(5);
|
|
7163
7671
|
}
|
|
7164
7672
|
if (update.key.startsWith("note:")) {
|
|
7165
|
-
return
|
|
7673
|
+
return sub("notes", update.key.slice(5), ".md");
|
|
7166
7674
|
}
|
|
7167
7675
|
if (update.key.startsWith("paper:")) {
|
|
7168
|
-
return
|
|
7676
|
+
return sub("papers", update.key.slice(6), ".tex");
|
|
7169
7677
|
}
|
|
7170
7678
|
if (update.key.startsWith("experiment:")) {
|
|
7171
|
-
return
|
|
7679
|
+
return sub("experiments", update.key.slice(11), ".json");
|
|
7172
7680
|
}
|
|
7173
7681
|
if (update.key.startsWith("source:")) {
|
|
7174
|
-
return
|
|
7682
|
+
return sub("sources", update.key.slice(7), ".md");
|
|
7175
7683
|
}
|
|
7176
7684
|
return `artifacts/${update.key}.md`;
|
|
7177
7685
|
}
|
|
7178
7686
|
async function applyProposedUpdate(workspaceDir, update) {
|
|
7179
7687
|
const relativePath = resolveRelativePath(update);
|
|
7180
7688
|
const absolutePath = path14.join(workspaceDir, relativePath);
|
|
7181
|
-
await
|
|
7182
|
-
await
|
|
7689
|
+
await fs14.mkdir(path14.dirname(absolutePath), { recursive: true });
|
|
7690
|
+
await fs14.writeFile(absolutePath, update.content, "utf8");
|
|
7183
7691
|
return absolutePath;
|
|
7184
7692
|
}
|
|
7185
7693
|
|
|
@@ -7397,7 +7905,7 @@ function SessionPicker({ sessions, onSelect, onCancel }) {
|
|
|
7397
7905
|
}
|
|
7398
7906
|
|
|
7399
7907
|
// src/lib/cli/update-check.ts
|
|
7400
|
-
import
|
|
7908
|
+
import fs15 from "fs/promises";
|
|
7401
7909
|
import path15 from "path";
|
|
7402
7910
|
import os3 from "os";
|
|
7403
7911
|
var PACKAGE_NAME = "open-research";
|
|
@@ -7405,7 +7913,7 @@ var CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
|
|
|
7405
7913
|
var STATE_FILE = path15.join(os3.homedir(), ".open-research", "update-check.json");
|
|
7406
7914
|
async function readState() {
|
|
7407
7915
|
try {
|
|
7408
|
-
const raw = await
|
|
7916
|
+
const raw = await fs15.readFile(STATE_FILE, "utf8");
|
|
7409
7917
|
const parsed = JSON.parse(raw);
|
|
7410
7918
|
return {
|
|
7411
7919
|
lastCheck: typeof parsed.lastCheck === "number" ? parsed.lastCheck : 0,
|
|
@@ -7417,8 +7925,8 @@ async function readState() {
|
|
|
7417
7925
|
}
|
|
7418
7926
|
}
|
|
7419
7927
|
async function writeState(state) {
|
|
7420
|
-
await
|
|
7421
|
-
await
|
|
7928
|
+
await fs15.mkdir(path15.dirname(STATE_FILE), { recursive: true });
|
|
7929
|
+
await fs15.writeFile(STATE_FILE, JSON.stringify(state), "utf8");
|
|
7422
7930
|
}
|
|
7423
7931
|
function getCurrentVersion() {
|
|
7424
7932
|
return getPackageVersion();
|
|
@@ -7473,6 +7981,7 @@ async function checkForUpdate() {
|
|
|
7473
7981
|
// src/tui/commands.ts
|
|
7474
7982
|
var SLASH_COMMANDS = [
|
|
7475
7983
|
{ name: "auth", aliases: ["/connect", "/login"], description: "Connect your OpenAI account via browser OAuth", category: "auth" },
|
|
7984
|
+
{ name: "auth-gemini", aliases: ["/login-gemini", "/gemini"], description: "Connect your Google account via browser OAuth (Gemini)", category: "auth" },
|
|
7476
7985
|
{ name: "auth-codex", aliases: ["/import-codex"], description: "Import auth from existing Codex CLI", category: "auth" },
|
|
7477
7986
|
{ name: "auth-status", aliases: [], description: "Check auth connection status", category: "auth" },
|
|
7478
7987
|
{ name: "logout", aliases: [], description: "Clear stored auth", category: "auth" },
|
|
@@ -7579,6 +8088,14 @@ function getUnifiedSuggestions(partial, allSkills) {
|
|
|
7579
8088
|
}));
|
|
7580
8089
|
return [...cmdHits, ...skillHits];
|
|
7581
8090
|
}
|
|
8091
|
+
function extractSlashTrigger(text) {
|
|
8092
|
+
const lastSlash = text.lastIndexOf("/");
|
|
8093
|
+
if (lastSlash === -1) return null;
|
|
8094
|
+
if (lastSlash > 0 && text[lastSlash - 1] !== " ") return null;
|
|
8095
|
+
const after = text.slice(lastSlash + 1);
|
|
8096
|
+
if (after.includes(" ")) return null;
|
|
8097
|
+
return { partial: after.toLowerCase(), start: lastSlash };
|
|
8098
|
+
}
|
|
7582
8099
|
function extractAtMention(text) {
|
|
7583
8100
|
const lastAt = text.lastIndexOf("@");
|
|
7584
8101
|
if (lastAt === -1) return null;
|
|
@@ -7603,7 +8120,9 @@ function truncate3(value, max = 96) {
|
|
|
7603
8120
|
}
|
|
7604
8121
|
|
|
7605
8122
|
// src/tui/components.tsx
|
|
7606
|
-
import {
|
|
8123
|
+
import { memo, useMemo as useMemo3, useState as useState4 } from "react";
|
|
8124
|
+
import { Box as Box4, Text as Text4, useInput as useInput4 } from "ink";
|
|
8125
|
+
import { structuredPatch } from "diff";
|
|
7607
8126
|
|
|
7608
8127
|
// src/tui/layout.ts
|
|
7609
8128
|
var DEFAULT_TERMINAL_WIDTH = 80;
|
|
@@ -7611,11 +8130,19 @@ var MIN_TERMINAL_WIDTH = 20;
|
|
|
7611
8130
|
function getTerminalWidth(columns) {
|
|
7612
8131
|
return Math.max(MIN_TERMINAL_WIDTH, columns ?? process.stdout.columns ?? DEFAULT_TERMINAL_WIDTH);
|
|
7613
8132
|
}
|
|
7614
|
-
function
|
|
8133
|
+
function normalizeObservedTerminalWidth(fallbackWidth, ...columns) {
|
|
7615
8134
|
const observed = columns.filter((value) => typeof value === "number" && Number.isFinite(value) && value > 0);
|
|
7616
|
-
if (observed.length === 0)
|
|
8135
|
+
if (observed.length === 0) {
|
|
8136
|
+
return Math.max(MIN_TERMINAL_WIDTH, fallbackWidth);
|
|
8137
|
+
}
|
|
7617
8138
|
return Math.max(MIN_TERMINAL_WIDTH, Math.min(...observed));
|
|
7618
8139
|
}
|
|
8140
|
+
function getObservedTerminalWidth(...columns) {
|
|
8141
|
+
return normalizeObservedTerminalWidth(DEFAULT_TERMINAL_WIDTH, ...columns);
|
|
8142
|
+
}
|
|
8143
|
+
function getStableObservedTerminalWidth(currentWidth, ...columns) {
|
|
8144
|
+
return normalizeObservedTerminalWidth(currentWidth, ...columns);
|
|
8145
|
+
}
|
|
7619
8146
|
function insetWidth(width, inset) {
|
|
7620
8147
|
return Math.max(1, width - inset);
|
|
7621
8148
|
}
|
|
@@ -7731,7 +8258,7 @@ function renderInline(text, codeColor = source_default.cyan, linkColor = source_
|
|
|
7731
8258
|
}
|
|
7732
8259
|
|
|
7733
8260
|
// src/tui/components.tsx
|
|
7734
|
-
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
8261
|
+
import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
7735
8262
|
var GUTTER = {
|
|
7736
8263
|
user: "\u203A",
|
|
7737
8264
|
agent: "\u25AA",
|
|
@@ -7755,7 +8282,12 @@ function borderedContentWidth(width) {
|
|
|
7755
8282
|
function wrapText(value, width) {
|
|
7756
8283
|
return wrapAnsi(value, Math.max(1, width), { trim: false, hard: true });
|
|
7757
8284
|
}
|
|
7758
|
-
function
|
|
8285
|
+
function Divider({ width, color }) {
|
|
8286
|
+
const theme = useTheme();
|
|
8287
|
+
const w = insetWidth(resolveWidth(width), 4);
|
|
8288
|
+
return /* @__PURE__ */ jsx4(Text4, { color: color ?? theme.muted, dimColor: true, children: "\u2500".repeat(Math.max(1, w)) });
|
|
8289
|
+
}
|
|
8290
|
+
var UserMessage = memo(function UserMessage2({ text, turnNumber, width }) {
|
|
7759
8291
|
const theme = useTheme();
|
|
7760
8292
|
const contentWidth = resolveWidth(width);
|
|
7761
8293
|
const bodyWidth = indentedWidth(contentWidth);
|
|
@@ -7766,12 +8298,18 @@ function UserMessage({ text, width }) {
|
|
|
7766
8298
|
GUTTER.user,
|
|
7767
8299
|
" "
|
|
7768
8300
|
] }),
|
|
7769
|
-
/* @__PURE__ */ jsx4(Text4, { bold: true, color: theme.accent, children: "you" })
|
|
8301
|
+
/* @__PURE__ */ jsx4(Text4, { bold: true, color: theme.accent, children: "you" }),
|
|
8302
|
+
typeof turnNumber === "number" && /* @__PURE__ */ jsxs3(Text4, { color: theme.muted, dimColor: true, children: [
|
|
8303
|
+
" ",
|
|
8304
|
+
GUTTER.system,
|
|
8305
|
+
" #",
|
|
8306
|
+
turnNumber
|
|
8307
|
+
] })
|
|
7770
8308
|
] }),
|
|
7771
8309
|
/* @__PURE__ */ jsx4(Box4, { marginLeft: 2, width: bodyWidth, children: /* @__PURE__ */ jsx4(Text4, { color: theme.text, wrap: "wrap", children: wrappedText }) })
|
|
7772
8310
|
] });
|
|
7773
|
-
}
|
|
7774
|
-
|
|
8311
|
+
});
|
|
8312
|
+
var AgentMessage = memo(function AgentMessage2({ text, width }) {
|
|
7775
8313
|
const contentWidth = resolveWidth(width);
|
|
7776
8314
|
const bodyWidth = indentedWidth(contentWidth);
|
|
7777
8315
|
const theme = useTheme();
|
|
@@ -7787,6 +8325,20 @@ function AgentMessage({ text, width }) {
|
|
|
7787
8325
|
] }),
|
|
7788
8326
|
/* @__PURE__ */ jsx4(Box4, { marginLeft: 2, width: bodyWidth, children: /* @__PURE__ */ jsx4(Text4, { wrap: "wrap", children: wrappedText }) })
|
|
7789
8327
|
] });
|
|
8328
|
+
});
|
|
8329
|
+
function ThinkingIndicator({ frame, width }) {
|
|
8330
|
+
const theme = useTheme();
|
|
8331
|
+
const contentWidth = resolveWidth(width);
|
|
8332
|
+
return /* @__PURE__ */ jsxs3(Box4, { marginBottom: 1, width: contentWidth, children: [
|
|
8333
|
+
/* @__PURE__ */ jsxs3(Text4, { color: theme.secondary, bold: true, children: [
|
|
8334
|
+
GUTTER.agent,
|
|
8335
|
+
" "
|
|
8336
|
+
] }),
|
|
8337
|
+
/* @__PURE__ */ jsxs3(Text4, { color: theme.muted, dimColor: true, children: [
|
|
8338
|
+
frame,
|
|
8339
|
+
" thinking..."
|
|
8340
|
+
] })
|
|
8341
|
+
] });
|
|
7790
8342
|
}
|
|
7791
8343
|
function TaskPanel({
|
|
7792
8344
|
tasks: tasks2,
|
|
@@ -7819,7 +8371,7 @@ function TaskPanel({
|
|
|
7819
8371
|
] })
|
|
7820
8372
|
] });
|
|
7821
8373
|
}
|
|
7822
|
-
|
|
8374
|
+
var ToolActivitySummary = memo(function ToolActivitySummary2({
|
|
7823
8375
|
summary,
|
|
7824
8376
|
tools,
|
|
7825
8377
|
expanded = false,
|
|
@@ -7827,43 +8379,50 @@ function ToolActivitySummary({
|
|
|
7827
8379
|
}) {
|
|
7828
8380
|
const theme = useTheme();
|
|
7829
8381
|
const contentWidth = indentedWidth(resolveWidth(width));
|
|
8382
|
+
const innerWidth = indentedWidth(contentWidth, 4);
|
|
7830
8383
|
if (expanded) {
|
|
7831
|
-
return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "
|
|
7832
|
-
/* @__PURE__ */
|
|
7833
|
-
|
|
7834
|
-
|
|
7835
|
-
|
|
7836
|
-
] }),
|
|
7837
|
-
tools.map((t, i) => {
|
|
7838
|
-
const dur = t.durationMs ? ` (${(t.durationMs / 1e3).toFixed(1)}s)` : "";
|
|
7839
|
-
const prefix = i === tools.length - 1 ? "\u2514" : "\u251C";
|
|
7840
|
-
return /* @__PURE__ */ jsxs3(Text4, { color: theme.muted, dimColor: true, wrap: "wrap", children: [
|
|
7841
|
-
" ",
|
|
7842
|
-
prefix,
|
|
7843
|
-
" ",
|
|
7844
|
-
GUTTER.success,
|
|
8384
|
+
return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "row", marginLeft: 2, marginBottom: 0, width: contentWidth, children: [
|
|
8385
|
+
/* @__PURE__ */ jsx4(Text4, { color: theme.muted, dimColor: true, children: "\u2502 " }),
|
|
8386
|
+
/* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", width: innerWidth, children: [
|
|
8387
|
+
/* @__PURE__ */ jsxs3(Text4, { color: theme.muted, dimColor: true, wrap: "wrap", children: [
|
|
8388
|
+
GUTTER.tool,
|
|
7845
8389
|
" ",
|
|
7846
|
-
|
|
7847
|
-
|
|
7848
|
-
|
|
7849
|
-
|
|
8390
|
+
summary
|
|
8391
|
+
] }),
|
|
8392
|
+
tools.map((t, i) => {
|
|
8393
|
+
const dur = t.durationMs ? ` (${(t.durationMs / 1e3).toFixed(1)}s)` : "";
|
|
8394
|
+
const prefix = i === tools.length - 1 ? "\u2514" : "\u251C";
|
|
8395
|
+
return /* @__PURE__ */ jsxs3(Text4, { color: theme.muted, dimColor: true, wrap: "wrap", children: [
|
|
8396
|
+
" ",
|
|
8397
|
+
prefix,
|
|
8398
|
+
" ",
|
|
8399
|
+
GUTTER.success,
|
|
8400
|
+
" ",
|
|
8401
|
+
t.description,
|
|
8402
|
+
dur
|
|
8403
|
+
] }, i);
|
|
8404
|
+
})
|
|
8405
|
+
] })
|
|
7850
8406
|
] });
|
|
7851
8407
|
}
|
|
7852
8408
|
const lastTarget = tools.length > 0 ? tools[tools.length - 1].description : "";
|
|
7853
8409
|
const hint = tools.length > 1 ? " (ctrl+o to expand)" : "";
|
|
7854
|
-
return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "
|
|
7855
|
-
/* @__PURE__ */
|
|
7856
|
-
|
|
7857
|
-
|
|
7858
|
-
|
|
7859
|
-
|
|
7860
|
-
|
|
7861
|
-
|
|
7862
|
-
|
|
7863
|
-
lastTarget
|
|
8410
|
+
return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "row", marginLeft: 2, marginBottom: 0, width: contentWidth, children: [
|
|
8411
|
+
/* @__PURE__ */ jsx4(Text4, { color: theme.muted, dimColor: true, children: "\u2502 " }),
|
|
8412
|
+
/* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", width: innerWidth, children: [
|
|
8413
|
+
/* @__PURE__ */ jsxs3(Text4, { color: theme.muted, dimColor: true, wrap: "wrap", children: [
|
|
8414
|
+
GUTTER.tool,
|
|
8415
|
+
" ",
|
|
8416
|
+
summary,
|
|
8417
|
+
hint
|
|
8418
|
+
] }),
|
|
8419
|
+
lastTarget && tools.length > 1 && /* @__PURE__ */ jsxs3(Text4, { color: theme.muted, dimColor: true, wrap: "wrap", children: [
|
|
8420
|
+
" \u2514 ",
|
|
8421
|
+
lastTarget
|
|
8422
|
+
] })
|
|
7864
8423
|
] })
|
|
7865
8424
|
] });
|
|
7866
|
-
}
|
|
8425
|
+
});
|
|
7867
8426
|
function SubAgentIndicator({
|
|
7868
8427
|
agentType,
|
|
7869
8428
|
goal,
|
|
@@ -7912,23 +8471,30 @@ function SubAgentIndicator({
|
|
|
7912
8471
|
}
|
|
7913
8472
|
);
|
|
7914
8473
|
}
|
|
7915
|
-
|
|
8474
|
+
var SystemMessage = memo(function SystemMessage2({ text, width }) {
|
|
7916
8475
|
const theme = useTheme();
|
|
7917
8476
|
const contentWidth = resolveWidth(width);
|
|
7918
8477
|
const indentedContentWidth = indentedWidth(contentWidth);
|
|
7919
8478
|
const wrappedIndentedText = wrapText(text, indentedContentWidth);
|
|
7920
8479
|
const wrappedText = wrapText(text, contentWidth);
|
|
7921
|
-
|
|
8480
|
+
const trimmed = text.trimStart();
|
|
8481
|
+
if (trimmed.startsWith("\u2713") || trimmed.startsWith("\u2717")) {
|
|
7922
8482
|
return /* @__PURE__ */ jsx4(Box4, { marginLeft: 2, width: indentedContentWidth, children: /* @__PURE__ */ jsx4(Text4, { color: theme.muted, dimColor: true, wrap: "wrap", children: wrappedIndentedText }) });
|
|
7923
8483
|
}
|
|
7924
|
-
if (
|
|
8484
|
+
if (trimmed.startsWith("Error:") || trimmed.startsWith("Failed:")) {
|
|
8485
|
+
return /* @__PURE__ */ jsx4(Box4, { marginLeft: 2, width: indentedContentWidth, children: /* @__PURE__ */ jsx4(Text4, { color: theme.error, wrap: "wrap", children: wrapText(`${GUTTER.error} ${text.trim()}`, indentedContentWidth) }) });
|
|
8486
|
+
}
|
|
8487
|
+
if (trimmed.includes("\u25CA remembered:") || trimmed.includes("\u25CA ontology") || trimmed.includes("\u25CA learned:")) {
|
|
8488
|
+
return /* @__PURE__ */ jsx4(Box4, { marginLeft: 2, width: indentedContentWidth, children: /* @__PURE__ */ jsx4(Text4, { color: theme.accent, dimColor: true, wrap: "wrap", children: wrapText(text.trim(), indentedContentWidth) }) });
|
|
8489
|
+
}
|
|
8490
|
+
if (text.includes("compacted") || text.includes("Context compacted")) {
|
|
7925
8491
|
return /* @__PURE__ */ jsx4(Box4, { marginLeft: 2, width: indentedContentWidth, children: /* @__PURE__ */ jsx4(Text4, { color: theme.warning, dimColor: true, wrap: "wrap", children: wrapText(`${GUTTER.system} ${text.trim()}`, indentedContentWidth) }) });
|
|
7926
8492
|
}
|
|
7927
|
-
if (
|
|
8493
|
+
if (trimmed.startsWith(">")) {
|
|
7928
8494
|
return /* @__PURE__ */ jsx4(Box4, { width: contentWidth, children: /* @__PURE__ */ jsx4(Text4, { color: theme.muted, dimColor: true, wrap: "wrap", children: wrappedText }) });
|
|
7929
8495
|
}
|
|
7930
8496
|
return /* @__PURE__ */ jsx4(Box4, { width: contentWidth, children: /* @__PURE__ */ jsx4(Text4, { color: theme.muted, wrap: "wrap", children: wrapText(`${GUTTER.system} ${text.trim()}`, contentWidth) }) });
|
|
7931
|
-
}
|
|
8497
|
+
});
|
|
7932
8498
|
function PromptPrefix({
|
|
7933
8499
|
busy,
|
|
7934
8500
|
frame,
|
|
@@ -7947,25 +8513,131 @@ function PromptPrefix({
|
|
|
7947
8513
|
" "
|
|
7948
8514
|
] });
|
|
7949
8515
|
}
|
|
8516
|
+
var MAX_DIFF_LINES = 16;
|
|
8517
|
+
function computeDiffLines(oldContent, newContent, fileName) {
|
|
8518
|
+
if (oldContent == null) {
|
|
8519
|
+
const lines = newContent.split("\n");
|
|
8520
|
+
return lines.slice(0, MAX_DIFF_LINES + 4).map((l) => ({ type: "add", text: l }));
|
|
8521
|
+
}
|
|
8522
|
+
const patch = structuredPatch(fileName, fileName, oldContent, newContent, "", "", { context: 2 });
|
|
8523
|
+
const result = [];
|
|
8524
|
+
for (const hunk of patch.hunks) {
|
|
8525
|
+
if (result.length > 0) {
|
|
8526
|
+
result.push({ type: "ctx", text: "\xB7\xB7\xB7" });
|
|
8527
|
+
}
|
|
8528
|
+
for (const line of hunk.lines) {
|
|
8529
|
+
if (line.startsWith("+")) {
|
|
8530
|
+
result.push({ type: "add", text: line.slice(1) });
|
|
8531
|
+
} else if (line.startsWith("-")) {
|
|
8532
|
+
result.push({ type: "del", text: line.slice(1) });
|
|
8533
|
+
} else {
|
|
8534
|
+
result.push({ type: "ctx", text: line.slice(1) });
|
|
8535
|
+
}
|
|
8536
|
+
}
|
|
8537
|
+
}
|
|
8538
|
+
return result;
|
|
8539
|
+
}
|
|
7950
8540
|
function PendingUpdateCard({
|
|
7951
8541
|
count,
|
|
7952
8542
|
summary,
|
|
8543
|
+
fileName,
|
|
8544
|
+
updateType,
|
|
8545
|
+
oldContent,
|
|
8546
|
+
newContent,
|
|
8547
|
+
active,
|
|
8548
|
+
onAccept,
|
|
8549
|
+
onReject,
|
|
8550
|
+
onFeedback,
|
|
7953
8551
|
width
|
|
7954
8552
|
}) {
|
|
7955
8553
|
const theme = useTheme();
|
|
7956
8554
|
const contentWidth = resolveWidth(width);
|
|
7957
|
-
const
|
|
8555
|
+
const innerWidth = borderedContentWidth(contentWidth);
|
|
8556
|
+
const bodyWidth = indentedWidth(innerWidth);
|
|
8557
|
+
const diffLines = useMemo3(
|
|
8558
|
+
() => computeDiffLines(oldContent, newContent, fileName),
|
|
8559
|
+
[oldContent, newContent, fileName]
|
|
8560
|
+
);
|
|
8561
|
+
const truncated = diffLines.length > MAX_DIFF_LINES;
|
|
8562
|
+
const visibleLines = truncated ? diffLines.slice(0, MAX_DIFF_LINES) : diffLines;
|
|
8563
|
+
const additions = diffLines.filter((l) => l.type === "add").length;
|
|
8564
|
+
const deletions = diffLines.filter((l) => l.type === "del").length;
|
|
8565
|
+
const options = [
|
|
8566
|
+
{ label: "Accept", description: "Apply this update" },
|
|
8567
|
+
{ label: "Reject", description: "Discard this update" }
|
|
8568
|
+
];
|
|
8569
|
+
const totalItems = options.length + 1;
|
|
8570
|
+
const feedbackIndex = options.length;
|
|
8571
|
+
const [selectedIndex, setSelectedIndex] = useState4(0);
|
|
8572
|
+
const [mode, setMode] = useState4("selecting");
|
|
8573
|
+
const [feedbackText, setFeedbackText] = useState4("");
|
|
8574
|
+
useInput4((input2, key) => {
|
|
8575
|
+
if (!active || mode !== "selecting") return;
|
|
8576
|
+
if (key.upArrow) {
|
|
8577
|
+
setSelectedIndex((i) => Math.max(0, i - 1));
|
|
8578
|
+
return;
|
|
8579
|
+
}
|
|
8580
|
+
if (key.downArrow) {
|
|
8581
|
+
setSelectedIndex((i) => Math.min(totalItems - 1, i + 1));
|
|
8582
|
+
return;
|
|
8583
|
+
}
|
|
8584
|
+
if (key.return) {
|
|
8585
|
+
if (selectedIndex === 0) {
|
|
8586
|
+
onAccept();
|
|
8587
|
+
return;
|
|
8588
|
+
}
|
|
8589
|
+
if (selectedIndex === 1) {
|
|
8590
|
+
onReject();
|
|
8591
|
+
return;
|
|
8592
|
+
}
|
|
8593
|
+
if (selectedIndex === feedbackIndex) {
|
|
8594
|
+
setMode("typing");
|
|
8595
|
+
setFeedbackText("");
|
|
8596
|
+
}
|
|
8597
|
+
return;
|
|
8598
|
+
}
|
|
8599
|
+
}, { isActive: active && mode === "selecting" });
|
|
8600
|
+
useInput4((input2, key) => {
|
|
8601
|
+
if (!active || mode !== "typing") return;
|
|
8602
|
+
if (key.escape) {
|
|
8603
|
+
setMode("selecting");
|
|
8604
|
+
setFeedbackText("");
|
|
8605
|
+
return;
|
|
8606
|
+
}
|
|
8607
|
+
if (key.return) {
|
|
8608
|
+
if (feedbackText.trim()) {
|
|
8609
|
+
onFeedback(feedbackText.trim());
|
|
8610
|
+
setFeedbackText("");
|
|
8611
|
+
setMode("selecting");
|
|
8612
|
+
}
|
|
8613
|
+
return;
|
|
8614
|
+
}
|
|
8615
|
+
if (key.backspace || key.delete) {
|
|
8616
|
+
setFeedbackText((t) => t.slice(0, -1));
|
|
8617
|
+
return;
|
|
8618
|
+
}
|
|
8619
|
+
if (key.ctrl && input2 === "u") {
|
|
8620
|
+
setFeedbackText("");
|
|
8621
|
+
return;
|
|
8622
|
+
}
|
|
8623
|
+
if (!key.ctrl && !key.meta && !key.tab && input2.length === 1 && input2 >= " ") {
|
|
8624
|
+
setFeedbackText((t) => t + input2);
|
|
8625
|
+
}
|
|
8626
|
+
}, { isActive: active && mode === "typing" });
|
|
8627
|
+
const isFeedbackSelected = selectedIndex === feedbackIndex;
|
|
8628
|
+
const lineNumWidth = Math.max(3, String(diffLines.length).length + 1);
|
|
8629
|
+
const diffContentWidth = Math.max(1, bodyWidth - lineNumWidth - 3);
|
|
7958
8630
|
return /* @__PURE__ */ jsxs3(
|
|
7959
8631
|
Box4,
|
|
7960
8632
|
{
|
|
7961
8633
|
borderStyle: "single",
|
|
7962
|
-
borderColor: theme.
|
|
8634
|
+
borderColor: theme.warning,
|
|
7963
8635
|
paddingX: 1,
|
|
7964
|
-
marginBottom:
|
|
8636
|
+
marginBottom: 0,
|
|
7965
8637
|
flexDirection: "column",
|
|
7966
8638
|
width: contentWidth,
|
|
7967
8639
|
children: [
|
|
7968
|
-
/* @__PURE__ */ jsxs3(Box4, { width:
|
|
8640
|
+
/* @__PURE__ */ jsxs3(Box4, { width: innerWidth, children: [
|
|
7969
8641
|
/* @__PURE__ */ jsxs3(Text4, { color: theme.pending, bold: true, children: [
|
|
7970
8642
|
GUTTER.pending,
|
|
7971
8643
|
" "
|
|
@@ -7977,13 +8649,70 @@ function PendingUpdateCard({
|
|
|
7977
8649
|
" awaiting review"
|
|
7978
8650
|
] })
|
|
7979
8651
|
] }),
|
|
7980
|
-
/* @__PURE__ */
|
|
7981
|
-
|
|
7982
|
-
/* @__PURE__ */ jsx4(Text4, {
|
|
7983
|
-
|
|
7984
|
-
|
|
7985
|
-
|
|
7986
|
-
|
|
8652
|
+
/* @__PURE__ */ jsxs3(Box4, { marginLeft: 2, marginTop: 0, width: bodyWidth, children: [
|
|
8653
|
+
/* @__PURE__ */ jsx4(Text4, { color: theme.muted, wrap: "wrap", children: summary }),
|
|
8654
|
+
/* @__PURE__ */ jsx4(Text4, { color: theme.muted, dimColor: true, children: " " }),
|
|
8655
|
+
additions > 0 && /* @__PURE__ */ jsxs3(Text4, { color: theme.secondary, children: [
|
|
8656
|
+
"+",
|
|
8657
|
+
additions
|
|
8658
|
+
] }),
|
|
8659
|
+
additions > 0 && deletions > 0 && /* @__PURE__ */ jsx4(Text4, { color: theme.muted, dimColor: true, children: " " }),
|
|
8660
|
+
deletions > 0 && /* @__PURE__ */ jsxs3(Text4, { color: theme.error, children: [
|
|
8661
|
+
"-",
|
|
8662
|
+
deletions
|
|
8663
|
+
] })
|
|
8664
|
+
] }),
|
|
8665
|
+
/* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", marginLeft: 2, marginTop: 1, width: bodyWidth, children: [
|
|
8666
|
+
visibleLines.map((line, i) => {
|
|
8667
|
+
const prefix = line.type === "add" ? "+" : line.type === "del" ? "-" : " ";
|
|
8668
|
+
const color = line.type === "add" ? theme.secondary : line.type === "del" ? theme.error : theme.muted;
|
|
8669
|
+
const dimmed = line.type === "ctx";
|
|
8670
|
+
const displayText = truncateToWidth(line.text, diffContentWidth);
|
|
8671
|
+
return /* @__PURE__ */ jsxs3(Text4, { color, dimColor: dimmed, wrap: "truncate-end", children: [
|
|
8672
|
+
prefix,
|
|
8673
|
+
" ",
|
|
8674
|
+
displayText
|
|
8675
|
+
] }, i);
|
|
8676
|
+
}),
|
|
8677
|
+
truncated && /* @__PURE__ */ jsxs3(Text4, { color: theme.muted, dimColor: true, children: [
|
|
8678
|
+
" \xB7\xB7\xB7 ",
|
|
8679
|
+
diffLines.length - MAX_DIFF_LINES,
|
|
8680
|
+
" more lines"
|
|
8681
|
+
] })
|
|
8682
|
+
] }),
|
|
8683
|
+
/* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", marginLeft: 2, marginTop: 1, width: bodyWidth, children: [
|
|
8684
|
+
options.map((opt, idx) => {
|
|
8685
|
+
const isSelected = active && mode === "selecting" && idx === selectedIndex;
|
|
8686
|
+
const optColor = idx === 0 ? theme.secondary : theme.error;
|
|
8687
|
+
return /* @__PURE__ */ jsx4(Box4, { width: bodyWidth, children: isSelected ? /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
8688
|
+
/* @__PURE__ */ jsx4(Text4, { inverse: true, bold: true, color: theme.accent, children: " \u203A " }),
|
|
8689
|
+
/* @__PURE__ */ jsx4(Text4, { inverse: true, bold: true, children: " " + opt.label + " " }),
|
|
8690
|
+
/* @__PURE__ */ jsxs3(Text4, { color: theme.muted, dimColor: true, children: [
|
|
8691
|
+
" \u2014 ",
|
|
8692
|
+
opt.description
|
|
8693
|
+
] })
|
|
8694
|
+
] }) : /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
8695
|
+
/* @__PURE__ */ jsx4(Text4, { color: theme.muted, children: " " }),
|
|
8696
|
+
/* @__PURE__ */ jsx4(Text4, { color: optColor, children: opt.label }),
|
|
8697
|
+
/* @__PURE__ */ jsxs3(Text4, { color: theme.muted, dimColor: true, children: [
|
|
8698
|
+
" \u2014 ",
|
|
8699
|
+
opt.description
|
|
8700
|
+
] })
|
|
8701
|
+
] }) }, opt.label);
|
|
8702
|
+
}),
|
|
8703
|
+
mode === "typing" ? /* @__PURE__ */ jsxs3(Box4, { width: bodyWidth, children: [
|
|
8704
|
+
/* @__PURE__ */ jsx4(Text4, { color: theme.accent, bold: true, children: " \u203A " }),
|
|
8705
|
+
/* @__PURE__ */ jsx4(Text4, { color: theme.text, children: feedbackText }),
|
|
8706
|
+
/* @__PURE__ */ jsx4(Text4, { color: theme.accent, children: "\u2588" })
|
|
8707
|
+
] }) : /* @__PURE__ */ jsx4(Box4, { width: bodyWidth, children: active && isFeedbackSelected ? /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
8708
|
+
/* @__PURE__ */ jsx4(Text4, { inverse: true, bold: true, color: theme.accent, children: " \u203A " }),
|
|
8709
|
+
/* @__PURE__ */ jsx4(Text4, { inverse: true, bold: true, color: theme.muted, children: " Give feedback... " })
|
|
8710
|
+
] }) : /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
8711
|
+
/* @__PURE__ */ jsx4(Text4, { color: theme.muted, children: " " }),
|
|
8712
|
+
/* @__PURE__ */ jsx4(Text4, { color: theme.muted, dimColor: true, children: "Give feedback..." })
|
|
8713
|
+
] }) })
|
|
8714
|
+
] }),
|
|
8715
|
+
/* @__PURE__ */ jsx4(Box4, { marginLeft: 2, marginTop: 0, width: bodyWidth, children: /* @__PURE__ */ jsx4(Text4, { color: theme.muted, dimColor: true, wrap: "wrap", children: mode === "typing" ? "Type your feedback \xB7 Enter submit \xB7 Esc back" : active ? "\u2191/\u2193 select \xB7 Enter confirm" : "Waiting..." }) })
|
|
7987
8716
|
]
|
|
7988
8717
|
}
|
|
7989
8718
|
);
|
|
@@ -7991,12 +8720,67 @@ function PendingUpdateCard({
|
|
|
7991
8720
|
function QuestionCard({
|
|
7992
8721
|
question,
|
|
7993
8722
|
options,
|
|
8723
|
+
active,
|
|
8724
|
+
onSelect,
|
|
7994
8725
|
width
|
|
7995
8726
|
}) {
|
|
7996
8727
|
const theme = useTheme();
|
|
7997
8728
|
const contentWidth = resolveWidth(width);
|
|
7998
8729
|
const innerWidth = borderedContentWidth(contentWidth);
|
|
7999
8730
|
const bodyWidth = indentedWidth(innerWidth);
|
|
8731
|
+
const hasOptions = options.length > 0;
|
|
8732
|
+
const totalItems = hasOptions ? options.length + 1 : 0;
|
|
8733
|
+
const customIndex = options.length;
|
|
8734
|
+
const [selectedIndex, setSelectedIndex] = useState4(0);
|
|
8735
|
+
const [mode, setMode] = useState4(hasOptions ? "selecting" : "typing");
|
|
8736
|
+
const [customText, setCustomText] = useState4("");
|
|
8737
|
+
useInput4((input2, key) => {
|
|
8738
|
+
if (!active || mode !== "selecting" || !hasOptions) return;
|
|
8739
|
+
if (key.upArrow) {
|
|
8740
|
+
setSelectedIndex((i) => Math.max(0, i - 1));
|
|
8741
|
+
return;
|
|
8742
|
+
}
|
|
8743
|
+
if (key.downArrow) {
|
|
8744
|
+
setSelectedIndex((i) => Math.min(totalItems - 1, i + 1));
|
|
8745
|
+
return;
|
|
8746
|
+
}
|
|
8747
|
+
if (key.return) {
|
|
8748
|
+
if (selectedIndex < options.length) {
|
|
8749
|
+
const picked = options[selectedIndex];
|
|
8750
|
+
if (picked) onSelect(picked.label, false);
|
|
8751
|
+
} else {
|
|
8752
|
+
setMode("typing");
|
|
8753
|
+
setCustomText("");
|
|
8754
|
+
}
|
|
8755
|
+
return;
|
|
8756
|
+
}
|
|
8757
|
+
}, { isActive: active && mode === "selecting" });
|
|
8758
|
+
useInput4((input2, key) => {
|
|
8759
|
+
if (!active || mode !== "typing") return;
|
|
8760
|
+
if (key.escape && hasOptions) {
|
|
8761
|
+
setMode("selecting");
|
|
8762
|
+
setCustomText("");
|
|
8763
|
+
return;
|
|
8764
|
+
}
|
|
8765
|
+
if (key.return) {
|
|
8766
|
+
if (customText.trim()) {
|
|
8767
|
+
onSelect(customText.trim(), true);
|
|
8768
|
+
}
|
|
8769
|
+
return;
|
|
8770
|
+
}
|
|
8771
|
+
if (key.backspace || key.delete) {
|
|
8772
|
+
setCustomText((t) => t.slice(0, -1));
|
|
8773
|
+
return;
|
|
8774
|
+
}
|
|
8775
|
+
if (key.ctrl && input2 === "u") {
|
|
8776
|
+
setCustomText("");
|
|
8777
|
+
return;
|
|
8778
|
+
}
|
|
8779
|
+
if (!key.ctrl && !key.meta && !key.tab && input2.length === 1 && input2 >= " ") {
|
|
8780
|
+
setCustomText((t) => t + input2);
|
|
8781
|
+
}
|
|
8782
|
+
}, { isActive: active && mode === "typing" });
|
|
8783
|
+
const isCustomSelected = selectedIndex === customIndex;
|
|
8000
8784
|
return /* @__PURE__ */ jsxs3(
|
|
8001
8785
|
Box4,
|
|
8002
8786
|
{
|
|
@@ -8015,18 +8799,38 @@ function QuestionCard({
|
|
|
8015
8799
|
/* @__PURE__ */ jsx4(Text4, { bold: true, color: theme.warning, children: "Agent needs your input" })
|
|
8016
8800
|
] }),
|
|
8017
8801
|
/* @__PURE__ */ jsx4(Box4, { marginLeft: 2, marginTop: 0, width: bodyWidth, children: /* @__PURE__ */ jsx4(Text4, { color: theme.text, wrap: "wrap", children: question }) }),
|
|
8018
|
-
|
|
8019
|
-
|
|
8020
|
-
|
|
8021
|
-
|
|
8022
|
-
|
|
8023
|
-
|
|
8024
|
-
|
|
8025
|
-
|
|
8026
|
-
|
|
8027
|
-
|
|
8028
|
-
|
|
8029
|
-
|
|
8802
|
+
/* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", marginLeft: 2, marginTop: 1, width: bodyWidth, children: [
|
|
8803
|
+
options.map((opt, idx) => {
|
|
8804
|
+
const isSelected = active && mode === "selecting" && idx === selectedIndex;
|
|
8805
|
+
return /* @__PURE__ */ jsx4(Box4, { width: bodyWidth, children: isSelected ? /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
8806
|
+
/* @__PURE__ */ jsx4(Text4, { inverse: true, bold: true, color: theme.accent, children: " \u203A " }),
|
|
8807
|
+
/* @__PURE__ */ jsx4(Text4, { inverse: true, bold: true, children: " " + opt.label + " " }),
|
|
8808
|
+
/* @__PURE__ */ jsxs3(Text4, { color: theme.muted, dimColor: true, children: [
|
|
8809
|
+
" \u2014 ",
|
|
8810
|
+
opt.description
|
|
8811
|
+
] })
|
|
8812
|
+
] }) : /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
8813
|
+
/* @__PURE__ */ jsx4(Text4, { color: theme.muted, children: " " }),
|
|
8814
|
+
/* @__PURE__ */ jsx4(Text4, { color: theme.text, children: opt.label }),
|
|
8815
|
+
/* @__PURE__ */ jsxs3(Text4, { color: theme.muted, dimColor: true, children: [
|
|
8816
|
+
" \u2014 ",
|
|
8817
|
+
opt.description
|
|
8818
|
+
] })
|
|
8819
|
+
] }) }, opt.label);
|
|
8820
|
+
}),
|
|
8821
|
+
mode === "typing" ? /* @__PURE__ */ jsxs3(Box4, { width: bodyWidth, children: [
|
|
8822
|
+
/* @__PURE__ */ jsx4(Text4, { color: theme.accent, bold: true, children: " \u203A " }),
|
|
8823
|
+
/* @__PURE__ */ jsx4(Text4, { color: theme.text, children: customText }),
|
|
8824
|
+
/* @__PURE__ */ jsx4(Text4, { color: theme.accent, children: "\u2588" })
|
|
8825
|
+
] }) : /* @__PURE__ */ jsx4(Box4, { width: bodyWidth, children: active && isCustomSelected ? /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
8826
|
+
/* @__PURE__ */ jsx4(Text4, { inverse: true, bold: true, color: theme.accent, children: " \u203A " }),
|
|
8827
|
+
/* @__PURE__ */ jsx4(Text4, { inverse: true, bold: true, color: theme.muted, children: " Custom answer... " })
|
|
8828
|
+
] }) : /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
8829
|
+
/* @__PURE__ */ jsx4(Text4, { color: theme.muted, children: " " }),
|
|
8830
|
+
/* @__PURE__ */ jsx4(Text4, { color: theme.muted, dimColor: true, children: "Custom answer..." })
|
|
8831
|
+
] }) })
|
|
8832
|
+
] }),
|
|
8833
|
+
/* @__PURE__ */ jsx4(Box4, { marginLeft: 2, marginTop: 0, width: bodyWidth, children: /* @__PURE__ */ jsx4(Text4, { color: theme.muted, dimColor: true, wrap: "wrap", children: mode === "typing" ? "Type your answer \xB7 Enter submit \xB7 Esc back" : active ? "\u2191/\u2193 select \xB7 Enter confirm" : "Waiting..." }) })
|
|
8030
8834
|
]
|
|
8031
8835
|
}
|
|
8032
8836
|
);
|
|
@@ -8221,22 +9025,22 @@ function FooterBar({
|
|
|
8221
9025
|
var STREAM_FLUSH_PATTERN = /[.!?]\s|\n|^#{1,3}\s|^[-*]\s/m;
|
|
8222
9026
|
var STREAM_FLUSH_INTERVAL_MS = 80;
|
|
8223
9027
|
function splitMessagesForRender(messages, busy) {
|
|
8224
|
-
if (
|
|
9028
|
+
if (messages.length === 0) {
|
|
8225
9029
|
return {
|
|
8226
9030
|
staticMessages: messages,
|
|
8227
9031
|
dynamicMessages: []
|
|
8228
9032
|
};
|
|
8229
9033
|
}
|
|
8230
9034
|
const last = messages[messages.length - 1];
|
|
8231
|
-
if (
|
|
9035
|
+
if (last && (last.role === "assistant" ? busy : !busy && last.role === "system")) {
|
|
8232
9036
|
return {
|
|
8233
|
-
staticMessages: messages,
|
|
8234
|
-
dynamicMessages: []
|
|
9037
|
+
staticMessages: messages.slice(0, -1),
|
|
9038
|
+
dynamicMessages: [last]
|
|
8235
9039
|
};
|
|
8236
9040
|
}
|
|
8237
9041
|
return {
|
|
8238
|
-
staticMessages: messages
|
|
8239
|
-
dynamicMessages: [
|
|
9042
|
+
staticMessages: messages,
|
|
9043
|
+
dynamicMessages: []
|
|
8240
9044
|
};
|
|
8241
9045
|
}
|
|
8242
9046
|
function createSentenceStreamBuffer({
|
|
@@ -8285,10 +9089,10 @@ function createSentenceStreamBuffer({
|
|
|
8285
9089
|
}
|
|
8286
9090
|
|
|
8287
9091
|
// src/tui/hooks/use-animated-frame.ts
|
|
8288
|
-
import { useState as
|
|
9092
|
+
import { useState as useState5, useEffect as useEffect2 } from "react";
|
|
8289
9093
|
var SPINNER_FRAMES = ["\u25D0", "\u25D3", "\u25D1", "\u25D2"];
|
|
8290
9094
|
function useAnimatedFrame(active) {
|
|
8291
|
-
const [index, setIndex] =
|
|
9095
|
+
const [index, setIndex] = useState5(0);
|
|
8292
9096
|
useEffect2(() => {
|
|
8293
9097
|
if (!active) {
|
|
8294
9098
|
setIndex(0);
|
|
@@ -8301,28 +9105,49 @@ function useAnimatedFrame(active) {
|
|
|
8301
9105
|
}
|
|
8302
9106
|
|
|
8303
9107
|
// src/tui/hooks/use-terminal-width.ts
|
|
8304
|
-
import { useState as
|
|
9108
|
+
import { useState as useState6, useEffect as useEffect3 } from "react";
|
|
8305
9109
|
import { useStdout as useStdout2 } from "ink";
|
|
9110
|
+
var RESIZE_DEBOUNCE_MS = 50;
|
|
8306
9111
|
function useTerminalWidth() {
|
|
8307
9112
|
const { stdout } = useStdout2();
|
|
8308
|
-
const [terminalWidth, setTerminalWidth] =
|
|
9113
|
+
const [terminalWidth, setTerminalWidth] = useState6(
|
|
8309
9114
|
() => getObservedTerminalWidth(stdout.columns, process.stdout.columns)
|
|
8310
9115
|
);
|
|
8311
9116
|
useEffect3(() => {
|
|
8312
9117
|
const stream = stdout;
|
|
8313
|
-
|
|
8314
|
-
|
|
8315
|
-
setTerminalWidth((current) =>
|
|
9118
|
+
let resizeTimer = null;
|
|
9119
|
+
const commitWidth = () => {
|
|
9120
|
+
setTerminalWidth((current) => {
|
|
9121
|
+
const nextWidth = getStableObservedTerminalWidth(current, stream.columns, process.stdout.columns);
|
|
9122
|
+
return current === nextWidth ? current : nextWidth;
|
|
9123
|
+
});
|
|
8316
9124
|
};
|
|
8317
|
-
|
|
9125
|
+
const scheduleWidthUpdate = () => {
|
|
9126
|
+
if (resizeTimer) {
|
|
9127
|
+
clearTimeout(resizeTimer);
|
|
9128
|
+
}
|
|
9129
|
+
resizeTimer = setTimeout(() => {
|
|
9130
|
+
resizeTimer = null;
|
|
9131
|
+
commitWidth();
|
|
9132
|
+
}, RESIZE_DEBOUNCE_MS);
|
|
9133
|
+
};
|
|
9134
|
+
commitWidth();
|
|
8318
9135
|
if (typeof stream.on === "function") {
|
|
8319
|
-
stream.on("resize",
|
|
9136
|
+
stream.on("resize", scheduleWidthUpdate);
|
|
8320
9137
|
return () => {
|
|
9138
|
+
if (resizeTimer) {
|
|
9139
|
+
clearTimeout(resizeTimer);
|
|
9140
|
+
}
|
|
8321
9141
|
if (typeof stream.off === "function") {
|
|
8322
|
-
stream.off("resize",
|
|
9142
|
+
stream.off("resize", scheduleWidthUpdate);
|
|
8323
9143
|
}
|
|
8324
9144
|
};
|
|
8325
9145
|
}
|
|
9146
|
+
return () => {
|
|
9147
|
+
if (resizeTimer) {
|
|
9148
|
+
clearTimeout(resizeTimer);
|
|
9149
|
+
}
|
|
9150
|
+
};
|
|
8326
9151
|
}, [stdout]);
|
|
8327
9152
|
return terminalWidth;
|
|
8328
9153
|
}
|
|
@@ -8331,7 +9156,7 @@ function useTerminalWidth() {
|
|
|
8331
9156
|
import { startTransition } from "react";
|
|
8332
9157
|
|
|
8333
9158
|
// src/lib/workspace/init-agents-md.ts
|
|
8334
|
-
import
|
|
9159
|
+
import fs16 from "fs/promises";
|
|
8335
9160
|
import path16 from "path";
|
|
8336
9161
|
var IGNORED_DIRS2 = /* @__PURE__ */ new Set([
|
|
8337
9162
|
"node_modules",
|
|
@@ -8371,7 +9196,7 @@ async function scanDirectoryShallow(dir, maxDepth = 2, depth = 0) {
|
|
|
8371
9196
|
const results = [];
|
|
8372
9197
|
if (depth > maxDepth) return results;
|
|
8373
9198
|
try {
|
|
8374
|
-
const entries = await
|
|
9199
|
+
const entries = await fs16.readdir(dir, { withFileTypes: true });
|
|
8375
9200
|
for (const entry of entries) {
|
|
8376
9201
|
if (IGNORED_DIRS2.has(entry.name)) continue;
|
|
8377
9202
|
if (entry.name.startsWith(".") && depth === 0 && entry.isDirectory()) continue;
|
|
@@ -8382,7 +9207,7 @@ async function scanDirectoryShallow(dir, maxDepth = 2, depth = 0) {
|
|
|
8382
9207
|
const children = await scanDirectoryShallow(fullPath, maxDepth, depth + 1);
|
|
8383
9208
|
results.push(...children);
|
|
8384
9209
|
} else {
|
|
8385
|
-
const stat = await
|
|
9210
|
+
const stat = await fs16.stat(fullPath).catch(() => null);
|
|
8386
9211
|
results.push({ path: relativePath, size: stat?.size ?? 0, isDir: false });
|
|
8387
9212
|
}
|
|
8388
9213
|
}
|
|
@@ -8394,7 +9219,7 @@ async function readKeyFiles(dir) {
|
|
|
8394
9219
|
const contents = {};
|
|
8395
9220
|
for (const name of INTERESTING_FILES) {
|
|
8396
9221
|
try {
|
|
8397
|
-
const content = await
|
|
9222
|
+
const content = await fs16.readFile(path16.join(dir, name), "utf8");
|
|
8398
9223
|
contents[name] = content.slice(0, 2e3);
|
|
8399
9224
|
} catch {
|
|
8400
9225
|
}
|
|
@@ -8480,7 +9305,7 @@ ${scanData.slice(0, 25e3)}`;
|
|
|
8480
9305
|
|
|
8481
9306
|
// src/lib/preview/server.ts
|
|
8482
9307
|
import http2 from "http";
|
|
8483
|
-
import
|
|
9308
|
+
import fs17 from "fs";
|
|
8484
9309
|
import path17 from "path";
|
|
8485
9310
|
|
|
8486
9311
|
// src/lib/preview/latex-to-html.ts
|
|
@@ -8752,7 +9577,7 @@ function startPreviewServer(texPath) {
|
|
|
8752
9577
|
let currentHash = "";
|
|
8753
9578
|
function getContentHash() {
|
|
8754
9579
|
try {
|
|
8755
|
-
const content =
|
|
9580
|
+
const content = fs17.readFileSync(resolved, "utf8");
|
|
8756
9581
|
return `${content.length}-${content.slice(0, 100)}-${content.slice(-100)}`;
|
|
8757
9582
|
} catch {
|
|
8758
9583
|
return "error";
|
|
@@ -8760,7 +9585,7 @@ function startPreviewServer(texPath) {
|
|
|
8760
9585
|
}
|
|
8761
9586
|
function renderPage() {
|
|
8762
9587
|
try {
|
|
8763
|
-
const latex =
|
|
9588
|
+
const latex = fs17.readFileSync(resolved, "utf8");
|
|
8764
9589
|
const htmlContent = latexToHtml(latex);
|
|
8765
9590
|
currentHash = getContentHash();
|
|
8766
9591
|
return HTML_TEMPLATE.replace("{{CONTENT}}", htmlContent);
|
|
@@ -8807,7 +9632,7 @@ async function executeSlashCommand(cmd, args, ctx) {
|
|
|
8807
9632
|
history,
|
|
8808
9633
|
skills: skills2,
|
|
8809
9634
|
workspaceFiles,
|
|
8810
|
-
sessionId,
|
|
9635
|
+
sessionId: sessionId2,
|
|
8811
9636
|
sessionTokens,
|
|
8812
9637
|
agentMode,
|
|
8813
9638
|
addSystemMessage,
|
|
@@ -8846,6 +9671,23 @@ async function executeSlashCommand(cmd, args, ctx) {
|
|
|
8846
9671
|
}
|
|
8847
9672
|
break;
|
|
8848
9673
|
}
|
|
9674
|
+
case "auth-gemini": {
|
|
9675
|
+
addSystemMessage("Opening browser for Google login...");
|
|
9676
|
+
addSystemMessage("Note: Uses Gemini CLI credentials. Google may restrict third-party use.");
|
|
9677
|
+
setBusy(true);
|
|
9678
|
+
try {
|
|
9679
|
+
const { loginWithGemini } = await import("./gemini-login-EYY3EFH4.js");
|
|
9680
|
+
const result = await loginWithGemini({ homeDir });
|
|
9681
|
+
setAuthStatus("connected");
|
|
9682
|
+
addSystemMessage(`Connected Google account ${result.tokens.email}`);
|
|
9683
|
+
addSystemMessage("Switch to Gemini: /config provider gemini");
|
|
9684
|
+
} catch (err) {
|
|
9685
|
+
addSystemMessage(`Auth failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
9686
|
+
} finally {
|
|
9687
|
+
setBusy(false);
|
|
9688
|
+
}
|
|
9689
|
+
break;
|
|
9690
|
+
}
|
|
8849
9691
|
case "auth-codex": {
|
|
8850
9692
|
addSystemMessage("Importing Codex CLI auth...");
|
|
8851
9693
|
setBusy(true);
|
|
@@ -8934,7 +9776,7 @@ async function executeSlashCommand(cmd, args, ctx) {
|
|
|
8934
9776
|
addSystemMessage("No workspace. Run /init first.");
|
|
8935
9777
|
break;
|
|
8936
9778
|
}
|
|
8937
|
-
const { listSessions: listSessions2 } = await import("./sessions-
|
|
9779
|
+
const { listSessions: listSessions2 } = await import("./sessions-KL4LUGD7.js");
|
|
8938
9780
|
const foundSessions = await listSessions2(workspacePath);
|
|
8939
9781
|
if (foundSessions.length === 0) {
|
|
8940
9782
|
addSystemMessage("No previous sessions found.");
|
|
@@ -9484,22 +10326,36 @@ function parseCharterYaml(raw) {
|
|
|
9484
10326
|
|
|
9485
10327
|
// src/tui/helpers/render-message.tsx
|
|
9486
10328
|
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
9487
|
-
function
|
|
9488
|
-
|
|
9489
|
-
|
|
9490
|
-
|
|
9491
|
-
|
|
9492
|
-
|
|
9493
|
-
|
|
9494
|
-
|
|
10329
|
+
function renderConversationMessages(messages, expanded, width) {
|
|
10330
|
+
const elements = [];
|
|
10331
|
+
let turnCount = 0;
|
|
10332
|
+
for (let i = 0; i < messages.length; i++) {
|
|
10333
|
+
const message = messages[i];
|
|
10334
|
+
const key = `msg-${i}`;
|
|
10335
|
+
if (message.role === "user" && i > 0) {
|
|
10336
|
+
const prev = messages[i - 1];
|
|
10337
|
+
if (prev && prev.role !== "user") {
|
|
10338
|
+
elements.push(/* @__PURE__ */ jsx5(Divider, { width }, `div-${i}`));
|
|
10339
|
+
}
|
|
10340
|
+
}
|
|
10341
|
+
if (message.role === "user") {
|
|
10342
|
+
turnCount++;
|
|
10343
|
+
elements.push(/* @__PURE__ */ jsx5(UserMessage, { text: message.text, turnNumber: turnCount, width }, key));
|
|
10344
|
+
} else if (message.role === "system") {
|
|
10345
|
+
if (message.text.startsWith("__tool_summary__")) {
|
|
10346
|
+
try {
|
|
10347
|
+
const data = JSON.parse(message.text.slice("__tool_summary__".length));
|
|
10348
|
+
elements.push(/* @__PURE__ */ jsx5(ToolActivitySummary, { summary: data.summary, tools: data.tools, expanded, width }, key));
|
|
10349
|
+
} catch {
|
|
10350
|
+
}
|
|
10351
|
+
} else {
|
|
10352
|
+
elements.push(/* @__PURE__ */ jsx5(SystemMessage, { text: message.text, width }, key));
|
|
9495
10353
|
}
|
|
10354
|
+
} else {
|
|
10355
|
+
elements.push(/* @__PURE__ */ jsx5(AgentMessage, { text: message.text, width }, key));
|
|
9496
10356
|
}
|
|
9497
|
-
return /* @__PURE__ */ jsx5(SystemMessage, { text: message.text, width }, key);
|
|
9498
10357
|
}
|
|
9499
|
-
|
|
9500
|
-
return /* @__PURE__ */ jsx5(UserMessage, { text: message.text, width }, key);
|
|
9501
|
-
}
|
|
9502
|
-
return /* @__PURE__ */ jsx5(AgentMessage, { text: message.text, width }, key);
|
|
10358
|
+
return elements;
|
|
9503
10359
|
}
|
|
9504
10360
|
|
|
9505
10361
|
// src/tui/app.tsx
|
|
@@ -9510,55 +10366,84 @@ function App({
|
|
|
9510
10366
|
}) {
|
|
9511
10367
|
const app = useApp();
|
|
9512
10368
|
const abortRef = useRef2(null);
|
|
9513
|
-
const [input2, setInput] =
|
|
9514
|
-
const [composerFocused, setComposerFocused] =
|
|
9515
|
-
const [busy, setBusy] =
|
|
9516
|
-
const [authStatus, setAuthStatus] =
|
|
9517
|
-
const [workspacePath, setWorkspacePath] =
|
|
9518
|
-
const [workspaceFiles, setWorkspaceFiles] =
|
|
9519
|
-
const [skills2, setSkills] =
|
|
9520
|
-
const [messages, setMessages] =
|
|
9521
|
-
const [
|
|
9522
|
-
const [
|
|
9523
|
-
const [
|
|
9524
|
-
const [
|
|
9525
|
-
const [
|
|
9526
|
-
const [
|
|
9527
|
-
const [
|
|
9528
|
-
const [
|
|
9529
|
-
const [
|
|
9530
|
-
const [
|
|
9531
|
-
const [taskVersion, setTaskVersion] = useState6(0);
|
|
10369
|
+
const [input2, setInput] = useState7("");
|
|
10370
|
+
const [composerFocused, setComposerFocused] = useState7(true);
|
|
10371
|
+
const [busy, setBusy] = useState7(false);
|
|
10372
|
+
const [authStatus, setAuthStatus] = useState7(initialState.authStatus);
|
|
10373
|
+
const [workspacePath, setWorkspacePath] = useState7(initialState.workspacePath);
|
|
10374
|
+
const [workspaceFiles, setWorkspaceFiles] = useState7([]);
|
|
10375
|
+
const [skills2, setSkills] = useState7([]);
|
|
10376
|
+
const [messages, setMessages] = useState7([]);
|
|
10377
|
+
const [history, setHistory] = useState7([]);
|
|
10378
|
+
const [activeSkills, setActiveSkills] = useState7([]);
|
|
10379
|
+
const [pendingUpdates, setPendingUpdates] = useState7(initialState.pendingUpdates);
|
|
10380
|
+
const [statusLine, setStatusLine] = useState7("");
|
|
10381
|
+
const [activeToolActivities, setActiveToolActivities] = useState7({});
|
|
10382
|
+
const [turnToolCount, setTurnToolCount] = useState7(0);
|
|
10383
|
+
const [subAgentProgress, setSubAgentProgress] = useState7({});
|
|
10384
|
+
const [toolActivityExpanded, setToolActivityExpanded] = useState7(false);
|
|
10385
|
+
const [taskPanelVisible, setTaskPanelVisible] = useState7(true);
|
|
10386
|
+
const [taskVersion, setTaskVersion] = useState7(0);
|
|
9532
10387
|
const turnToolLogRef = useRef2([]);
|
|
9533
|
-
const [sessionTokens, setSessionTokens] =
|
|
9534
|
-
const [tokenDisplay, setTokenDisplay] =
|
|
9535
|
-
const [showSuggestions, setShowSuggestions] =
|
|
9536
|
-
const [agentMode, setAgentMode] =
|
|
9537
|
-
const [planningState, setPlanningState] =
|
|
10388
|
+
const [sessionTokens, setSessionTokens] = useState7(() => createSessionUsage());
|
|
10389
|
+
const [tokenDisplay, setTokenDisplay] = useState7("");
|
|
10390
|
+
const [showSuggestions, setShowSuggestions] = useState7(false);
|
|
10391
|
+
const [agentMode, setAgentMode] = useState7("manual-review");
|
|
10392
|
+
const [planningState, setPlanningState] = useState7({
|
|
9538
10393
|
status: "idle",
|
|
9539
10394
|
planningHistory: []
|
|
9540
10395
|
});
|
|
9541
|
-
const [theme, setTheme] =
|
|
9542
|
-
const [config, setConfig] =
|
|
9543
|
-
const [cursorToEnd, setCursorToEnd] =
|
|
9544
|
-
const [
|
|
9545
|
-
const [
|
|
9546
|
-
const [
|
|
9547
|
-
const
|
|
9548
|
-
const
|
|
10396
|
+
const [theme, setTheme] = useState7("dark");
|
|
10397
|
+
const [config, setConfig] = useState7(null);
|
|
10398
|
+
const [cursorToEnd, setCursorToEnd] = useState7(0);
|
|
10399
|
+
const [messageRenderVersion, setMessageRenderVersion] = useState7(0);
|
|
10400
|
+
const [screen, setScreen] = useState7("main");
|
|
10401
|
+
const [resumeSessions, setResumeSessions] = useState7([]);
|
|
10402
|
+
const [ctrlCPending, setCtrlCPending] = useState7(false);
|
|
10403
|
+
const sessionId2 = useMemo4(() => crypto.randomUUID(), []);
|
|
9549
10404
|
const deferredPendingUpdates = useDeferredValue(pendingUpdates);
|
|
10405
|
+
const visiblePendingUpdates = deferredPendingUpdates.length > 0 ? deferredPendingUpdates : pendingUpdates;
|
|
9550
10406
|
const activityFrame = useAnimatedFrame(busy);
|
|
9551
10407
|
const terminalWidth = useTerminalWidth();
|
|
9552
10408
|
const contentWidth = insetWidth(terminalWidth, 2);
|
|
9553
10409
|
const panelInnerWidth = insetWidth(contentWidth, 4);
|
|
9554
10410
|
const panelBodyWidth = insetWidth(panelInnerWidth, 2);
|
|
9555
|
-
const [agentQuestion, setAgentQuestion] =
|
|
10411
|
+
const [agentQuestion, setAgentQuestion] = useState7(null);
|
|
9556
10412
|
const previewRef = useRef2(null);
|
|
9557
10413
|
const ctrlCTimerRef = useRef2(null);
|
|
9558
|
-
const isHome =
|
|
9559
|
-
const { staticMessages, dynamicMessages } =
|
|
9560
|
-
() => splitMessagesForRender(
|
|
9561
|
-
[busy,
|
|
10414
|
+
const isHome = messages.length === 0 && !busy;
|
|
10415
|
+
const { staticMessages, dynamicMessages } = useMemo4(
|
|
10416
|
+
() => splitMessagesForRender(messages, busy),
|
|
10417
|
+
[busy, messages]
|
|
10418
|
+
);
|
|
10419
|
+
const staticRenderItems = useMemo4(
|
|
10420
|
+
() => renderConversationMessages(staticMessages, toolActivityExpanded, contentWidth),
|
|
10421
|
+
[contentWidth, staticMessages, toolActivityExpanded]
|
|
10422
|
+
);
|
|
10423
|
+
const dynamicRenderItems = useMemo4(
|
|
10424
|
+
() => renderConversationMessages(dynamicMessages, toolActivityExpanded, contentWidth),
|
|
10425
|
+
[contentWidth, dynamicMessages, toolActivityExpanded]
|
|
10426
|
+
);
|
|
10427
|
+
const showThinking = busy && (messages.length === 0 || messages[messages.length - 1]?.role !== "assistant");
|
|
10428
|
+
const activeToolDescriptions = useMemo4(
|
|
10429
|
+
() => Object.values(activeToolActivities),
|
|
10430
|
+
[activeToolActivities]
|
|
10431
|
+
);
|
|
10432
|
+
const currentToolActivity = useMemo4(() => {
|
|
10433
|
+
if (activeToolDescriptions.length === 0) {
|
|
10434
|
+
return "";
|
|
10435
|
+
}
|
|
10436
|
+
if (activeToolDescriptions.length === 1 && turnToolCount === 0) {
|
|
10437
|
+
return activeToolDescriptions[0] ?? "";
|
|
10438
|
+
}
|
|
10439
|
+
if (activeToolDescriptions.length === 1) {
|
|
10440
|
+
return "Running 1 tool";
|
|
10441
|
+
}
|
|
10442
|
+
return `Running ${activeToolDescriptions.length} tools in parallel`;
|
|
10443
|
+
}, [activeToolDescriptions, turnToolCount]);
|
|
10444
|
+
const visibleSubAgents = useMemo4(
|
|
10445
|
+
() => Object.values(subAgentProgress),
|
|
10446
|
+
[subAgentProgress]
|
|
9562
10447
|
);
|
|
9563
10448
|
const hasWorkspace = workspacePath !== null;
|
|
9564
10449
|
const hasAuth = authStatus === "connected";
|
|
@@ -9571,7 +10456,7 @@ function App({
|
|
|
9571
10456
|
const pending = getPendingQuestion();
|
|
9572
10457
|
if (pending && (!agentQuestion || pending.question.id !== agentQuestion.question.id)) {
|
|
9573
10458
|
setAgentQuestion(pending);
|
|
9574
|
-
setComposerFocused(
|
|
10459
|
+
setComposerFocused(false);
|
|
9575
10460
|
}
|
|
9576
10461
|
}, 200);
|
|
9577
10462
|
return () => clearInterval(interval);
|
|
@@ -9626,15 +10511,16 @@ function App({
|
|
|
9626
10511
|
}
|
|
9627
10512
|
};
|
|
9628
10513
|
}, []);
|
|
9629
|
-
const [selectedSuggestion, setSelectedSuggestion] =
|
|
9630
|
-
const atMention =
|
|
9631
|
-
const
|
|
10514
|
+
const [selectedSuggestion, setSelectedSuggestion] = useState7(-1);
|
|
10515
|
+
const atMention = useMemo4(() => extractAtMention(input2), [input2]);
|
|
10516
|
+
const slashTrigger = useMemo4(() => extractSlashTrigger(input2), [input2]);
|
|
10517
|
+
const suggestions = useMemo4(() => {
|
|
9632
10518
|
if (atMention) {
|
|
9633
10519
|
return getFileSuggestions(atMention.partial, workspaceFiles);
|
|
9634
10520
|
}
|
|
9635
|
-
if (!
|
|
9636
|
-
return getUnifiedSuggestions(
|
|
9637
|
-
}, [input2, skills2, atMention, workspaceFiles]);
|
|
10521
|
+
if (!slashTrigger) return [];
|
|
10522
|
+
return getUnifiedSuggestions(`/${slashTrigger.partial}`, skills2);
|
|
10523
|
+
}, [input2, skills2, atMention, slashTrigger, workspaceFiles]);
|
|
9638
10524
|
useEffect4(() => {
|
|
9639
10525
|
setSelectedSuggestion(-1);
|
|
9640
10526
|
}, [suggestions.length, input2]);
|
|
@@ -9643,8 +10529,9 @@ function App({
|
|
|
9643
10529
|
if (s.kind === "file" && atMention) {
|
|
9644
10530
|
const before = input2.slice(0, atMention.start);
|
|
9645
10531
|
setInput(`${before}@${s.path} `);
|
|
9646
|
-
} else if (s.kind === "command" || s.kind === "skill") {
|
|
9647
|
-
|
|
10532
|
+
} else if ((s.kind === "command" || s.kind === "skill") && slashTrigger) {
|
|
10533
|
+
const before = input2.slice(0, slashTrigger.start);
|
|
10534
|
+
setInput(`${before}/${s.name}`);
|
|
9648
10535
|
}
|
|
9649
10536
|
setSelectedSuggestion(-1);
|
|
9650
10537
|
setCursorToEnd((c) => c + 1);
|
|
@@ -9701,10 +10588,8 @@ function App({
|
|
|
9701
10588
|
});
|
|
9702
10589
|
}
|
|
9703
10590
|
function replaceMessages(nextMessages) {
|
|
9704
|
-
|
|
9705
|
-
|
|
9706
|
-
setMessages(nextMessages);
|
|
9707
|
-
});
|
|
10591
|
+
setMessages(nextMessages);
|
|
10592
|
+
setMessageRenderVersion((current) => current + 1);
|
|
9708
10593
|
}
|
|
9709
10594
|
const slashCtx = {
|
|
9710
10595
|
homeDir,
|
|
@@ -9715,7 +10600,7 @@ function App({
|
|
|
9715
10600
|
history,
|
|
9716
10601
|
skills: skills2,
|
|
9717
10602
|
workspaceFiles,
|
|
9718
|
-
sessionId,
|
|
10603
|
+
sessionId: sessionId2,
|
|
9719
10604
|
sessionTokens,
|
|
9720
10605
|
agentMode,
|
|
9721
10606
|
previewRef,
|
|
@@ -9743,7 +10628,7 @@ function App({
|
|
|
9743
10628
|
if (pendingUpdates.length === 0 || !workspacePath) return;
|
|
9744
10629
|
const [next, ...rest] = pendingUpdates;
|
|
9745
10630
|
await applyProposedUpdate(workspacePath, next);
|
|
9746
|
-
await appendSessionEvent(workspacePath,
|
|
10631
|
+
await appendSessionEvent(workspacePath, sessionId2, {
|
|
9747
10632
|
type: "update.accepted",
|
|
9748
10633
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9749
10634
|
payload: { key: next.key, summary: next.summary }
|
|
@@ -9758,7 +10643,7 @@ function App({
|
|
|
9758
10643
|
function rejectNextPendingUpdate() {
|
|
9759
10644
|
if (pendingUpdates.length === 0 || !workspacePath) return;
|
|
9760
10645
|
const [next, ...rest] = pendingUpdates;
|
|
9761
|
-
void appendSessionEvent(workspacePath,
|
|
10646
|
+
void appendSessionEvent(workspacePath, sessionId2, {
|
|
9762
10647
|
type: "update.rejected",
|
|
9763
10648
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9764
10649
|
payload: { key: next.key, summary: next.summary }
|
|
@@ -9768,6 +10653,19 @@ function App({
|
|
|
9768
10653
|
addSystemMessage(`Rejected: ${next.summary}`);
|
|
9769
10654
|
});
|
|
9770
10655
|
}
|
|
10656
|
+
function feedbackOnPendingUpdate(feedback) {
|
|
10657
|
+
if (pendingUpdates.length === 0 || !workspacePath) return;
|
|
10658
|
+
const [next, ...rest] = pendingUpdates;
|
|
10659
|
+
void appendSessionEvent(workspacePath, sessionId2, {
|
|
10660
|
+
type: "update.rejected",
|
|
10661
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10662
|
+
payload: { key: next.key, summary: next.summary, feedback }
|
|
10663
|
+
});
|
|
10664
|
+
startTransition2(() => {
|
|
10665
|
+
setPendingUpdates(rest);
|
|
10666
|
+
addSystemMessage(`Feedback on "${next.summary}": ${feedback}`);
|
|
10667
|
+
});
|
|
10668
|
+
}
|
|
9771
10669
|
function clearCtrlCPending() {
|
|
9772
10670
|
if (ctrlCTimerRef.current) {
|
|
9773
10671
|
clearTimeout(ctrlCTimerRef.current);
|
|
@@ -9822,7 +10720,7 @@ function App({
|
|
|
9822
10720
|
summary: `Research charter: ${charter.researchQuestion}`
|
|
9823
10721
|
};
|
|
9824
10722
|
await applyProposedUpdate(workspacePath, charterUpdate);
|
|
9825
|
-
await appendSessionEvent(workspacePath,
|
|
10723
|
+
await appendSessionEvent(workspacePath, sessionId2, {
|
|
9826
10724
|
type: "charter.approved",
|
|
9827
10725
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9828
10726
|
payload: { charterId: charter.id }
|
|
@@ -9835,7 +10733,7 @@ function App({
|
|
|
9835
10733
|
}
|
|
9836
10734
|
function rejectCharter() {
|
|
9837
10735
|
if (!planningState.charter || !workspacePath) return;
|
|
9838
|
-
void appendSessionEvent(workspacePath,
|
|
10736
|
+
void appendSessionEvent(workspacePath, sessionId2, {
|
|
9839
10737
|
type: "charter.rejected",
|
|
9840
10738
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9841
10739
|
payload: { charterId: planningState.charter.id }
|
|
@@ -9843,7 +10741,7 @@ function App({
|
|
|
9843
10741
|
setPlanningState({ status: "idle", planningHistory: [] });
|
|
9844
10742
|
addSystemMessage("Charter cancelled. Planning reset.");
|
|
9845
10743
|
}
|
|
9846
|
-
|
|
10744
|
+
useInput5((key, inputKey) => {
|
|
9847
10745
|
if (inputKey.ctrl && key === "c") {
|
|
9848
10746
|
if (busy) {
|
|
9849
10747
|
clearCtrlCPending();
|
|
@@ -9916,14 +10814,7 @@ function App({
|
|
|
9916
10814
|
return;
|
|
9917
10815
|
}
|
|
9918
10816
|
}
|
|
9919
|
-
if (
|
|
9920
|
-
void acceptNextPendingUpdate();
|
|
9921
|
-
return;
|
|
9922
|
-
}
|
|
9923
|
-
if (key === "r" && pendingUpdates.length > 0) {
|
|
9924
|
-
rejectNextPendingUpdate();
|
|
9925
|
-
return;
|
|
9926
|
-
}
|
|
10817
|
+
if (pendingUpdates.length > 0 && !agentQuestion || agentQuestion) return;
|
|
9927
10818
|
if (key === "i" || key.length === 1 && !inputKey.ctrl && !inputKey.meta && !inputKey.tab) {
|
|
9928
10819
|
setComposerFocused(true);
|
|
9929
10820
|
if (key !== "i") setInput((c) => c + key);
|
|
@@ -9935,24 +10826,9 @@ function App({
|
|
|
9935
10826
|
if (handleDropdownSelect()) return;
|
|
9936
10827
|
const trimmed = value.trim();
|
|
9937
10828
|
if (!trimmed) return;
|
|
9938
|
-
if (agentQuestion)
|
|
9939
|
-
const options = agentQuestion.question.options;
|
|
9940
|
-
const numChoice = parseInt(trimmed, 10);
|
|
9941
|
-
if (!isNaN(numChoice) && numChoice >= 1 && numChoice <= options.length) {
|
|
9942
|
-
const picked = options[numChoice - 1];
|
|
9943
|
-
addSystemMessage(`> ${picked.label}`);
|
|
9944
|
-
agentQuestion.resolve({ questionId: agentQuestion.question.id, answer: picked.label, isCustom: false });
|
|
9945
|
-
} else {
|
|
9946
|
-
addSystemMessage(`> ${trimmed}`);
|
|
9947
|
-
agentQuestion.resolve({ questionId: agentQuestion.question.id, answer: trimmed, isCustom: true });
|
|
9948
|
-
}
|
|
9949
|
-
clearPendingQuestion();
|
|
9950
|
-
setAgentQuestion(null);
|
|
9951
|
-
setInput("");
|
|
9952
|
-
return;
|
|
9953
|
-
}
|
|
10829
|
+
if (agentQuestion) return;
|
|
9954
10830
|
if (busy) return;
|
|
9955
|
-
if (
|
|
10831
|
+
if (dropdownVisible && applyAutocomplete()) {
|
|
9956
10832
|
return;
|
|
9957
10833
|
}
|
|
9958
10834
|
setInput("");
|
|
@@ -9995,9 +10871,11 @@ function App({
|
|
|
9995
10871
|
if (!workspacePath) return;
|
|
9996
10872
|
turnToolLogRef.current = [];
|
|
9997
10873
|
setTurnToolCount(0);
|
|
9998
|
-
|
|
10874
|
+
setActiveToolActivities({});
|
|
10875
|
+
setSubAgentProgress({});
|
|
9999
10876
|
const controller = new AbortController();
|
|
10000
10877
|
let streamBuffer = null;
|
|
10878
|
+
let focusPendingReviewOnComplete = false;
|
|
10001
10879
|
abortRef.current = controller;
|
|
10002
10880
|
setBusy(true);
|
|
10003
10881
|
startTransition2(() => {
|
|
@@ -10008,7 +10886,7 @@ function App({
|
|
|
10008
10886
|
const workspace = await scanWorkspace(workspacePath);
|
|
10009
10887
|
const workspaceContext = {
|
|
10010
10888
|
workspaceDir: workspacePath,
|
|
10011
|
-
runId:
|
|
10889
|
+
runId: sessionId2,
|
|
10012
10890
|
workspaceFiles: Object.fromEntries(workspace.files.map((f) => [f.key, f.content])),
|
|
10013
10891
|
availableKeys: workspace.files.map((f) => f.key),
|
|
10014
10892
|
fileLabels: Object.fromEntries(workspace.files.map((f) => [f.key, f.label]))
|
|
@@ -10037,9 +10915,16 @@ function App({
|
|
|
10037
10915
|
onToolActivity: (activity) => {
|
|
10038
10916
|
streamBuffer?.flush();
|
|
10039
10917
|
if (activity.type === "tool_start") {
|
|
10040
|
-
|
|
10918
|
+
setActiveToolActivities((current) => ({
|
|
10919
|
+
...current,
|
|
10920
|
+
[activity.toolCallId]: activity.description ?? activity.name
|
|
10921
|
+
}));
|
|
10041
10922
|
} else {
|
|
10042
|
-
|
|
10923
|
+
setActiveToolActivities((current) => {
|
|
10924
|
+
const next = { ...current };
|
|
10925
|
+
delete next[activity.toolCallId];
|
|
10926
|
+
return next;
|
|
10927
|
+
});
|
|
10043
10928
|
turnToolLogRef.current.push({
|
|
10044
10929
|
name: activity.name,
|
|
10045
10930
|
description: activity.description ?? activity.name,
|
|
@@ -10053,9 +10938,16 @@ function App({
|
|
|
10053
10938
|
},
|
|
10054
10939
|
onSubAgentProgress: (progress) => {
|
|
10055
10940
|
if (progress.status === "done") {
|
|
10056
|
-
setSubAgentProgress(
|
|
10941
|
+
setSubAgentProgress((current) => {
|
|
10942
|
+
const next = { ...current };
|
|
10943
|
+
delete next[progress.agentId];
|
|
10944
|
+
return next;
|
|
10945
|
+
});
|
|
10057
10946
|
} else {
|
|
10058
|
-
setSubAgentProgress(
|
|
10947
|
+
setSubAgentProgress((current) => ({
|
|
10948
|
+
...current,
|
|
10949
|
+
[progress.agentId]: { ...progress }
|
|
10950
|
+
}));
|
|
10059
10951
|
}
|
|
10060
10952
|
},
|
|
10061
10953
|
onMemoryExtracted: (mems) => {
|
|
@@ -10102,9 +10994,10 @@ function App({
|
|
|
10102
10994
|
}
|
|
10103
10995
|
}
|
|
10104
10996
|
if (reviewRequired.length > 0) {
|
|
10105
|
-
|
|
10997
|
+
focusPendingReviewOnComplete = true;
|
|
10998
|
+
setPendingUpdates((current) => [...current, ...reviewRequired]);
|
|
10106
10999
|
}
|
|
10107
|
-
await appendSessionEvent(workspacePath,
|
|
11000
|
+
await appendSessionEvent(workspacePath, sessionId2, {
|
|
10108
11001
|
type: "chat.turn",
|
|
10109
11002
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10110
11003
|
payload: {
|
|
@@ -10134,8 +11027,10 @@ ${error.stack}` : String(error)}` }
|
|
|
10134
11027
|
} finally {
|
|
10135
11028
|
streamBuffer?.dispose();
|
|
10136
11029
|
abortRef.current = null;
|
|
11030
|
+
setActiveToolActivities({});
|
|
11031
|
+
setSubAgentProgress({});
|
|
10137
11032
|
setBusy(false);
|
|
10138
|
-
setComposerFocused(
|
|
11033
|
+
setComposerFocused(!focusPendingReviewOnComplete);
|
|
10139
11034
|
if (controller.signal.aborted) {
|
|
10140
11035
|
addSystemMessage("Agent interrupted.");
|
|
10141
11036
|
}
|
|
@@ -10162,7 +11057,7 @@ ${error.stack}` : String(error)}` }
|
|
|
10162
11057
|
const workspace = await scanWorkspace(workspacePath);
|
|
10163
11058
|
const workspaceContext = {
|
|
10164
11059
|
workspaceDir: workspacePath,
|
|
10165
|
-
runId:
|
|
11060
|
+
runId: sessionId2,
|
|
10166
11061
|
workspaceFiles: Object.fromEntries(workspace.files.map((f) => [f.key, f.content])),
|
|
10167
11062
|
availableKeys: workspace.files.map((f) => f.key),
|
|
10168
11063
|
fileLabels: Object.fromEntries(workspace.files.map((f) => [f.key, f.label]))
|
|
@@ -10202,7 +11097,7 @@ ${error.stack}` : String(error)}` }
|
|
|
10202
11097
|
const charter = parseCharterYaml(result.detectedCharter);
|
|
10203
11098
|
setPlanningState((prev) => ({ ...prev, status: "charter-review", charter }));
|
|
10204
11099
|
if (workspacePath) {
|
|
10205
|
-
await appendSessionEvent(workspacePath,
|
|
11100
|
+
await appendSessionEvent(workspacePath, sessionId2, {
|
|
10206
11101
|
type: "charter.generated",
|
|
10207
11102
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10208
11103
|
payload: { charterId: charter.id, researchQuestion: charter.researchQuestion }
|
|
@@ -10242,10 +11137,10 @@ ${error.stack}` : String(error)}` }
|
|
|
10242
11137
|
else statusParts.push("no workspace");
|
|
10243
11138
|
if (skills2.length > 0) statusParts.push(`${skills2.length} skills`);
|
|
10244
11139
|
statusParts.push(agentMode);
|
|
10245
|
-
if (
|
|
11140
|
+
if (pendingUpdates.length > 0) statusParts.push(`${pendingUpdates.length} pending`);
|
|
10246
11141
|
const themeColors = getThemeColors(theme);
|
|
10247
|
-
const statusColor = busy ? themeColors.warning : ctrlCPending ? themeColors.warning : !hasAuth ? themeColors.error :
|
|
10248
|
-
const configItems =
|
|
11142
|
+
const statusColor = busy ? themeColors.warning : ctrlCPending ? themeColors.warning : !hasAuth ? themeColors.error : pendingUpdates.length > 0 ? themeColors.pending : themeColors.secondary;
|
|
11143
|
+
const configItems = useMemo4(() => [
|
|
10249
11144
|
{ key: "defaults.model", label: "Model", values: [...getAvailableModels()], current: config?.defaults.model ?? "gpt-5.4" },
|
|
10250
11145
|
{ key: "theme", label: "Theme", values: [...themeValues], current: theme },
|
|
10251
11146
|
{ key: "defaults.reasoningEffort", label: "Reasoning effort", values: ["low", "medium", "high", "xhigh"], current: config?.defaults.reasoningEffort ?? "medium" },
|
|
@@ -10279,11 +11174,8 @@ ${error.stack}` : String(error)}` }
|
|
|
10279
11174
|
onSelect: async (session) => {
|
|
10280
11175
|
try {
|
|
10281
11176
|
const restored = await loadSessionHistory(workspacePath, session.id);
|
|
10282
|
-
|
|
10283
|
-
|
|
10284
|
-
setMessages(restored.messages);
|
|
10285
|
-
setHistory(restored.llmHistory);
|
|
10286
|
-
});
|
|
11177
|
+
replaceMessages(restored.messages);
|
|
11178
|
+
startTransition2(() => setHistory(restored.llmHistory));
|
|
10287
11179
|
addSystemMessage(`Resumed session (${session.turnCount} turns). Continue where you left off.`);
|
|
10288
11180
|
} catch (err) {
|
|
10289
11181
|
addSystemMessage(`Failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -10316,23 +11208,28 @@ ${error.stack}` : String(error)}` }
|
|
|
10316
11208
|
width: contentWidth
|
|
10317
11209
|
}
|
|
10318
11210
|
),
|
|
10319
|
-
|
|
10320
|
-
|
|
10321
|
-
|
|
10322
|
-
|
|
10323
|
-
|
|
10324
|
-
|
|
10325
|
-
|
|
11211
|
+
staticRenderItems.length > 0 && /* @__PURE__ */ jsx6(
|
|
11212
|
+
Static,
|
|
11213
|
+
{
|
|
11214
|
+
items: staticRenderItems,
|
|
11215
|
+
children: (item, index) => /* @__PURE__ */ jsx6(React6.Fragment, { children: item }, `conversation-static-item-${messageRenderVersion}-${index}`)
|
|
11216
|
+
},
|
|
11217
|
+
`conversation-static-${messageRenderVersion}`
|
|
11218
|
+
),
|
|
11219
|
+
dynamicRenderItems.length > 0 && /* @__PURE__ */ jsx6(Box5, { flexDirection: "column", marginBottom: 1, width: contentWidth, children: dynamicRenderItems }),
|
|
11220
|
+
showThinking && /* @__PURE__ */ jsx6(ThinkingIndicator, { frame: activityFrame, width: contentWidth }),
|
|
11221
|
+
visibleSubAgents.map((progress) => /* @__PURE__ */ jsx6(
|
|
10326
11222
|
SubAgentIndicator,
|
|
10327
11223
|
{
|
|
10328
|
-
agentType:
|
|
10329
|
-
goal:
|
|
10330
|
-
currentTool:
|
|
10331
|
-
toolCount:
|
|
11224
|
+
agentType: progress.agentType,
|
|
11225
|
+
goal: progress.goal,
|
|
11226
|
+
currentTool: progress.currentTool,
|
|
11227
|
+
toolCount: progress.toolCount,
|
|
10332
11228
|
frame: activityFrame,
|
|
10333
11229
|
width: contentWidth
|
|
10334
|
-
}
|
|
10335
|
-
|
|
11230
|
+
},
|
|
11231
|
+
progress.agentId
|
|
11232
|
+
)),
|
|
10336
11233
|
taskPanelVisible && getVisibleTasks().length > 0 && /* @__PURE__ */ jsx6(
|
|
10337
11234
|
TaskPanel,
|
|
10338
11235
|
{
|
|
@@ -10341,11 +11238,28 @@ ${error.stack}` : String(error)}` }
|
|
|
10341
11238
|
width: contentWidth
|
|
10342
11239
|
}
|
|
10343
11240
|
),
|
|
10344
|
-
|
|
11241
|
+
visiblePendingUpdates.length > 0 && /* @__PURE__ */ jsx6(
|
|
10345
11242
|
PendingUpdateCard,
|
|
10346
11243
|
{
|
|
10347
|
-
count:
|
|
10348
|
-
summary: truncate3(
|
|
11244
|
+
count: pendingUpdates.length,
|
|
11245
|
+
summary: truncate3(visiblePendingUpdates[0].summary, Math.max(24, insetWidth(contentWidth, 8))),
|
|
11246
|
+
fileName: visiblePendingUpdates[0].key,
|
|
11247
|
+
updateType: visiblePendingUpdates[0].type,
|
|
11248
|
+
oldContent: visiblePendingUpdates[0].oldContent,
|
|
11249
|
+
newContent: visiblePendingUpdates[0].content,
|
|
11250
|
+
active: !composerFocused && !agentQuestion,
|
|
11251
|
+
onAccept: () => {
|
|
11252
|
+
void acceptNextPendingUpdate();
|
|
11253
|
+
if (pendingUpdates.length <= 1) setComposerFocused(true);
|
|
11254
|
+
},
|
|
11255
|
+
onReject: () => {
|
|
11256
|
+
rejectNextPendingUpdate();
|
|
11257
|
+
if (pendingUpdates.length <= 1) setComposerFocused(true);
|
|
11258
|
+
},
|
|
11259
|
+
onFeedback: (fb) => {
|
|
11260
|
+
feedbackOnPendingUpdate(fb);
|
|
11261
|
+
if (pendingUpdates.length <= 1) setComposerFocused(true);
|
|
11262
|
+
},
|
|
10349
11263
|
width: contentWidth
|
|
10350
11264
|
}
|
|
10351
11265
|
),
|
|
@@ -10389,7 +11303,23 @@ ${error.stack}` : String(error)}` }
|
|
|
10389
11303
|
] }) })
|
|
10390
11304
|
] }),
|
|
10391
11305
|
dropdownVisible && /* @__PURE__ */ jsx6(SuggestionDropdown, { width: contentWidth, items: suggestions, selectedIndex: selectedSuggestion }),
|
|
10392
|
-
agentQuestion && /* @__PURE__ */ jsx6(
|
|
11306
|
+
agentQuestion && /* @__PURE__ */ jsx6(
|
|
11307
|
+
QuestionCard,
|
|
11308
|
+
{
|
|
11309
|
+
width: contentWidth,
|
|
11310
|
+
question: agentQuestion.question.question,
|
|
11311
|
+
options: agentQuestion.question.options,
|
|
11312
|
+
active: !composerFocused,
|
|
11313
|
+
onSelect: (answer, isCustom) => {
|
|
11314
|
+
addSystemMessage(`> ${answer}`);
|
|
11315
|
+
agentQuestion.resolve({ questionId: agentQuestion.question.id, answer, isCustom });
|
|
11316
|
+
clearPendingQuestion();
|
|
11317
|
+
const nextQuestion = getPendingQuestion();
|
|
11318
|
+
setAgentQuestion(nextQuestion);
|
|
11319
|
+
setComposerFocused(nextQuestion === null);
|
|
11320
|
+
}
|
|
11321
|
+
}
|
|
11322
|
+
),
|
|
10393
11323
|
/* @__PURE__ */ jsx6(Box5, { borderStyle: "round", borderColor: agentQuestion ? themeColors.warning : composerFocused ? themeColors.borderFocused : themeColors.borderDefault, paddingX: 1, flexDirection: "column", width: contentWidth, children: /* @__PURE__ */ jsxs4(Box5, { children: [
|
|
10394
11324
|
/* @__PURE__ */ jsx6(PromptPrefix, { busy, frame: activityFrame, hasQuestion: !!agentQuestion, mode: agentMode }),
|
|
10395
11325
|
/* @__PURE__ */ jsx6(
|
|
@@ -10405,7 +11335,7 @@ ${error.stack}` : String(error)}` }
|
|
|
10405
11335
|
cursorToEnd,
|
|
10406
11336
|
accentColor: themeColors.accent,
|
|
10407
11337
|
mutedColor: themeColors.muted,
|
|
10408
|
-
placeholder: agentQuestion ? "
|
|
11338
|
+
placeholder: agentQuestion ? "Answer in the card above" : !hasAuth ? "Type /auth or /config apikey" : !hasWorkspace ? "Type /init to create workspace" : pendingUpdates.length > 0 && composerFocused ? "Esc to review updates, or keep drafting" : busy ? "Draft your next message while the agent works" : "Ask a question or type / for commands"
|
|
10409
11339
|
}
|
|
10410
11340
|
)
|
|
10411
11341
|
] }) }),
|
|
@@ -10434,6 +11364,35 @@ ${error.stack}` : String(error)}` }
|
|
|
10434
11364
|
] }) });
|
|
10435
11365
|
}
|
|
10436
11366
|
|
|
11367
|
+
// src/tui/ink-stdout.ts
|
|
11368
|
+
function getStableDimension(current, fallback) {
|
|
11369
|
+
return typeof current === "number" && current > 0 ? current : fallback;
|
|
11370
|
+
}
|
|
11371
|
+
function createStableInkStdout(stdout, options) {
|
|
11372
|
+
const forceFullRedraw = options?.forceFullRedraw ?? process.env.OPEN_RESEARCH_FORCE_FULL_REDRAW === "1";
|
|
11373
|
+
let lastRows = getStableDimension(stdout.rows, 24);
|
|
11374
|
+
let lastColumns = getStableDimension(stdout.columns, 80);
|
|
11375
|
+
return new Proxy(stdout, {
|
|
11376
|
+
get(target, prop, receiver) {
|
|
11377
|
+
if (prop === "rows") {
|
|
11378
|
+
if (forceFullRedraw) {
|
|
11379
|
+
return 0;
|
|
11380
|
+
}
|
|
11381
|
+
const rows = Reflect.get(target, prop, receiver);
|
|
11382
|
+
lastRows = getStableDimension(rows, lastRows);
|
|
11383
|
+
return lastRows;
|
|
11384
|
+
}
|
|
11385
|
+
if (prop === "columns") {
|
|
11386
|
+
const columns = Reflect.get(target, prop, receiver);
|
|
11387
|
+
lastColumns = getStableDimension(columns, lastColumns);
|
|
11388
|
+
return lastColumns;
|
|
11389
|
+
}
|
|
11390
|
+
const value = Reflect.get(target, prop, receiver);
|
|
11391
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
11392
|
+
}
|
|
11393
|
+
});
|
|
11394
|
+
}
|
|
11395
|
+
|
|
10437
11396
|
// src/cli.ts
|
|
10438
11397
|
var program = new Command();
|
|
10439
11398
|
program.name("open-research").version(getPackageVersion()).description("Local-first research CLI powered by OpenAI account auth or API keys.").argument("[workspacePath]", "Optional workspace path to open").action(async (workspacePath) => {
|
|
@@ -10442,7 +11401,7 @@ program.name("open-research").version(getPackageVersion()).description("Local-fi
|
|
|
10442
11401
|
const project = await loadWorkspaceProject(target);
|
|
10443
11402
|
const hasProvider = await hasConfiguredProvider();
|
|
10444
11403
|
render(
|
|
10445
|
-
|
|
11404
|
+
React7.createElement(App, {
|
|
10446
11405
|
initialState: {
|
|
10447
11406
|
authStatus: hasProvider ? "connected" : "missing",
|
|
10448
11407
|
workspacePath: project ? target : null,
|
|
@@ -10450,7 +11409,10 @@ program.name("open-research").version(getPackageVersion()).description("Local-fi
|
|
|
10450
11409
|
pendingUpdates: []
|
|
10451
11410
|
}
|
|
10452
11411
|
}),
|
|
10453
|
-
{
|
|
11412
|
+
{
|
|
11413
|
+
exitOnCtrlC: false,
|
|
11414
|
+
stdout: createStableInkStdout(process.stdout)
|
|
11415
|
+
}
|
|
10454
11416
|
);
|
|
10455
11417
|
});
|
|
10456
11418
|
program.command("init").argument("[workspacePath]").description("Initialize an Open Research workspace.").action(async (workspacePath) => {
|
|
@@ -10541,7 +11503,7 @@ skills.command("validate").argument("[nameOrPath]").description("Validate one us
|
|
|
10541
11503
|
await ensureOpenResearchConfig();
|
|
10542
11504
|
const skillDir = nameOrPath ? path19.isAbsolute(nameOrPath) ? nameOrPath : path19.join(getOpenResearchSkillsDir(), nameOrPath) : getOpenResearchSkillsDir();
|
|
10543
11505
|
const stat = await import("fs/promises").then(
|
|
10544
|
-
(
|
|
11506
|
+
(fs18) => fs18.stat(skillDir).catch(() => null)
|
|
10545
11507
|
);
|
|
10546
11508
|
if (!stat) {
|
|
10547
11509
|
throw new Error(`Skill path not found: ${skillDir}`);
|