@ycodium-ai/agent-codex 0.2.2488 → 0.2.2510

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/README.md CHANGED
@@ -19,6 +19,12 @@ Facts:
19
19
  - `sessionModelSwitch` is `in-session`.
20
20
  - Native ChatGPT device-code login is supported: `deviceCodeSignInArgs` runs
21
21
  `codex login --device-auth`.
22
+ - Account section UI: the add-provider dialog and the provider detail page draw
23
+ this plugin's own account section (`src/ui/`, bundled to `assets/ui.js`) through the host's
24
+ provider settings slot, declared in `package.json#ycodium.ui.providerSettings`. It
25
+ covers choosing an account already on the machine, ChatGPT sign-in, API URL and
26
+ key, and the separate shadow directory a second account gets. The UI uses the
27
+ host's React and `@ycodium-ai/plugin-api/provider-settings`.
22
28
 
23
29
  Develop with `vp run --filter @ycodium-ai/agent-codex typecheck`, `build`, and `test`.
24
30
 
package/assets/ui.js ADDED
@@ -0,0 +1,79 @@
1
+ var Q=window.__ycodiumPluginHost.modules?.["@ycodium-ai/plugin-api/provider-settings"];if(!Q)throw new Error("This plugin needs a newer Ycodium app. Update the app.");var{PROVIDER_SETTINGS_VIEW_CAPABILITY:Z,accountDirectoryCandidates:ee,accountDirectorySlug:Ye,accountProbeHasSignedInAccount:te,allocateAccountDirectory:ne,providerInstanceAccountPaths:oe}=Q;var ke=window.__ycodiumPluginHost.react;var{Activity:Xe,Children:je,Component:ze,Fragment:$e,Profiler:qe,PureComponent:Ge,StrictMode:We,Suspense:Je,__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE:Qe,__COMPILER_RUNTIME:Ze,act:et,cache:tt,cacheSignal:nt,captureOwnerStack:ot,cloneElement:rt,createContext:re,createElement:ae,createRef:at,forwardRef:it,isValidElement:ct,lazy:st,memo:dt,startTransition:ut,unstable_useCacheRefresh:lt,use:pt,useActionState:gt,useCallback:mt,useContext:ie,useDebugValue:ft,useDeferredValue:yt,useEffect:A,useEffectEvent:ht,useId:Pt,useImperativeHandle:wt,useInsertionEffect:xt,useLayoutEffect:vt,useMemo:St,useOptimistic:At,useReducer:Ct,useRef:m,useState:K,useSyncExternalStore:bt,useTransition:It,version:Nt}=ke;var ce=re(null),se=ce.Provider;function h(){let e=ie(ce);if(e===null)throw new Error("Codex UI has not been registered.");return e}var x="YCODIUM_CODEX_API_KEY";var B={configKeys:["shadowHomePath","homePath"],defaultDirectory:"~/.codex"},M="account",k="codex",He="Codex";function u(e,t){if(e===null||typeof e!="object")return"";let r=e[t];return typeof r=="string"?r:""}function f(e){let t=u(e.config,"authMode");return t==="chatgpt"||t==="apiKey"?t:"default"}function H(e){return e.displayName?.trim()||He}function de(e){if(f(e)!=="apiKey")return!0;try{let t=new URL(u(e.config,"apiBaseUrl"));if(t.protocol!=="https:"&&t.protocol!=="http:")return!1}catch{return!1}return e.environment.some(t=>t.name===x&&(t.valueRedacted===!0||t.value.trim().length>0))}function v(e){if(f(e)!=="chatgpt")return null;let t=e.config;if(t.shadowHomePath!=="")return null;let r=u(t,"homePath");return r.length>0?r:null}function ue(e){return f(e)==="chatgpt"&&v(e)===null}function le(e){return{config:{homePath:e,shadowHomePath:"",authMode:"chatgpt"}}}function pe(e,t,r){let a=r==="add"?v(e):null;return t==="apiKey"&&a!==null?{config:{authMode:t,homePath:void 0,shadowHomePath:void 0}}:{config:{authMode:t}}}function ge(){return{config:{authMode:"chatgpt",shadowHomePath:void 0}}}function Oe(e){return u(e.config,"shadowHomePath").trim().length===0}async function me(e){let t=e.siblings.filter(o=>o.instance.driver===k).filter(o=>f(o.instance)!=="apiKey").map(o=>({id:o.instanceId,path:u(o.instance.config,"shadowHomePath")||u(o.instance.config,"homePath")})),r=[...new Set(["",...t.map(o=>o.path)])],a=await e.probe(r),c=new Map(t.flatMap(({id:o,path:l})=>{let S=a[r.indexOf(l)];return S?[[S.accountPath,o]]:[]}));return{probes:[...new Map(a.map(o=>[o.accountPath,o])).values()],configuredPaths:c,defaultAccount:a[0]??null}}function fe(e){return e.filter(t=>t.authType!=="apiKey"&&(t.state==="signed-in"||t.state==="expired"))}function ye(e){return{none:e.every(r=>r.state==="signed-out"||r.authType==="apiKey")?e.length===0?"detectFailed":"noAccounts":null,unreadable:e.some(r=>r.state==="unreadable")}}async function he(e){let t=oe(e.instances,k,B),r=ee({displayName:e.displayName,taken:t,rule:B}),c=(await e.probe([...t,...r])).slice(t.length).findIndex(o=>!o.directoryExists);return c<0?void 0:r[c]}function O(e){return ne({driver:k,displayName:e.displayName,instances:e.instances,rule:B,probe:e.probe})}function Pe(e){let t=f(e);return(t==="chatgpt"||t==="apiKey")&&v(e)===null&&Oe(e)}async function we(e,t){let r=u(e.config,"shadowHomePath").trim();if(r.length===0)return!0;let[a]=await t([r]);return a!==void 0&&a.authType!=="apiKey"&&te(a)}function xe(e,t){if(e!==null)return t.find(r=>r.authEmail===e)}function y(e){return(t=>ae(h().ui[e],{...t,className:`ycodium-codex-ui ${typeof t.className=="string"?t.className:""}`}))}var C=y("Button"),U=y("DraftInput"),ve=y("Select"),b=y("SelectItem"),Se=y("SelectPopup"),Ae=y("SelectTrigger"),Ce=y("SelectValue");function be(){let e=h().i18n.usePluginTranslation();return{t:(t,r)=>e(t,r)}}var{jsx:i,jsxs:s,Fragment:Ie}=window.__ycodiumPluginHost.jsxRuntime;function Ne(e){let{t}=be(),r=h(),{placement:a,instance:c}=e,o=m(e);o.current=e;let[l,S]=K(null),[P,L]=K(!1),I=m(!1),N=m(!1),V=m(null),w=m(0),p=f(c),Te=v(c),Y=H(c),E=()=>[...o.current.siblings.map(n=>n.instance),...o.current.placement==="edit"?[o.current.instance]:[]],F=()=>r.notify(t("account.directoryUnavailable"),"error"),Re=n=>{V.current=n.length>0?n:null,o.current.onChange({config:{shadowHomePath:n.length>0?n:void 0}})},X=n=>{let d=++w.current;he({displayName:n,instances:E(),probe:o.current.probeAccount}).then(g=>{d===w.current&&(g!==void 0?Re(g):F())})},j=async()=>{if(!I.current){I.current=!0,L(!0);try{let n=await me({siblings:o.current.siblings,probe:o.current.probeAccount});S({probes:n.probes,configuredPaths:n.configuredPaths})}finally{L(!1),I.current=!1}}};A(()=>{a==="add"&&e.active&&j()},[a,e.active]);let z=m(c.displayName?.trim()??"");A(()=>{let n=c.displayName?.trim()??"",d=z.current;z.current=n,!(a!=="add"||!N.current)&&(n.length===0||n===d||u(o.current.instance.config,"shadowHomePath")===(V.current??"")&&X(n))},[c.displayName,a]);let $=a==="edit"?xe(e.authEmail,e.siblings):void 0,T=de(c),R=a==="add"&&ue(c),q=a==="edit"&&p==="apiKey",G=$===void 0?void 0:t("account.duplicate",{name:$.displayName}),W=R?t("account.addAndSignIn"):void 0,{onReport:J}=e;A(()=>{J({complete:T,submitLabel:W,signInAfterAdd:R,hidesSignIn:q,notice:G,prepare:async n=>{if(!Pe(n))return null;let d=await O({displayName:H(n),instances:E(),probe:o.current.probeAccount});if(d===void 0)throw new Error(t("account.directoryUnavailable"));return{config:{shadowHomePath:d}}}})},[J,T,W,R,q,G]);let _e=n=>{if(n!==p){if(w.current+=1,!(a==="edit"&&n==="apiKey")){e.onChange(pe(c,n,a));return}(async()=>{let d=await we(c,o.current.probeAccount),g;d&&(g=await O({displayName:Y,instances:E(),probe:o.current.probeAccount}),g===void 0&&F()),o.current.onChange({config:{authMode:"apiKey",...g===void 0?{}:{shadowHomePath:g}}})})()}},De=n=>{w.current+=1;let d=l?.configuredPaths.get(n.accountPath);if(d!==void 0){e.openInstance(d);return}N.current=!1,e.onChange(le(n.accountPath))},Ke=()=>{w.current+=1,N.current=!0,e.onChange(ge()),X(Y)},_=l===null?null:ye(l.probes),Be=a==="add"&&p!=="apiKey"?s("div",{className:"grid gap-2","aria-busy":P,children:[P?i("p",{className:"text-xs text-muted-foreground",children:t("account.detecting")}):null,!P&&_?.none!=null?i("p",{className:"text-xs text-muted-foreground",children:t(`account.${_.none}`)}):null,l!==null?fe(l.probes).map(n=>s(C,{variant:"outline","aria-pressed":Te===n.accountPath,onClick:()=>De(n),children:[n.email===void 0?t("account.existing"):t("account.useAccount",{email:n.email}),l.configuredPaths.has(n.accountPath)?` \xB7 ${t("account.alreadyAdded")}`:""]},n.accountPath)):null,!P&&_?.unreadable===!0?i("p",{className:"text-xs text-muted-foreground",children:t("account.detectFailed")}):null,i(C,{variant:"outline",onClick:Ke,children:t("account.addAnother")}),i(C,{size:"xs",variant:"ghost",disabled:P,onClick:()=>{j()},children:t("account.retry")})]}):null,D=c.environment.find(n=>n.name===x),Me=s("div",{className:"grid gap-3",children:[s("label",{className:"grid gap-1",children:[i("span",{className:"text-xs font-medium",children:t("account.method")}),s(ve,{value:p,onValueChange:n=>_e(Ue(n)),children:[i(Ae,{"aria-label":t("account.method"),children:i(Ce,{children:t(p==="apiKey"?"account.apiKey":p==="chatgpt"?"account.chatgpt":"account.existing")})}),s(Se,{children:[i(b,{value:"default",children:t("account.existing")}),i(b,{value:"chatgpt",children:t("account.chatgpt")}),i(b,{value:"apiKey",children:t("account.apiKey")})]})]})]}),p==="apiKey"?s(Ie,{children:[s("label",{className:"grid gap-1",children:[i("span",{className:"text-xs font-medium",children:t("account.url")}),i(U,{value:u(c.config,"apiBaseUrl"),onCommit:n=>e.onChange({config:{apiBaseUrl:n.trim()}}),spellCheck:!1,"aria-label":t("account.url")})]}),s("label",{className:"grid gap-1",children:[i("span",{className:"text-xs font-medium",children:t("account.key")}),i(U,{value:D?.valueRedacted===!0?"":D?.value??"",type:"password",autoComplete:"off",spellCheck:!1,"aria-label":t("account.key"),placeholder:D?.valueRedacted===!0?t("account.keyStoredPlaceholder"):void 0,onCommit:n=>e.onChange({environment:[{name:x,value:n,sensitive:!0}]})})]}),T?null:i("p",{className:"text-xs text-muted-foreground",children:t("account.apiRequired")})]}):i("p",{className:"text-xs text-muted-foreground",children:t("account.loginHint")})]});return s("div",{className:"ycodium-codex-ui grid gap-6",children:[Be,Me]})}function Ue(e){return e==="chatgpt"||e==="apiKey"?e:"default"}var Ee=`/*! tailwindcss v4.3.0 | MIT License | https://tailwindcss.com */
2
+ @layer properties;
3
+ @scope (.ycodium-codex-ui) {
4
+ .container:where(:scope, *) {
5
+ width: 100%;
6
+ @media (width >= 40rem) {
7
+ max-width: 40rem;
8
+ }
9
+ @media (width >= 48rem) {
10
+ max-width: 48rem;
11
+ }
12
+ @media (width >= 64rem) {
13
+ max-width: 64rem;
14
+ }
15
+ @media (width >= 80rem) {
16
+ max-width: 80rem;
17
+ }
18
+ @media (width >= 96rem) {
19
+ max-width: 96rem;
20
+ }
21
+ }
22
+ .grid:where(:scope, *) {
23
+ display: grid;
24
+ }
25
+ .gap-1:where(:scope, *) {
26
+ gap: calc(var(--spacing, 0.25rem) * 1);
27
+ }
28
+ .gap-2:where(:scope, *) {
29
+ gap: calc(var(--spacing, 0.25rem) * 2);
30
+ }
31
+ .gap-3:where(:scope, *) {
32
+ gap: calc(var(--spacing, 0.25rem) * 3);
33
+ }
34
+ .gap-6:where(:scope, *) {
35
+ gap: calc(var(--spacing, 0.25rem) * 6);
36
+ }
37
+ .text-xs:where(:scope, *) {
38
+ font-size: var(--text-xs, 0.75rem);
39
+ line-height: var(--tw-leading, var(--text-xs--line-height, calc(1 / 0.75)));
40
+ }
41
+ .font-medium:where(:scope, *) {
42
+ --tw-font-weight: var(--font-weight-medium, 500);
43
+ font-weight: var(--font-weight-medium, 500);
44
+ }
45
+ .text-muted-foreground:where(:scope, *) {
46
+ color: var(--contrast-muted-foreground);
47
+ }
48
+ .shadow:where(:scope, *) {
49
+ --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
50
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
51
+ }
52
+ .outline:where(:scope, *) {
53
+ outline-style: var(--tw-outline-style);
54
+ outline-width: 1px;
55
+ }
56
+ }
57
+ @layer properties {
58
+ @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
59
+ *, ::before, ::after, ::backdrop {
60
+ --tw-font-weight: initial;
61
+ --tw-shadow: 0 0 #0000;
62
+ --tw-shadow-color: initial;
63
+ --tw-shadow-alpha: 100%;
64
+ --tw-inset-shadow: 0 0 #0000;
65
+ --tw-inset-shadow-color: initial;
66
+ --tw-inset-shadow-alpha: 100%;
67
+ --tw-ring-color: initial;
68
+ --tw-ring-shadow: 0 0 #0000;
69
+ --tw-inset-ring-color: initial;
70
+ --tw-inset-ring-shadow: 0 0 #0000;
71
+ --tw-ring-inset: initial;
72
+ --tw-ring-offset-width: 0px;
73
+ --tw-ring-offset-color: #fff;
74
+ --tw-ring-offset-shadow: 0 0 #0000;
75
+ --tw-outline-style: solid;
76
+ }
77
+ }
78
+ }
79
+ `;function Ve(e){let t=r=>s(se,{value:e,children:[i("style",{children:Ee}),i(Ne,{...r})]});return{pages:{},panels:{},views:{[M]:t},viewCapabilities:{[M]:[Z]}}}export{Ve as default};
package/dist/index.js CHANGED
@@ -429,7 +429,11 @@ import { ExecutorSpawnRefusedError } from "@ycodium-ai/plugin-api/executor";
429
429
 
430
430
  // src/config.ts
431
431
  import { modelSelectionStringOption } from "@ycodium-ai/plugin-api/executor";
432
+
433
+ // src/apiKeyEnv.ts
432
434
  var CODEX_INSTANCE_API_KEY_ENV = "YCODIUM_CODEX_API_KEY";
435
+
436
+ // src/config.ts
433
437
  var CODEX_INSTANCE_MODEL_PROVIDER = "ycodium_api";
434
438
  function isRecord2(value) {
435
439
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -913,6 +917,28 @@ function jsonRpcErrorMessage(error) {
913
917
  }
914
918
 
915
919
  // src/rpc.ts
920
+ function createLineAssembler() {
921
+ const fragments = [];
922
+ return {
923
+ push: (text) => {
924
+ const lines = [];
925
+ let start = 0;
926
+ for (let newline = text.indexOf("\n"); newline !== -1; newline = text.indexOf("\n", start)) {
927
+ fragments.push(text.slice(start, newline));
928
+ lines.push(fragments.join(""));
929
+ fragments.length = 0;
930
+ start = newline + 1;
931
+ }
932
+ if (start < text.length) fragments.push(text.slice(start));
933
+ return lines;
934
+ },
935
+ end: () => {
936
+ const line = fragments.join("");
937
+ fragments.length = 0;
938
+ return line;
939
+ }
940
+ };
941
+ }
916
942
  var STDERR_TAIL_LIMIT = 4e3;
917
943
  var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
918
944
  var ANSI_ESCAPE_CHAR = String.fromCharCode(27);
@@ -980,17 +1006,12 @@ async function createCodexRpcClient(handle, handlers) {
980
1006
  }
981
1007
  };
982
1008
  const pumpStdout = (async () => {
983
- let buffer = "";
1009
+ const lines = createLineAssembler();
984
1010
  for await (const chunk of handle.stdout) {
985
- buffer += decoder.decode(chunk, { stream: true });
986
- let index = buffer.indexOf("\n");
987
- while (index >= 0) {
988
- dispatch(buffer.slice(0, index));
989
- buffer = buffer.slice(index + 1);
990
- index = buffer.indexOf("\n");
991
- }
1011
+ for (const line of lines.push(decoder.decode(chunk, { stream: true }))) dispatch(line);
992
1012
  }
993
- if (buffer.length > 0) dispatch(buffer);
1013
+ const last = lines.end();
1014
+ if (last.length > 0) dispatch(last);
994
1015
  })().catch(() => void 0);
995
1016
  const pumpStderr = (async () => {
996
1017
  const errorDecoder = new TextDecoder();
@@ -1455,7 +1476,7 @@ function turnCompletedControlFlow(params) {
1455
1476
  const error = isRecord3(turn["error"]) ? trimText(turn["error"]["message"]) : void 0;
1456
1477
  return error ? { state, errorMessage: error } : { state };
1457
1478
  }
1458
- function toApplyPatchFileChanges(params) {
1479
+ function fileChangeEntries(params) {
1459
1480
  if (!isRecord3(params)) return [];
1460
1481
  const item = isRecord3(params["item"]) ? params["item"] : params;
1461
1482
  const changes = item["changes"] ?? item["fileChanges"] ?? params["fileChanges"];
@@ -1469,10 +1490,32 @@ function toApplyPatchFileChanges(params) {
1469
1490
  if (!isRecord3(entry)) continue;
1470
1491
  const path = asString(entry["path"]) ?? asString(entry["file"]);
1471
1492
  if (!path) continue;
1472
- const kind = (asString(entry["type"]) ?? asString(entry["kind"]) ?? "update").toLowerCase();
1473
- const movePath = asString(entry["move_path"]) ?? asString(entry["movePath"]);
1474
- const diff = asString(entry["unified_diff"]) ?? asString(entry["unifiedDiff"]);
1475
- const content = asString(entry["content"]);
1493
+ out.push({
1494
+ path,
1495
+ kind: (asString(entry["type"]) ?? asString(entry["kind"]) ?? "update").toLowerCase(),
1496
+ movePath: asString(entry["move_path"]) ?? asString(entry["movePath"]),
1497
+ diff: asString(entry["unified_diff"]) ?? asString(entry["unifiedDiff"]),
1498
+ content: asString(entry["content"])
1499
+ });
1500
+ }
1501
+ return out;
1502
+ }
1503
+ var MAX_DESCRIBED_FILE_CHANGES = 20;
1504
+ function describeFileChanges(params) {
1505
+ const entries = fileChangeEntries(params).toSorted(
1506
+ (left, right) => left.path.localeCompare(right.path)
1507
+ );
1508
+ if (entries.length === 0) return void 0;
1509
+ const lines = entries.slice(0, MAX_DESCRIBED_FILE_CHANGES).map(
1510
+ (entry) => entry.movePath ? `${entry.kind} ${entry.path} -> ${entry.movePath}` : `${entry.kind} ${entry.path}`
1511
+ );
1512
+ const remaining = entries.length - lines.length;
1513
+ return remaining > 0 ? `${lines.join("\n")}
1514
+ +${remaining} more` : lines.join("\n");
1515
+ }
1516
+ function toApplyPatchFileChanges(params) {
1517
+ const out = [];
1518
+ for (const { path, kind, movePath, diff, content } of fileChangeEntries(params)) {
1476
1519
  if (kind === "add") {
1477
1520
  out.push({
1478
1521
  path,
@@ -1728,8 +1771,10 @@ function describeFileChangeApproval(params) {
1728
1771
  const reason = trimText(record["reason"]);
1729
1772
  const grantRoot = asString(record["grantRoot"]);
1730
1773
  const callId = asString(record["callId"]);
1774
+ const detail = reason ?? describeFileChanges(record) ?? trimText(grantRoot);
1731
1775
  return {
1732
1776
  ...reason ? { reason } : {},
1777
+ ...detail ? { detail } : {},
1733
1778
  ...grantRoot ? { grantRoot } : {},
1734
1779
  ...callId ? { callId } : {},
1735
1780
  fileChanges: toApplyPatchFileChanges(record)
@@ -2390,7 +2435,7 @@ function mapServerRequestToEvents(input) {
2390
2435
  payload["args"] = input.params;
2391
2436
  } else if (input.method === "item/fileChange/requestApproval" || input.method.includes("applyPatch")) {
2392
2437
  const described = describeFileChangeApproval(input.params);
2393
- if (described.reason) payload["detail"] = described.reason;
2438
+ if (described.detail) payload["detail"] = described.detail;
2394
2439
  if (described.fileChanges.length > 0) payload["fileChanges"] = described.fileChanges;
2395
2440
  payload["args"] = {
2396
2441
  ...described.callId ? { callId: described.callId } : {},
@@ -4409,6 +4454,8 @@ var createCodexInstance = (config, host, attachments) => {
4409
4454
  ...CODEX_NATIVE_MODE_REPORT
4410
4455
  };
4411
4456
  },
4457
+ // 有意不处理 input.readOnlyDirectories:codex 的沙箱只限制写入,读不受限,
4458
+ // 附件目录本来就能读,无需放行。
4412
4459
  startSession: async (input, signal) => {
4413
4460
  if (disposed) throw new Error("instance disposed");
4414
4461
  if (signal?.aborted) throw new Error("aborted");
package/locales/en.json CHANGED
@@ -19,5 +19,26 @@
19
19
  "title": "Launch arguments",
20
20
  "description": "Additional CLI arguments passed to codex app-server on session start."
21
21
  }
22
+ },
23
+ "account": {
24
+ "method": "Account connection",
25
+ "existing": "Use existing Codex configuration",
26
+ "chatgpt": "ChatGPT sign-in",
27
+ "apiKey": "API URL and key",
28
+ "url": "API base URL",
29
+ "key": "API key",
30
+ "keyStoredPlaceholder": "Stored secret — enter a new value to replace it",
31
+ "apiRequired": "Enter an HTTP or HTTPS API URL and an API key.",
32
+ "loginHint": "Use an account already on this machine, or sign in to another account.",
33
+ "detecting": "Checking accounts on this machine...",
34
+ "detectFailed": "Could not check accounts. Try again.",
35
+ "noAccounts": "No ChatGPT account was found on this machine.",
36
+ "addAnother": "Sign in to another ChatGPT account",
37
+ "useAccount": "Use {{email}}",
38
+ "alreadyAdded": "Already added",
39
+ "retry": "Check again",
40
+ "duplicate": "This ChatGPT account is already available as {{name}}.",
41
+ "directoryUnavailable": "Could not prepare a separate account on this machine. Check the connection and try again.",
42
+ "addAndSignIn": "Add and sign in"
22
43
  }
23
44
  }
@@ -19,5 +19,26 @@
19
19
  "title": "启动参数",
20
20
  "description": "会话启动时传给 codex app-server 的额外命令行参数。"
21
21
  }
22
+ },
23
+ "account": {
24
+ "method": "账户连接方式",
25
+ "existing": "使用现有 Codex 配置",
26
+ "chatgpt": "ChatGPT 登录",
27
+ "apiKey": "API URL 和密钥",
28
+ "url": "API 基础 URL",
29
+ "key": "API 密钥",
30
+ "keyStoredPlaceholder": "已存储的密钥 — 输入新值以替换",
31
+ "apiRequired": "请输入 HTTP 或 HTTPS API URL 和 API 密钥。",
32
+ "loginHint": "使用这台机器上已有的账户,或登录其他账户。",
33
+ "detecting": "正在检查这台机器上的账户...",
34
+ "detectFailed": "无法检查账户。请重试。",
35
+ "noAccounts": "在这台机器上未找到 ChatGPT 账户。",
36
+ "addAnother": "登录其他 ChatGPT 账户",
37
+ "useAccount": "使用 {{email}}",
38
+ "alreadyAdded": "已添加",
39
+ "retry": "再次检查",
40
+ "duplicate": "此 ChatGPT 账户已作为 {{name}} 可用。",
41
+ "directoryUnavailable": "无法在这台机器上为单独的账户做好准备。请检查连接后重试。",
42
+ "addAndSignIn": "添加并登录"
22
43
  }
23
44
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ycodium-ai/agent-codex",
3
- "version": "0.2.2488",
3
+ "version": "0.2.2510",
4
4
  "description": "Codex executor: Codex CLI over the app-server JSON-RPC protocol.",
5
5
  "keywords": [
6
6
  "ycodium",
@@ -18,11 +18,19 @@
18
18
  "access": "public"
19
19
  },
20
20
  "devDependencies": {
21
+ "@tailwindcss/node": "^4.3.0",
22
+ "@tailwindcss/oxide": "^4.3.0",
21
23
  "@types/node": "24.12.4",
24
+ "@types/react": "~19.2.14",
25
+ "@types/react-dom": "^19.2.3",
22
26
  "esbuild": "0.28.1",
27
+ "react": "19.2.6",
28
+ "react-dom": "19.2.6",
29
+ "tailwindcss": "^4.0.0",
30
+ "typescript": "~6.0.3",
23
31
  "vite-plus": "0.2.2",
24
- "@ycodium-ai/plugin-api": "0.2.2488",
25
- "ycodium-executor-testing": "0.0.0"
32
+ "ycodium-executor-testing": "0.0.0",
33
+ "@ycodium-ai/plugin-api": "0.2.2510"
26
34
  },
27
35
  "ycodium": {
28
36
  "plugin": "./dist/index.js",
@@ -33,11 +41,31 @@
33
41
  {
34
42
  "kind": "executor"
35
43
  }
36
- ]
44
+ ],
45
+ "ui": {
46
+ "assets": "./assets",
47
+ "entry": "ui.js",
48
+ "providerSettings": [
49
+ {
50
+ "id": "account",
51
+ "driverKind": "codex",
52
+ "ownedFields": {
53
+ "add": [
54
+ "homePath",
55
+ "shadowHomePath"
56
+ ]
57
+ },
58
+ "ownedEnvironment": [
59
+ "YCODIUM_CODEX_API_KEY"
60
+ ],
61
+ "genericFields": "collapsed"
62
+ }
63
+ ]
64
+ }
37
65
  },
38
66
  "scripts": {
39
67
  "build": "node build.mjs",
40
- "typecheck": "tsgo --noEmit",
68
+ "typecheck": "tsgo --noEmit && tsgo --noEmit -p tsconfig.ui.json",
41
69
  "test": "vp test run"
42
70
  }
43
71
  }