linkshell-cli 0.5.33 → 0.5.34
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/cli/src/runtime/acp/agent-workspace.d.ts +10 -0
- package/dist/cli/src/runtime/acp/agent-workspace.js +39 -2
- package/dist/cli/src/runtime/acp/agent-workspace.js.map +1 -1
- package/dist/cli/src/runtime/acp/claude-sdk-client.d.ts +6 -0
- package/dist/cli/src/runtime/acp/claude-sdk-client.js +51 -29
- package/dist/cli/src/runtime/acp/claude-sdk-client.js.map +1 -1
- package/dist/cli/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/runtime/acp/agent-workspace.ts +43 -2
- package/src/runtime/acp/claude-sdk-client.ts +51 -22
- package/web/assets/{FileBrowser-B1vcZ0wW.js → FileBrowser-9wRS6a0E.js} +1 -1
- package/web/assets/{PortPreview-CugEjE0m.js → PortPreview-yvXMn63g.js} +1 -1
- package/web/assets/{TerminalPanel-DgSFo-6s.js → TerminalPanel-xtGQI_fv.js} +1 -1
- package/web/assets/{UsageDashboard-DTRkZRsh.js → UsageDashboard-ByQ6mehF.js} +1 -1
- package/web/assets/{index-ByEwVA14.js → index-pqslvKcS.js} +6 -6
- package/web/index.html +1 -1
|
@@ -1918,6 +1918,9 @@ function previewFromTimelineItem(item: AgentTimelineItem): string | undefined {
|
|
|
1918
1918
|
|
|
1919
1919
|
export class AgentWorkspaceProxy {
|
|
1920
1920
|
private clients = new Map<AgentProvider, AcpClient | ClaudeSdkClient | ClaudeStreamJsonClient>();
|
|
1921
|
+
// In-flight client starts, so concurrent ensureProviderClient calls (eager
|
|
1922
|
+
// initialize() racing conversation.open) share one subprocess spawn.
|
|
1923
|
+
private clientStartPromises = new Map<AgentProvider, Promise<AcpClient | ClaudeSdkClient | ClaudeStreamJsonClient | undefined>>();
|
|
1921
1924
|
private agentProtocols = new Map<AgentProvider, AgentProtocol>();
|
|
1922
1925
|
private providerCapabilities = new Map<AgentProvider, ProviderRuntimeCapabilities>();
|
|
1923
1926
|
private providerErrors = new Map<AgentProvider, string>();
|
|
@@ -2247,6 +2250,23 @@ export class AgentWorkspaceProxy {
|
|
|
2247
2250
|
const existing = this.clients.get(provider);
|
|
2248
2251
|
if (existing) return existing;
|
|
2249
2252
|
|
|
2253
|
+
// Dedupe concurrent starts: eager initialize() and conversation.open can
|
|
2254
|
+
// race here before this.clients is populated (it's only set AFTER the
|
|
2255
|
+
// client finishes initializing), which would spawn the provider subprocess
|
|
2256
|
+
// twice. Memoize the in-flight promise so both callers share one spawn;
|
|
2257
|
+
// drop it once settled so a failed start can be retried.
|
|
2258
|
+
const inflight = this.clientStartPromises.get(provider);
|
|
2259
|
+
if (inflight) return inflight;
|
|
2260
|
+
const start = this.startProviderClient(provider);
|
|
2261
|
+
this.clientStartPromises.set(provider, start);
|
|
2262
|
+
try {
|
|
2263
|
+
return await start;
|
|
2264
|
+
} finally {
|
|
2265
|
+
this.clientStartPromises.delete(provider);
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
private async startProviderClient(provider: AgentProvider): Promise<AcpClient | ClaudeSdkClient | ClaudeStreamJsonClient | undefined> {
|
|
2250
2270
|
const resolved = resolveAgentCommand({
|
|
2251
2271
|
provider,
|
|
2252
2272
|
command: this.input.command,
|
|
@@ -2299,7 +2319,7 @@ export class AgentWorkspaceProxy {
|
|
|
2299
2319
|
const client = await tryCreateClient(resolved);
|
|
2300
2320
|
this.clients.set(provider, client);
|
|
2301
2321
|
this.providerErrors.delete(provider);
|
|
2302
|
-
|
|
2322
|
+
this.refreshProviderCapabilitiesInBackground(provider, client, resolved.protocol);
|
|
2303
2323
|
this.status = "idle";
|
|
2304
2324
|
this.error = undefined;
|
|
2305
2325
|
this.sendCapabilities();
|
|
@@ -2319,7 +2339,7 @@ export class AgentWorkspaceProxy {
|
|
|
2319
2339
|
const client = await tryCreateClient(fallback);
|
|
2320
2340
|
this.clients.set(provider, client);
|
|
2321
2341
|
this.providerErrors.delete(provider);
|
|
2322
|
-
|
|
2342
|
+
this.refreshProviderCapabilitiesInBackground(provider, client, fallback.protocol);
|
|
2323
2343
|
this.status = "idle";
|
|
2324
2344
|
this.error = undefined;
|
|
2325
2345
|
this.sendCapabilities();
|
|
@@ -2339,6 +2359,27 @@ export class AgentWorkspaceProxy {
|
|
|
2339
2359
|
}
|
|
2340
2360
|
}
|
|
2341
2361
|
|
|
2362
|
+
/** Kick off runtime capability discovery WITHOUT blocking the caller.
|
|
2363
|
+
* refreshProviderCapabilities spawns a warm provider subprocess (measured
|
|
2364
|
+
* 858ms warm, multi-second cold for Claude), and nothing on the open/prompt
|
|
2365
|
+
* path needs it — sendCapabilities and prompt-time model resolution both
|
|
2366
|
+
* degrade gracefully to static fallbacks until providerCapabilities is
|
|
2367
|
+
* populated. When discovery lands we push a fresh agent.v2.capabilities so
|
|
2368
|
+
* clients pick up the real model/command list. */
|
|
2369
|
+
private refreshProviderCapabilitiesInBackground(
|
|
2370
|
+
provider: AgentProvider,
|
|
2371
|
+
client: AcpClient | ClaudeSdkClient | ClaudeStreamJsonClient,
|
|
2372
|
+
protocol: AgentProtocol,
|
|
2373
|
+
): void {
|
|
2374
|
+
void this.refreshProviderCapabilities(provider, client, protocol)
|
|
2375
|
+
.then(() => this.sendCapabilities())
|
|
2376
|
+
.catch((error) => {
|
|
2377
|
+
if (this.input.verbose) {
|
|
2378
|
+
process.stderr.write(`[agent:v2] capability refresh failed for ${provider}: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
2379
|
+
}
|
|
2380
|
+
});
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2342
2383
|
private async refreshProviderCapabilities(
|
|
2343
2384
|
provider: AgentProvider,
|
|
2344
2385
|
client: AcpClient | ClaudeSdkClient | ClaudeStreamJsonClient,
|
|
@@ -241,6 +241,11 @@ export function parseClaudeJsonlSession(input: {
|
|
|
241
241
|
// one we see rather than accumulating. Decoupled from any live LinkShell turn
|
|
242
242
|
// — this is read straight from the on-disk transcript.
|
|
243
243
|
let usage: Record<string, unknown> | undefined;
|
|
244
|
+
// Last-seen raw permissionMode ("default" | "acceptEdits" | ...). Claude
|
|
245
|
+
// records it on user records; the LAST one is the session's current mode.
|
|
246
|
+
// Captured here so readSessionConfig can be a cache field read instead of a
|
|
247
|
+
// second full parse of the transcript.
|
|
248
|
+
let permissionMode: string | undefined;
|
|
244
249
|
let index = 0;
|
|
245
250
|
|
|
246
251
|
for (const line of input.text.split(/\r?\n/)) {
|
|
@@ -256,6 +261,7 @@ export function parseClaudeJsonlSession(input: {
|
|
|
256
261
|
continue;
|
|
257
262
|
}
|
|
258
263
|
|
|
264
|
+
permissionMode = stringField(raw, ["permissionMode"]) ?? permissionMode;
|
|
259
265
|
const timestamp = parseTimestamp(raw.timestamp ?? raw.createdAt ?? raw.created_at);
|
|
260
266
|
cwd = stringField(raw, ["cwd", "workingDirectory", "workspacePath"]) ?? cwd;
|
|
261
267
|
if (timestamp !== undefined) {
|
|
@@ -386,6 +392,7 @@ export function parseClaudeJsonlSession(input: {
|
|
|
386
392
|
updatedAt,
|
|
387
393
|
usage,
|
|
388
394
|
turns,
|
|
395
|
+
...(permissionMode ? { permissionMode } : {}),
|
|
389
396
|
},
|
|
390
397
|
};
|
|
391
398
|
}
|
|
@@ -564,6 +571,44 @@ export class ClaudeSdkClient {
|
|
|
564
571
|
// every one of them (the "scan storm"). Only the file being actively appended
|
|
565
572
|
// to changes, so the steady-state cost drops to stat()+status per file.
|
|
566
573
|
private sessionMetaCache = new Map<string, { mtimeMs: number; size: number; base: Record<string, unknown>; midTurn: boolean }>();
|
|
574
|
+
// Full parsed-transcript cache shared by readSession / readSessionConfig /
|
|
575
|
+
// listTurns, keyed by file path and validated by (mtime,size) like
|
|
576
|
+
// sessionMetaCache above. Opening a conversation hits all three of those in
|
|
577
|
+
// quick succession, and every history page re-slices the same parse — on a
|
|
578
|
+
// 39MB transcript that was ~257ms per open plus ~93ms per scroll page of
|
|
579
|
+
// redundant readFileSync + per-line JSON.parse. Unlike sessionMetaCache this
|
|
580
|
+
// holds the FULL turn list (large for big transcripts), so it is bounded to a
|
|
581
|
+
// small LRU (Map iteration order + delete/re-set on hit = LRU).
|
|
582
|
+
private parsedTranscriptCache = new Map<string, { mtimeMs: number; size: number; parsed: Record<string, unknown> }>();
|
|
583
|
+
private static readonly PARSED_TRANSCRIPT_CACHE_MAX = 8;
|
|
584
|
+
|
|
585
|
+
/** Read + parse a session transcript through the LRU cache. Returns the
|
|
586
|
+
* parseClaudeJsonlSession result (stable for a given (mtime,size)); an
|
|
587
|
+
* appended-to or rewritten file re-parses on next access. */
|
|
588
|
+
private parsedTranscript(filePath: string, sessionId: string): Record<string, unknown> {
|
|
589
|
+
const stat = statSync(filePath);
|
|
590
|
+
const cached = this.parsedTranscriptCache.get(filePath);
|
|
591
|
+
if (cached && cached.mtimeMs === stat.mtimeMs && cached.size === stat.size) {
|
|
592
|
+
// Refresh recency so the hottest transcripts survive the LRU bound.
|
|
593
|
+
this.parsedTranscriptCache.delete(filePath);
|
|
594
|
+
this.parsedTranscriptCache.set(filePath, cached);
|
|
595
|
+
return cached.parsed;
|
|
596
|
+
}
|
|
597
|
+
const parsed = parseClaudeJsonlSession({
|
|
598
|
+
text: readFileSync(filePath, "utf8"),
|
|
599
|
+
cwd: this.input.cwd,
|
|
600
|
+
sessionId,
|
|
601
|
+
fallbackUpdatedAt: stat.mtimeMs,
|
|
602
|
+
});
|
|
603
|
+
this.parsedTranscriptCache.delete(filePath);
|
|
604
|
+
this.parsedTranscriptCache.set(filePath, { mtimeMs: stat.mtimeMs, size: stat.size, parsed });
|
|
605
|
+
while (this.parsedTranscriptCache.size > ClaudeSdkClient.PARSED_TRANSCRIPT_CACHE_MAX) {
|
|
606
|
+
const oldest = this.parsedTranscriptCache.keys().next().value;
|
|
607
|
+
if (oldest === undefined) break;
|
|
608
|
+
this.parsedTranscriptCache.delete(oldest);
|
|
609
|
+
}
|
|
610
|
+
return parsed;
|
|
611
|
+
}
|
|
567
612
|
|
|
568
613
|
constructor(
|
|
569
614
|
private readonly input: {
|
|
@@ -622,13 +667,7 @@ export class ClaudeSdkClient {
|
|
|
622
667
|
},
|
|
623
668
|
};
|
|
624
669
|
}
|
|
625
|
-
|
|
626
|
-
return parseClaudeJsonlSession({
|
|
627
|
-
text: readFileSync(filePath, "utf8"),
|
|
628
|
-
cwd: this.input.cwd,
|
|
629
|
-
sessionId: input.sessionId,
|
|
630
|
-
fallbackUpdatedAt: stat.mtimeMs,
|
|
631
|
-
});
|
|
670
|
+
return this.parsedTranscript(filePath, input.sessionId);
|
|
632
671
|
}
|
|
633
672
|
|
|
634
673
|
/** Resolve the on-disk JSONL transcript for a session, preferring the cwd's
|
|
@@ -706,14 +745,10 @@ export class ClaudeSdkClient {
|
|
|
706
745
|
if (!filePath) return {};
|
|
707
746
|
let permissionModeRaw: string | undefined;
|
|
708
747
|
try {
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
try { record = asRecord(JSON.parse(line)); } catch { continue; }
|
|
714
|
-
const pm = stringField(record, ["permissionMode"]);
|
|
715
|
-
if (pm) permissionModeRaw = pm; // keep the last one — the current mode
|
|
716
|
-
}
|
|
748
|
+
// The shared parse already tracked the last-seen permissionMode, so this
|
|
749
|
+
// is a cache field read on the open path (readSession runs right before).
|
|
750
|
+
const thread = asRecord(this.parsedTranscript(filePath, input.sessionId).thread);
|
|
751
|
+
permissionModeRaw = stringField(thread, ["permissionMode"]);
|
|
717
752
|
} catch {
|
|
718
753
|
return {};
|
|
719
754
|
}
|
|
@@ -753,13 +788,7 @@ export class ClaudeSdkClient {
|
|
|
753
788
|
const filePath = this.resolveSessionFile(input.sessionId);
|
|
754
789
|
if (!filePath) return empty;
|
|
755
790
|
|
|
756
|
-
const
|
|
757
|
-
const parsed = parseClaudeJsonlSession({
|
|
758
|
-
text: readFileSync(filePath, "utf8"),
|
|
759
|
-
cwd: this.input.cwd,
|
|
760
|
-
sessionId: input.sessionId,
|
|
761
|
-
fallbackUpdatedAt: stat.mtimeMs,
|
|
762
|
-
});
|
|
791
|
+
const parsed = this.parsedTranscript(filePath, input.sessionId);
|
|
763
792
|
const thread = asRecord(parsed.thread);
|
|
764
793
|
const allTurns = Array.isArray(thread?.turns) ? thread.turns : [];
|
|
765
794
|
const total = allTurns.length;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{r as n,j as e}from"./vendor-react-0zU5oKmd.js";import{k as b,a as w,l as k,h as C,m as z}from"./index-
|
|
1
|
+
import{r as n,j as e}from"./vendor-react-0zU5oKmd.js";import{k as b,a as w,l as k,h as C,m as z}from"./index-pqslvKcS.js";import"./vendor-highlight-Cus7zBI4.js";function I({store:x,initialPath:d,onClose:j}){const[c,u]=n.useState(d||"."),[y,N]=n.useState([]),[o,l]=n.useState(!1),[f,r]=n.useState(null),[i,p]=n.useState(null),a=n.useCallback(async t=>{l(!0),r(null),p(null);try{const s=await x.browse(t,!0);s.error?r(s.error):(u(s.path),N([...s.entries].sort((m,h)=>m.isDirectory!==h.isDirectory?m.isDirectory?-1:1:m.name.localeCompare(h.name))))}catch(s){r(s.message||"浏览失败")}finally{l(!1)}},[x]);n.useEffect(()=>{a(d||".")},[a,d]);const g=async t=>{l(!0),r(null);try{const s=await x.readFile(t.path);s.error?r(s.error):p({path:s.path,content:s.content,truncated:s.truncated})}catch(s){r(s.message||"读取失败")}finally{l(!1)}},v=c.replace(/\/+$/,"").split("/").slice(0,-1).join("/")||"/";return e.jsxs("div",{className:"flex h-full flex-col bg-surface",children:[e.jsxs("div",{className:"flex items-center justify-between border-b border-border px-3 py-2",children:[e.jsxs("span",{className:"flex items-center gap-1.5 font-mono text-2xs text-content-secondary",children:[e.jsx(b,{size:13,className:"text-content-faint"})," 文件"]}),e.jsx("button",{onClick:j,className:"codex-btn-ghost px-2 py-1.5","aria-label":"关闭文件面板",children:e.jsx(w,{size:15})})]}),e.jsxs("div",{className:"flex items-center gap-1 border-b border-border px-2 py-1.5",children:[e.jsx("button",{onClick:()=>a(v),disabled:o,className:"cursor-pointer rounded-md p-1.5 text-content-muted transition-colors hover:bg-surface-overlay hover:text-accent",title:"上级目录","aria-label":"上级目录",children:e.jsx(k,{size:14})}),e.jsx("input",{value:c,onChange:t=>u(t.target.value),onKeyDown:t=>t.key==="Enter"&&a(c),className:"flex-1 bg-transparent font-mono text-2xs text-content-primary outline-none"}),e.jsx("button",{onClick:()=>a(c),disabled:o,className:"cursor-pointer rounded-md p-1.5 text-content-muted transition-colors hover:bg-surface-overlay hover:text-accent",title:"刷新","aria-label":"刷新",children:e.jsx(C,{size:14})})]}),f&&e.jsx("p",{className:"px-3 py-1.5 text-2xs text-danger",children:f}),e.jsxs("div",{className:"min-h-0 flex-1 overflow-auto",children:[o&&e.jsx("p",{className:"px-3 py-2 text-2xs text-content-muted",children:"加载中…"}),!o&&i?e.jsxs("div",{children:[e.jsxs("div",{className:"flex items-center justify-between border-b border-border px-3 py-1.5",children:[e.jsx("span",{className:"truncate font-mono text-2xs text-content-secondary",children:i.path}),e.jsx("button",{onClick:()=>p(null),className:"codex-btn-ghost text-2xs",children:"返回"})]}),e.jsxs("pre",{className:"whitespace-pre-wrap px-3 py-2 font-mono text-2xs leading-relaxed text-content-secondary",children:[i.content,i.truncated&&e.jsx("span",{className:"text-warning",children:`
|
|
2
2
|
…(已截断)`})]})]}):!o&&y.map(t=>e.jsxs("button",{onClick:()=>t.isDirectory?a(t.path):g(t),className:"flex w-full cursor-pointer items-center gap-2 px-3 py-1 text-left transition-colors hover:bg-surface-overlay",children:[t.isDirectory?e.jsx(b,{size:13,className:"shrink-0 text-content-faint"}):e.jsx(z,{size:13,className:"shrink-0 text-content-faint"}),e.jsx("span",{className:"truncate font-mono text-2xs text-content-primary",children:t.name}),!t.isDirectory&&t.size!=null&&e.jsxs("span",{className:"ml-auto font-mono text-2xs text-content-faint",children:[t.size,"B"]})]},t.path))]})]})}export{I as FileBrowser};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{r,j as e}from"./vendor-react-0zU5oKmd.js";import{e as me,g as he,a as be,c as ge,d as ve,f as je,h as ye,i as Ne,j as ke}from"./index-
|
|
1
|
+
import{r,j as e}from"./vendor-react-0zU5oKmd.js";import{e as me,g as he,a as be,c as ge,d as ve,f as je,h as ye,i as Ne,j as ke}from"./index-pqslvKcS.js";import"./vendor-highlight-Cus7zBI4.js";const Z=[{cssProp:"color",label:"文本颜色"},{cssProp:"background-color",label:"背景"},{cssProp:"font-size",label:"字号"},{cssProp:"font-weight",label:"字重"},{cssProp:"padding",label:"内边距"},{cssProp:"border-radius",label:"圆角"}];function we(d){var i;const c=(i=d.ownerDocument.defaultView)==null?void 0:i.getComputedStyle(d),o={};for(const{cssProp:f}of Z)o[f]=(c==null?void 0:c.getPropertyValue(f).trim())??"";return o}function $e(d){const c=/^\s*(\d{1,5})(.*)$/.exec(d);if(!c)return null;const o=c[1];if(Number(o)<1||Number(o)>65535)return null;let i=c[2]||"/";return i.startsWith("/")||(i=`/${i}`),{port:o,path:i}}function Ce(d){if(d.id)return`#${CSS.escape(d.id)}`;const c=[];let o=d,i=0;for(;o&&o.nodeType===1&&i<4;){let f=o.tagName.toLowerCase();const P=(o.getAttribute("class")||"").trim().split(/\s+/).filter(Boolean).slice(0,2).map(m=>`.${CSS.escape(m)}`).join("");f+=P;const h=o.parentElement;if(h){const m=[...h.children].filter(y=>y.tagName===o.tagName);m.length>1&&(f+=`:nth-of-type(${m.indexOf(o)+1})`)}if(c.unshift(f),o.id){c[0]=`#${CSS.escape(o.id)}`;break}o=h,i+=1}return c.join(" > ")}function Me({gatewayUrl:d,sessionId:c,initialAuthToken:o,defaultPort:i,isMobile:f,onClose:P,onAnnotate:h}){const[m,y]=r.useState(i||"3000"),[u,ee]=r.useState(null),[R,te]=r.useState(o??null),[E,T]=r.useState(!1),[M,H]=r.useState("desktop"),[ne,U]=r.useState(0),[I,N]=r.useState(!1),[K,z]=r.useState(null),[v,j]=r.useState(null),[l,L]=r.useState(null),[W,k]=r.useState(""),[w,B]=r.useState(!1),[$,D]=r.useState({}),[C,S]=r.useState({}),A=r.useRef(null),V=r.useRef(null),O=r.useMemo(()=>me(),[]);r.useEffect(()=>{let t=!1;return T(!1),he().then(n=>{t||te((n==null?void 0:n.accessToken)??null)}).finally(()=>{t||T(!0)}),()=>{t=!0}},[o]);const b=r.useMemo(()=>{if(!u||o&&!E)return null;const t=`${d.replace(/\/+$/,"")}/tunnel/${encodeURIComponent(c)}/${u.port}${u.path}`,n=[];if(O&&n.push(`token=${encodeURIComponent(O)}`),R&&n.push(`auth_token=${encodeURIComponent(R)}`),n.length===0)return t;const s=u.path.includes("?")?"&":"?";return`${t}${s}${n.join("&")}`},[u,d,c,O,R,E]),_=()=>{const t=$e(m);t&&(ee(t),U(n=>n+1))},se=()=>U(t=>t+1),oe=()=>{var t;if(u)try{const n=(t=A.current)==null?void 0:t.contentWindow;if(!n)return;const s=`/tunnel/${encodeURIComponent(c)}/${u.port}`;let x=n.location.pathname;x.startsWith(s)&&(x=x.slice(s.length)||"/");const p=new URLSearchParams(n.location.search);p.delete("token"),p.delete("auth_token");const g=p.toString();y(`${u.port}${x}${g?`?${g}`:""}`)}catch{}},F=(t,n)=>{const s=A.current,x=s==null?void 0:s.contentDocument;if(!s||!x)return null;const p=s.getBoundingClientRect();return x.elementFromPoint(t-p.left,n-p.top)},X=t=>{const n=t.getBoundingClientRect();return{top:n.top,left:n.left,width:n.width,height:n.height}},re=t=>{try{const n=F(t.clientX,t.clientY);j(n?X(n):null)}catch{j(null),z("此页面跨域,无法读取元素(生产环境同源可用)"),N(!1)}},le=t=>{t.preventDefault();try{const n=F(t.clientX,t.clientY);if(!n||!("style"in n))return;const s=n;L({el:s,selector:Ce(s),text:(s.textContent||"").replace(/\s+/g," ").trim().slice(0,120),rect:X(s)}),D(we(s)),S({}),k("")}catch{z("此页面跨域,无法读取元素(生产环境同源可用)"),N(!1)}},ce=(t,n)=>{if(l){try{l.el.style.setProperty(t,n)}catch{}S(s=>({...s,[t]:n}))}},ae=()=>Object.entries(C).filter(([t,n])=>n!==($[t]??"")).map(([t,n])=>({cssProp:t,from:$[t]??"",to:n})),Y=()=>{if(!l)return;const t=ae(),n=["关于预览页面中的这个元素:",`- 选择器: \`${l.selector}\``,l.text?`- 文本: "${l.text}"`:"",`- 路径: localhost:${m}`,W.trim()?`- 批注: ${W.trim()}`:""];if(t.length>0){n.push("- 请把以下样式改动应用到源码:");for(const s of t)n.push(` - ${s.cssProp}: ${s.from||"(默认)"} → ${s.to}`)}h==null||h(n.filter(Boolean).join(`
|
|
2
2
|
`)),L(null),k(""),D({}),S({}),B(!1),j(null),N(!1)},q=()=>{L(null),k(""),D({}),S({}),B(!1)},ie=()=>{z(null),q(),j(null),N(t=>!t)};return e.jsxs("div",{className:"flex h-full flex-col bg-canvas",children:[e.jsxs("div",{className:"flex items-center gap-1.5 border-b border-border bg-surface px-2 py-1.5",children:[f&&e.jsx("button",{onClick:P,className:"codex-btn-ghost px-2 py-1.5","aria-label":"关闭预览",children:e.jsx(be,{size:15})}),e.jsxs("div",{className:"flex min-w-0 flex-1 items-center gap-1 rounded-lg border border-border bg-surface-raised px-2 py-1",children:[e.jsx("span",{className:"shrink-0 font-mono text-2xs text-content-faint",children:"localhost:"}),e.jsx("input",{value:m,onChange:t=>y(t.target.value),onKeyDown:t=>t.key==="Enter"&&_(),placeholder:"3000",spellCheck:!1,className:"min-w-0 flex-1 bg-transparent font-mono text-2xs text-content-primary outline-none","aria-label":"端口与路径"}),e.jsx("button",{onClick:_,className:"shrink-0 cursor-pointer rounded bg-accent-dim px-2 py-0.5 text-2xs font-medium text-white transition-colors hover:bg-accent",children:"前往"})]}),e.jsx("button",{onClick:ie,disabled:!u,className:`shrink-0 cursor-pointer rounded-md p-1.5 transition-colors disabled:opacity-40 ${I?"bg-accent-dim text-white":"text-content-muted hover:bg-surface-overlay hover:text-accent"}`,title:"标注元素","aria-label":"标注元素",children:e.jsx(ge,{size:14})}),e.jsxs("div",{className:"flex shrink-0 items-center rounded-lg border border-border bg-surface-raised p-0.5",children:[e.jsx("button",{onClick:()=>H("desktop"),className:`cursor-pointer rounded p-1 transition-colors ${M==="desktop"?"bg-surface-overlay text-content-primary":"text-content-muted hover:text-content-primary"}`,title:"桌面视口","aria-label":"桌面视口",children:e.jsx(ve,{size:13})}),e.jsx("button",{onClick:()=>H("mobile"),className:`cursor-pointer rounded p-1 transition-colors ${M==="mobile"?"bg-surface-overlay text-content-primary":"text-content-muted hover:text-content-primary"}`,title:"移动视口","aria-label":"移动视口",children:e.jsx(je,{size:13})})]}),e.jsx("button",{onClick:se,disabled:!u,className:"shrink-0 cursor-pointer rounded-md p-1.5 text-content-muted transition-colors hover:bg-surface-overlay hover:text-accent disabled:opacity-40",title:"刷新","aria-label":"刷新",children:e.jsx(ye,{size:14})}),e.jsx("button",{onClick:()=>b&&window.open(b,"_blank","noopener,noreferrer"),disabled:!b,className:"shrink-0 cursor-pointer rounded-md p-1.5 text-content-muted transition-colors hover:bg-surface-overlay hover:text-accent disabled:opacity-40",title:"在新窗口打开","aria-label":"在新窗口打开",children:e.jsx(Ne,{size:14})})]}),K&&e.jsx("p",{className:"border-b border-border bg-warning/10 px-3 py-1.5 text-2xs text-warning",children:K}),I&&!l&&e.jsx("p",{className:"border-b border-border bg-accent/10 px-3 py-1.5 text-2xs text-accent",children:"标注模式:移动鼠标高亮元素,点击选择并添加批注。"}),e.jsx("div",{className:"min-h-0 flex-1 overflow-auto bg-surface-raised",children:b?e.jsx("div",{className:M==="mobile"?"mx-auto h-full w-[390px] max-w-full border-x border-border":"h-full w-full",children:e.jsxs("div",{className:"relative h-full w-full",children:[e.jsx("iframe",{ref:A,src:b,title:"端口预览",onLoad:oe,className:"h-full w-full bg-white"},`${b}-${ne}`),I&&e.jsx("div",{className:"absolute inset-0 cursor-crosshair",onMouseMove:re,onMouseLeave:()=>j(null),onClick:le,children:v&&!l&&e.jsx("div",{className:"pointer-events-none absolute border-2 border-accent bg-accent/10",style:{top:v.top,left:v.left,width:v.width,height:v.height}})}),l&&(()=>{var J,Q;const s=((J=V.current)==null?void 0:J.clientHeight)??600,x=l.rect.top+l.rect.height+8,p=l.rect.top-8,g=w?340:140,G=x+g>s&&p-g>0?Math.max(4,p-g):x,de=Math.max(4,Math.min(l.rect.left,(((Q=V.current)==null?void 0:Q.clientWidth)??800)-280-4));return e.jsxs("div",{className:"absolute z-20 flex flex-col gap-2 rounded-xl border border-accent-dim/40 bg-surface-raised px-3 py-2.5 shadow-2xl animate-fade-in",style:{top:G,left:de,width:280,maxHeight:s-G-8},children:[e.jsx("p",{className:"truncate font-mono text-2xs text-content-faint",title:l.selector,children:l.selector}),l.text&&e.jsxs("p",{className:"-mt-1 truncate text-2xs text-content-muted",children:['"',l.text,'"']}),e.jsx("input",{value:W,onChange:a=>k(a.target.value),onKeyDown:a=>a.key==="Enter"&&Y(),autoFocus:!0,placeholder:"添加批注…",className:"w-full rounded-lg border border-border bg-surface px-2.5 py-1.5 text-xs text-content-primary placeholder-content-muted outline-none focus:border-accent-dim"}),e.jsxs("button",{onClick:()=>B(a=>!a),className:`flex cursor-pointer items-center gap-1 self-start rounded-md px-1.5 py-0.5 text-2xs transition-colors ${w?"text-accent":"text-content-muted hover:text-content-secondary"}`,children:[e.jsx("svg",{width:10,height:10,viewBox:"0 0 10 10",className:`shrink-0 transition-transform ${w?"rotate-180":""}`,children:e.jsx("path",{d:"M2 3.5 L5 6.5 L8 3.5",fill:"none",stroke:"currentColor",strokeWidth:"1.4",strokeLinecap:"round",strokeLinejoin:"round"})}),"调整样式"]}),w&&e.jsx("div",{className:"grid grid-cols-2 gap-x-2 gap-y-1.5 animate-fade-in",children:Z.map(({cssProp:a,label:ue})=>{const xe=C[a]??$[a]??"",pe=a in C&&C[a]!==($[a]??"");return e.jsxs("label",{className:"flex flex-col gap-0.5",children:[e.jsx("span",{className:`text-2xs ${pe?"text-accent":"text-content-faint"}`,children:ue}),e.jsx("input",{value:xe,onChange:fe=>ce(a,fe.target.value),spellCheck:!1,className:"w-full rounded-md border border-border bg-surface px-2 py-1 font-mono text-2xs text-content-primary outline-none focus:border-accent-dim"})]},a)})}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("button",{onClick:q,className:"codex-btn-ghost flex-1 text-2xs",children:"取消"}),e.jsx("button",{onClick:Y,className:"codex-btn-primary flex-1 text-2xs",children:"发送给 Agent"})]})]})})()]})}):e.jsxs("div",{className:"flex h-full flex-col items-center justify-center gap-3 px-6 text-center",children:[e.jsx(ke,{size:34,className:"text-content-faint"}),e.jsx("p",{className:"text-sm leading-6 text-content-muted",children:u&&!E?"正在准备预览认证...":"输入主机上服务的端口号,预览远程本地服务"})]})})]})}export{Me as PortPreview};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{r as l,j as i}from"./vendor-react-0zU5oKmd.js";import{x as w,a as C}from"./vendor-xterm-D15THcI4.js";import{I as T,a as A,b as B}from"./index-
|
|
1
|
+
import{r as l,j as i}from"./vendor-react-0zU5oKmd.js";import{x as w,a as C}from"./vendor-xterm-D15THcI4.js";import{I as T,a as A,b as B}from"./index-pqslvKcS.js";import"./vendor-highlight-Cus7zBI4.js";function x(e,t){const r=getComputedStyle(document.documentElement).getPropertyValue(e).trim();return r?`rgb(${r})`:t}function $(e,t,r){const u=getComputedStyle(document.documentElement).getPropertyValue(e).trim();return u?`rgb(${u} / ${t})`:r}function E(){return{background:x("--c-canvas","#0b0d0f"),foreground:x("--c-content-primary","#e6e8eb"),cursor:x("--c-accent","#2dd4bf"),selectionBackground:$("--c-accent",.22,"rgba(45,212,191,0.25)"),black:x("--c-content-faint","#4e535a"),brightBlack:x("--c-content-muted","#70767e"),white:x("--c-content-secondary","#9ea5ad"),brightWhite:x("--c-content-primary","#e9ebee")}}function F(e,t){const r=l.useRef(null),u=l.useRef(null),h=l.useRef(null);return l.useEffect(()=>{if(!r.current||!e)return;const n=new w.Terminal({fontFamily:"JetBrains Mono, ui-monospace, monospace",fontSize:13,cursorBlink:!0,theme:E(),scrollback:5e3}),f=new C.FitAddon;n.loadAddon(f),n.open(r.current),f.fit(),u.current=n,h.current=f;const p=e.terminalBuffer(t);p&&n.write(p);const v=n.onData(c=>e.sendInput(t,c)),b=n.onResize(({cols:c,rows:o})=>e.sendResize(t,c,o)),k=e.onEvent(c=>{if(c.type==="terminal.reset"&&c.terminalId===t){n.reset();return}c.type==="terminal.output"&&c.terminalId===t&&n.write(c.data)}),y=new ResizeObserver(()=>{try{f.fit(),e.sendResize(t,n.cols,n.rows)}catch{}});y.observe(r.current);const s=new MutationObserver(()=>{n.options.theme=E()});return s.observe(document.documentElement,{attributes:!0,attributeFilter:["data-theme"]}),e.sendResize(t,n.cols,n.rows),()=>{v.dispose(),b.dispose(),k(),y.disconnect(),s.disconnect(),n.dispose(),u.current=null,h.current=null}},[e,t]),{containerRef:r}}function I(e,t){return e==="default"?"默认":t||e.slice(0,8)}const z=e=>`linkshell:web:lastTerminal:${e}`;function L(e){if(!e)return null;try{return localStorage.getItem(z(e))}catch{return null}}function P(e,t){if(e)try{localStorage.setItem(z(e),t)}catch{}}function O(e,t){if(e.length===0)return null;if(t&&e.includes(t))return t;const r=e.filter(u=>u!=="default");return r.length>0?r[r.length-1]:e[0]}function V({bridge:e,terminalId:t}){const{containerRef:r}=F(e,t);return i.jsx("div",{ref:r,className:"xterm-host min-h-0 flex-1 bg-canvas p-2"})}function N({bridge:e,onNewTerminal:t}){const r=e==null?void 0:e.sessionId,[u,h]=l.useState([]),[n,f]=l.useState("default"),p=l.useRef(!1),v=l.useRef(!1),b=l.useCallback((s,c)=>{h(o=>o.some(m=>m.id===s)?o:[...o,{id:s,label:I(s,c)}])},[]);l.useEffect(()=>{if(!e)return;p.current=!1,v.current=!1;const s=L(r);return e.requestTerminalList(),e.onEvent(o=>{if(o.type==="terminal.output"){b(o.terminalId);return}if(o.type==="terminal.list"){const m=o.envelope,a=m.payload;if(m.type==="terminal.spawned"){const R=a==null?void 0:a.terminalId;R&&(b(R,a==null?void 0:a.projectName),f(R));return}if(m.type==="terminal.list"){const j=(Array.isArray(a==null?void 0:a.terminals)?a.terminals:[]).filter(d=>typeof d.terminalId=="string"&&d.status!=="exited").map(d=>({id:d.terminalId,label:I(d.terminalId,d.projectName)}));if(h(j),!p.current){p.current=!0;const d=j.map(S=>S.id),g=O(d,s);g?f(g):v.current||(v.current=!0,t==null||t())}}}})},[e,b,t]),l.useEffect(()=>{p.current&&n&&P(r,n)},[r,n]);const k=()=>t==null?void 0:t(),y=s=>{e==null||e.killTerminal(s),h(c=>{var m;const o=c.filter(a=>a.id!==s);return s===n&&f(((m=o[o.length-1])==null?void 0:m.id)??"default"),o})};return i.jsxs("div",{className:"flex h-full flex-col bg-canvas",children:[i.jsxs("div",{className:"flex items-center gap-0.5 border-b border-border bg-surface-raised px-1.5 py-1",children:[i.jsx(T,{size:13,className:"mx-1 shrink-0 text-content-faint"}),i.jsx("div",{className:"flex min-w-0 flex-1 items-center gap-0.5 overflow-x-auto",children:u.map(s=>i.jsxs("div",{className:`group flex shrink-0 items-center gap-1 rounded-md px-2 py-1 text-2xs transition-colors ${s.id===n?"bg-surface-overlay text-content-primary":"text-content-muted hover:bg-surface-overlay/60"}`,children:[i.jsx("button",{onClick:()=>f(s.id),className:"cursor-pointer font-mono",children:s.label}),u.length>1&&i.jsx("button",{onClick:()=>y(s.id),className:"cursor-pointer rounded text-content-faint opacity-0 transition hover:text-danger group-hover:opacity-100","aria-label":"关闭终端",children:i.jsx(A,{size:11})})]},s.id))}),i.jsx("button",{onClick:k,className:"ml-1 shrink-0 cursor-pointer rounded-md p-1 text-content-muted transition-colors hover:bg-surface-overlay hover:text-accent",title:"新建终端","aria-label":"新建终端",children:i.jsx(B,{size:14})})]}),i.jsx(V,{bridge:e,terminalId:n},n)]})}export{N as TerminalPanel};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{j as e,r as T}from"./vendor-react-0zU5oKmd.js";import{a as $}from"./index-
|
|
1
|
+
import{j as e,r as T}from"./vendor-react-0zU5oKmd.js";import{a as $}from"./index-pqslvKcS.js";import"./vendor-highlight-Cus7zBI4.js";function h(t){return t>=1e8?(t/1e8).toFixed(1)+"亿":t>=1e4?(t/1e4).toFixed(1)+"万":String(Math.round(t))}function S(t){const a=Math.round(t/6e4),n=Math.floor(a/60),l=a%60;return n>0?`${n}小时${l}分`:`${l}分`}const C=53,j=864e5;function k(t){return`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}-${String(t.getDate()).padStart(2,"0")}`}function E(t,a){const n=new Map;for(const o of t)n.set(o.key,o.totalTokens);const l=new Date;l.setHours(12,0,0,0);const s=new Date(l.getTime()-(C*7-1)*j);s.setDate(s.getDate()-s.getDay());const r=new Map;if(a==="weekly")for(const[o,d]of n){const i=new Date(o+"T12:00:00");i.setDate(i.getDate()-i.getDay());const x=k(i);r.set(x,(r.get(x)??0)+d)}const c=[],u=[];let N=0,w=0,D=-1;for(let o=new Date(s);o<=l;w++){const d=[];for(let x=0;x<7;x++){const p=new Date(o.getTime()+x*j),M=k(p),y=n.get(M)??0;a==="cumulative"&&(N+=y);let g=y;if(a==="weekly"){const f=new Date(p);f.setDate(f.getDate()-f.getDay()),g=r.get(k(f))??0}else a==="cumulative"&&(g=N);d.push({date:p,key:M,value:g,daily:y})}const i=d[0].date.getMonth();i!==D&&(u.push({col:w,label:`${i+1}月`}),D=i),c.push(d),o=new Date(o.getTime()+7*j)}let b=0;for(const o of c)for(const d of o)b=Math.max(b,d.value);return{weeks:c,max:b,monthLabels:u}}function F(t,a){if(t<=0||a<=0)return{className:"bg-surface-raised"};const n=t/a;return{className:"",style:{backgroundColor:`rgba(96,165,250,${n>.66?1:n>.33?.72:n>.1?.48:.28})`}}}function m({value:t,label:a}){return e.jsxs("div",{className:"flex flex-col items-center justify-center gap-1 px-4 py-3",children:[e.jsx("span",{className:"whitespace-nowrap font-mono text-xl font-semibold text-content-primary tabular-nums",children:t}),e.jsx("span",{className:"text-2xs text-content-muted",children:a})]})}function v({title:t,buckets:a}){const n=a.slice(0,6),l=n.reduce((s,r)=>Math.max(s,r.totalTokens),0);return n.length===0?null:e.jsxs("div",{className:"rounded-xl border border-border bg-surface p-4",children:[e.jsx("p",{className:"mb-3 text-2xs font-semibold uppercase tracking-wider text-content-faint",children:t}),e.jsx("div",{className:"space-y-2",children:n.map(s=>e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("span",{className:"w-28 shrink-0 truncate text-xs text-content-secondary",title:s.key,children:s.key}),e.jsx("div",{className:"relative h-2 flex-1 overflow-hidden rounded-full bg-surface-raised",children:e.jsx("div",{className:"absolute inset-y-0 left-0 rounded-full",style:{width:`${l>0?s.totalTokens/l*100:0}%`,backgroundColor:"rgba(96,165,250,0.7)"}})}),e.jsx("span",{className:"w-14 shrink-0 text-right font-mono text-2xs tabular-nums text-content-muted",children:h(s.totalTokens)})]},s.key))})]})}function z({report:t}){const[a,n]=T.useState("daily"),l=T.useMemo(()=>t?E(t.byDay,a):null,[t,a]);return t?e.jsxs("div",{className:"space-y-6",children:[e.jsxs("div",{className:"grid grid-cols-2 divide-x divide-border rounded-2xl border border-border bg-surface sm:grid-cols-5",children:[e.jsx(m,{value:h(t.totals.totalTokens),label:"累计 Token 数"}),e.jsx(m,{value:h(t.peakDayTokens),label:"峰值 Token 数"}),e.jsx(m,{value:S(t.longestTaskMs),label:"最长任务时长"}),e.jsx(m,{value:`${t.currentStreakDays} 天`,label:"当前连续天数"}),e.jsx(m,{value:`${t.longestStreakDays} 天`,label:"最长连续天数"})]}),e.jsxs("div",{className:"rounded-2xl border border-border bg-surface p-5",children:[e.jsxs("div",{className:"mb-4 flex items-center justify-between",children:[e.jsx("h3",{className:"text-sm font-semibold text-content-primary",children:"Token 活动"}),e.jsx("div",{className:"flex items-center gap-1 text-2xs",children:[["daily","每日"],["weekly","每周"],["cumulative","累计"]].map(([s,r])=>e.jsx("button",{onClick:()=>n(s),className:`rounded-md px-2 py-1 transition-colors ${a===s?"bg-surface-overlay text-content-primary":"text-content-muted hover:text-content-secondary"}`,children:r},s))})]}),l&&e.jsx("div",{className:"overflow-x-auto",children:e.jsxs("div",{className:"inline-flex flex-col gap-1",children:[e.jsx("div",{className:"flex gap-1",children:l.weeks.map((s,r)=>e.jsx("div",{className:"flex flex-col gap-1",children:s.map(c=>{const u=F(c.value,l.max);return e.jsx("div",{className:`h-3 w-3 rounded-[3px] ${u.className}`,style:u.style,title:`${c.key} · ${h(c.daily)} tokens`},c.key)})},r))}),e.jsx("div",{className:"relative mt-1.5 h-4 text-2xs text-content-faint",children:l.monthLabels.map(s=>e.jsx("span",{className:"absolute",style:{left:`${s.col*16}px`},children:s.label},s.col))})]})})]}),e.jsxs("div",{className:"grid grid-cols-1 gap-4 sm:grid-cols-3",children:[e.jsx(v,{title:"工具分布",buckets:t.byTool}),e.jsx(v,{title:"模型分布",buckets:t.byModel}),e.jsx(v,{title:"项目分布",buckets:t.byProject})]})]}):e.jsxs("div",{className:"flex flex-col items-center justify-center gap-3 py-12 text-content-muted",children:[e.jsx("span",{className:"h-6 w-6 animate-spin rounded-full border-2 border-content-faint border-t-accent"}),e.jsx("p",{className:"text-sm",children:"正在扫描本机会话记录…"})]})}function L({report:t,onClose:a}){return e.jsxs("div",{className:"fixed inset-0 z-50 flex flex-col bg-canvas/95 backdrop-blur-sm",children:[e.jsxs("div",{className:"flex items-center justify-between border-b border-border px-5 py-3",children:[e.jsx("h2",{className:"text-base font-semibold text-content-primary",children:"用量统计"}),e.jsx("button",{onClick:a,className:"codex-btn-ghost px-2 py-1.5","aria-label":"关闭",children:e.jsx($,{size:16})})]}),e.jsx("div",{className:"mx-auto w-full max-w-5xl flex-1 overflow-y-auto px-5 py-6",children:e.jsx(z,{report:t})})]})}export{L as UsageDashboard,z as UsageDashboardContent};
|