@getpaseo/server 0.1.97-beta.3 → 0.1.98
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/server/server/agent/agent-manager.d.ts +11 -3
- package/dist/server/server/agent/agent-manager.js +95 -23
- package/dist/server/server/agent/agent-prompt.d.ts +1 -1
- package/dist/server/server/agent/agent-prompt.js +3 -10
- package/dist/server/server/agent/agent-response-loop.js +9 -3
- package/dist/server/server/agent/agent-sdk-types.d.ts +9 -3
- package/dist/server/server/agent/agent-storage.d.ts +20 -240
- package/dist/server/server/agent/agent-storage.js +6 -6
- package/dist/server/server/agent/create-agent/create.d.ts +2 -0
- package/dist/server/server/agent/create-agent/create.js +8 -7
- package/dist/server/server/agent/lifecycle-command.d.ts +15 -1
- package/dist/server/server/agent/lifecycle-command.js +9 -2
- package/dist/server/server/agent/mcp-server.js +263 -119
- package/dist/server/server/agent/mcp-shared.d.ts +35 -179
- package/dist/server/server/agent/provider-notices.d.ts +3 -0
- package/dist/server/server/agent/provider-notices.js +5 -0
- package/dist/server/server/agent/provider-registry.d.ts +2 -0
- package/dist/server/server/agent/provider-registry.js +10 -3
- package/dist/server/server/agent/provider-snapshot-manager.d.ts +3 -0
- package/dist/server/server/agent/provider-snapshot-manager.js +11 -2
- package/dist/server/server/agent/providers/claude/agent.js +257 -143
- package/dist/server/server/agent/providers/claude/models.js +7 -3
- package/dist/server/server/agent/providers/claude/project-dir.js +9 -6
- package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts +2 -22
- package/dist/server/server/agent/providers/codex/app-server-transport.d.ts +8 -118
- package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +4 -3
- package/dist/server/server/agent/providers/codex-app-server-agent.js +43 -1
- package/dist/server/server/agent/providers/copilot-acp-agent.js +4 -1
- package/dist/server/server/agent/providers/diagnostic-utils.d.ts +9 -0
- package/dist/server/server/agent/providers/diagnostic-utils.js +188 -0
- package/dist/server/server/agent/providers/generic-acp-agent.d.ts +1 -5
- package/dist/server/server/agent/providers/mock-slow-provider.js +1 -1
- package/dist/server/server/agent/providers/opencode/server-manager.d.ts +29 -2
- package/dist/server/server/agent/providers/opencode/server-manager.js +83 -17
- package/dist/server/server/agent/providers/opencode-agent.d.ts +2 -0
- package/dist/server/server/agent/providers/opencode-agent.js +14 -9
- package/dist/server/server/agent/providers/pi/agent.d.ts +1 -5
- package/dist/server/server/agent/providers/pi/agent.js +27 -14
- package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts +391 -1261
- package/dist/server/server/agent/providers/tool-call-detail-primitives.js +26 -16
- package/dist/server/server/bootstrap.d.ts +2 -0
- package/dist/server/server/bootstrap.js +32 -2
- package/dist/server/server/loop-service.d.ts +60 -359
- package/dist/server/server/managed-processes/managed-processes.d.ts +76 -0
- package/dist/server/server/managed-processes/managed-processes.js +326 -0
- package/dist/server/server/migrations/backfill-workspace-id.migration.js +10 -6
- package/dist/server/server/package-version.d.ts +1 -7
- package/dist/server/server/paseo-worktree-service.js +15 -1
- package/dist/server/server/persisted-config.d.ts +138 -1009
- package/dist/server/server/persisted-config.js +1 -1
- package/dist/server/server/pid-lock.d.ts +1 -15
- package/dist/server/server/resolve-worktree-creation-intent.d.ts +3 -0
- package/dist/server/server/resolve-worktree-creation-intent.js +3 -3
- package/dist/server/server/session.d.ts +18 -1
- package/dist/server/server/session.js +424 -64
- package/dist/server/server/speech/providers/local/sherpa/model-catalog.d.ts +2 -2
- package/dist/server/server/speech/providers/openai/runtime.js +3 -4
- package/dist/server/server/speech/speech-types.d.ts +9 -11
- package/dist/server/server/websocket-server.d.ts +1 -0
- package/dist/server/server/websocket-server.js +15 -0
- package/dist/server/server/workspace-archive-service.js +2 -3
- package/dist/server/server/workspace-directory.js +5 -5
- package/dist/server/server/workspace-reconciliation-service.js +2 -2
- package/dist/server/server/workspace-registry.d.ts +17 -48
- package/dist/server/server/workspace-registry.js +9 -0
- package/dist/server/server/worktree-core.d.ts +1 -0
- package/dist/server/server/worktree-core.js +5 -1
- package/dist/server/services/quota-fetcher/manifest.d.ts +4 -0
- package/dist/server/services/quota-fetcher/manifest.js +47 -0
- package/dist/server/services/quota-fetcher/provider.d.ts +17 -0
- package/dist/server/services/quota-fetcher/provider.js +2 -0
- package/dist/server/services/quota-fetcher/providers/claude.d.ts +26 -0
- package/dist/server/services/quota-fetcher/providers/claude.js +217 -0
- package/dist/server/services/quota-fetcher/providers/codex.d.ts +23 -0
- package/dist/server/services/quota-fetcher/providers/codex.js +211 -0
- package/dist/server/services/quota-fetcher/providers/copilot.d.ts +17 -0
- package/dist/server/services/quota-fetcher/providers/copilot.js +75 -0
- package/dist/server/services/quota-fetcher/providers/cursor.d.ts +17 -0
- package/dist/server/services/quota-fetcher/providers/cursor.js +123 -0
- package/dist/server/services/quota-fetcher/providers/grok.d.ts +18 -0
- package/dist/server/services/quota-fetcher/providers/grok.js +89 -0
- package/dist/server/services/quota-fetcher/providers/kimi.d.ts +20 -0
- package/dist/server/services/quota-fetcher/providers/kimi.js +89 -0
- package/dist/server/services/quota-fetcher/providers/zai.d.ts +17 -0
- package/dist/server/services/quota-fetcher/providers/zai.js +58 -0
- package/dist/server/services/quota-fetcher/service.d.ts +28 -0
- package/dist/server/services/quota-fetcher/service.js +58 -0
- package/dist/server/services/quota-fetcher/usage.d.ts +22 -0
- package/dist/server/services/quota-fetcher/usage.js +49 -0
- package/dist/server/terminal/terminal-session-controller.d.ts +8 -0
- package/dist/server/terminal/terminal-session-controller.js +23 -3
- package/dist/server/utils/checkout-git.js +36 -76
- package/dist/server/utils/directory-suggestions.js +98 -2
- package/dist/server/utils/worktree-metadata.d.ts +7 -59
- package/dist/src/server/persisted-config.js +1 -1
- package/package.json +9 -9
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Logger } from "pino";
|
|
2
|
+
import type { ProviderUsage } from "../../../server/messages.js";
|
|
3
|
+
import type { ProviderApiFetch, ProviderUsageFetcher } from "../provider.js";
|
|
4
|
+
interface ZaiQuotaProviderOptions {
|
|
5
|
+
logger: Logger;
|
|
6
|
+
fetch?: ProviderApiFetch;
|
|
7
|
+
}
|
|
8
|
+
export declare class ZaiQuotaProvider implements ProviderUsageFetcher {
|
|
9
|
+
readonly providerId = "zai";
|
|
10
|
+
readonly displayName = "Z.ai";
|
|
11
|
+
private readonly logger;
|
|
12
|
+
private readonly fetchApi;
|
|
13
|
+
constructor(options: ZaiQuotaProviderOptions);
|
|
14
|
+
fetchUsage(): Promise<ProviderUsage>;
|
|
15
|
+
}
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=zai.d.ts.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ApiOptionalStringSchema, fetchProviderApi, unavailableUsage } from "../usage.js";
|
|
3
|
+
const ZaiUsageResponseSchema = z.object({
|
|
4
|
+
data: z
|
|
5
|
+
.array(z.object({
|
|
6
|
+
productName: ApiOptionalStringSchema,
|
|
7
|
+
status: ApiOptionalStringSchema,
|
|
8
|
+
purchaseTime: ApiOptionalStringSchema,
|
|
9
|
+
valid: ApiOptionalStringSchema,
|
|
10
|
+
}))
|
|
11
|
+
.optional(),
|
|
12
|
+
});
|
|
13
|
+
export class ZaiQuotaProvider {
|
|
14
|
+
constructor(options) {
|
|
15
|
+
this.providerId = "zai";
|
|
16
|
+
this.displayName = "Z.ai";
|
|
17
|
+
this.logger = options.logger;
|
|
18
|
+
this.fetchApi = options.fetch ?? fetch;
|
|
19
|
+
}
|
|
20
|
+
async fetchUsage() {
|
|
21
|
+
const token = process.env["ZAI_API_KEY"] || process.env["GLM_API_KEY"];
|
|
22
|
+
if (!token)
|
|
23
|
+
return unavailableUsage(this);
|
|
24
|
+
const res = await fetchProviderApi(this.fetchApi, "https://api.z.ai/api/biz/subscription/list", {
|
|
25
|
+
headers: {
|
|
26
|
+
Authorization: `Bearer ${token}`,
|
|
27
|
+
Accept: "application/json",
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
if (!res.ok) {
|
|
31
|
+
this.logger.debug({ status: res.status }, "Z.ai usage fetch failed");
|
|
32
|
+
return unavailableUsage(this);
|
|
33
|
+
}
|
|
34
|
+
const resp = ZaiUsageResponseSchema.parse(await res.json());
|
|
35
|
+
const sub = resp.data?.[0];
|
|
36
|
+
if (!sub)
|
|
37
|
+
return unavailableUsage(this);
|
|
38
|
+
const details = [];
|
|
39
|
+
if (sub.status)
|
|
40
|
+
details.push({ id: "status", label: "Status", value: sub.status });
|
|
41
|
+
if (sub.valid)
|
|
42
|
+
details.push({ id: "valid", label: "Valid", value: sub.valid });
|
|
43
|
+
if (sub.purchaseTime) {
|
|
44
|
+
details.push({ id: "purchase_time", label: "Purchased", value: sub.purchaseTime });
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
providerId: this.providerId,
|
|
48
|
+
displayName: this.displayName,
|
|
49
|
+
status: "available",
|
|
50
|
+
planLabel: sub.productName || null,
|
|
51
|
+
windows: [],
|
|
52
|
+
balances: [],
|
|
53
|
+
details,
|
|
54
|
+
error: null,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=zai.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Logger } from "pino";
|
|
2
|
+
import type { ProviderUsage } from "../../server/messages.js";
|
|
3
|
+
import type { ProviderApiFetch, ProviderUsageFetcher } from "./provider.js";
|
|
4
|
+
export interface ProviderUsageServiceOptions {
|
|
5
|
+
logger: Logger;
|
|
6
|
+
fetchers?: ProviderUsageFetcher[];
|
|
7
|
+
fetch?: ProviderApiFetch;
|
|
8
|
+
cacheTtlMs?: number;
|
|
9
|
+
now?: () => number;
|
|
10
|
+
}
|
|
11
|
+
export interface ProviderUsageListResult {
|
|
12
|
+
fetchedAt: string;
|
|
13
|
+
providers: ProviderUsage[];
|
|
14
|
+
}
|
|
15
|
+
export declare class ProviderUsageService {
|
|
16
|
+
private readonly logger;
|
|
17
|
+
private readonly fetchers;
|
|
18
|
+
private readonly cacheTtlMs;
|
|
19
|
+
private readonly now;
|
|
20
|
+
private cached;
|
|
21
|
+
private inFlight;
|
|
22
|
+
constructor(options: ProviderUsageServiceOptions);
|
|
23
|
+
listUsage(options?: {
|
|
24
|
+
forceRefresh?: boolean;
|
|
25
|
+
}): Promise<ProviderUsageListResult>;
|
|
26
|
+
private fetchFreshUsage;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=service.d.ts.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { createProviderUsageFetchers } from "./manifest.js";
|
|
2
|
+
import { unavailableUsage } from "./usage.js";
|
|
3
|
+
const DEFAULT_PROVIDER_USAGE_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
4
|
+
export class ProviderUsageService {
|
|
5
|
+
constructor(options) {
|
|
6
|
+
this.cached = null;
|
|
7
|
+
this.inFlight = null;
|
|
8
|
+
this.logger = options.logger.child({ module: "provider-usage-service" });
|
|
9
|
+
this.fetchers =
|
|
10
|
+
options.fetchers ??
|
|
11
|
+
createProviderUsageFetchers({
|
|
12
|
+
logger: this.logger,
|
|
13
|
+
fetch: options.fetch,
|
|
14
|
+
});
|
|
15
|
+
this.cacheTtlMs = options.cacheTtlMs ?? DEFAULT_PROVIDER_USAGE_CACHE_TTL_MS;
|
|
16
|
+
this.now = options.now ?? Date.now;
|
|
17
|
+
}
|
|
18
|
+
async listUsage(options) {
|
|
19
|
+
const nowMs = this.now();
|
|
20
|
+
if (!options?.forceRefresh &&
|
|
21
|
+
this.cached &&
|
|
22
|
+
nowMs - this.cached.fetchedAtMs < this.cacheTtlMs) {
|
|
23
|
+
return this.cached.result;
|
|
24
|
+
}
|
|
25
|
+
if (this.inFlight) {
|
|
26
|
+
return this.inFlight;
|
|
27
|
+
}
|
|
28
|
+
const request = this.fetchFreshUsage(nowMs);
|
|
29
|
+
this.inFlight = request;
|
|
30
|
+
try {
|
|
31
|
+
return await request;
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
if (this.inFlight === request) {
|
|
35
|
+
this.inFlight = null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async fetchFreshUsage(nowMs) {
|
|
40
|
+
const settled = await Promise.allSettled(this.fetchers.map((fetcher) => fetcher.fetchUsage()));
|
|
41
|
+
const providers = settled.map((result, index) => {
|
|
42
|
+
const fetcher = this.fetchers[index];
|
|
43
|
+
if (result.status === "fulfilled") {
|
|
44
|
+
return result.value;
|
|
45
|
+
}
|
|
46
|
+
this.logger.debug({ err: result.reason, providerId: fetcher.providerId }, "Provider usage fetch failed");
|
|
47
|
+
return unavailableUsage({
|
|
48
|
+
providerId: fetcher.providerId,
|
|
49
|
+
displayName: fetcher.displayName,
|
|
50
|
+
error: result.reason instanceof Error ? result.reason.message : String(result.reason),
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
const result = { fetchedAt: new Date(nowMs).toISOString(), providers };
|
|
54
|
+
this.cached = { fetchedAtMs: nowMs, result };
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=service.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { ProviderUsage, ProviderUsageBalance, ProviderUsageWindow } from "../../server/messages.js";
|
|
3
|
+
import type { ProviderApiFetch } from "./provider.js";
|
|
4
|
+
export declare const ApiNumberSchema: z.ZodCoercedNumber<unknown>;
|
|
5
|
+
export declare const ApiNullableNumberSchema: z.ZodPreprocess<z.ZodNullable<z.ZodCoercedNumber<unknown>>>;
|
|
6
|
+
export declare const ApiOptionalStringSchema: z.ZodPreprocess<z.ZodOptional<z.ZodCoercedString<unknown>>>;
|
|
7
|
+
export declare function fetchProviderApi(fetchApi: ProviderApiFetch, input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
|
|
8
|
+
export declare function unavailableUsage(provider: {
|
|
9
|
+
providerId: string;
|
|
10
|
+
displayName: string;
|
|
11
|
+
error?: string | null;
|
|
12
|
+
}): ProviderUsage;
|
|
13
|
+
export declare function windowFromUsedPct(input: {
|
|
14
|
+
id: string;
|
|
15
|
+
label: string;
|
|
16
|
+
utilizationPct: number | null | undefined;
|
|
17
|
+
resetsAt?: string | null;
|
|
18
|
+
tone?: ProviderUsageWindow["tone"];
|
|
19
|
+
}): ProviderUsageWindow;
|
|
20
|
+
export declare function balanceToneFromRemaining(remaining: number | null | undefined): ProviderUsageBalance["tone"];
|
|
21
|
+
export declare function toIsoStringOrNull(timestampMs: number): string | null;
|
|
22
|
+
//# sourceMappingURL=usage.d.ts.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const PROVIDER_HTTP_TIMEOUT_MS = 15000;
|
|
3
|
+
export const ApiNumberSchema = z.coerce.number().finite();
|
|
4
|
+
export const ApiNullableNumberSchema = z.preprocess((value) => (value == null ? null : value), ApiNumberSchema.nullable());
|
|
5
|
+
export const ApiOptionalStringSchema = z.preprocess((value) => (value == null ? undefined : value), z.coerce.string().optional());
|
|
6
|
+
export function fetchProviderApi(fetchApi, input, init = {}) {
|
|
7
|
+
return fetchApi(input, {
|
|
8
|
+
...init,
|
|
9
|
+
signal: init.signal ?? AbortSignal.timeout(PROVIDER_HTTP_TIMEOUT_MS),
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
export function unavailableUsage(provider) {
|
|
13
|
+
return {
|
|
14
|
+
providerId: provider.providerId,
|
|
15
|
+
displayName: provider.displayName,
|
|
16
|
+
status: provider.error ? "error" : "unavailable",
|
|
17
|
+
planLabel: null,
|
|
18
|
+
windows: [],
|
|
19
|
+
balances: [],
|
|
20
|
+
details: [],
|
|
21
|
+
error: provider.error ?? null,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export function windowFromUsedPct(input) {
|
|
25
|
+
const usedPct = typeof input.utilizationPct === "number" ? input.utilizationPct : null;
|
|
26
|
+
const window = {
|
|
27
|
+
id: input.id,
|
|
28
|
+
label: input.label,
|
|
29
|
+
usedPct,
|
|
30
|
+
remainingPct: usedPct === null ? null : Math.max(0, 100 - usedPct),
|
|
31
|
+
resetsAt: input.resetsAt ?? null,
|
|
32
|
+
};
|
|
33
|
+
if (input.tone) {
|
|
34
|
+
window.tone = input.tone;
|
|
35
|
+
}
|
|
36
|
+
return window;
|
|
37
|
+
}
|
|
38
|
+
export function balanceToneFromRemaining(remaining) {
|
|
39
|
+
if (typeof remaining !== "number")
|
|
40
|
+
return "default";
|
|
41
|
+
if (remaining <= 0)
|
|
42
|
+
return "danger";
|
|
43
|
+
return "ok";
|
|
44
|
+
}
|
|
45
|
+
export function toIsoStringOrNull(timestampMs) {
|
|
46
|
+
const date = new Date(timestampMs);
|
|
47
|
+
return Number.isFinite(date.getTime()) ? date.toISOString() : null;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=usage.js.map
|
|
@@ -9,10 +9,15 @@ export interface TerminalSessionControllerOptions {
|
|
|
9
9
|
hasBinaryChannel: () => boolean;
|
|
10
10
|
isPathWithinRoot: (rootPath: string, candidatePath: string) => boolean;
|
|
11
11
|
sessionLogger: pino.Logger;
|
|
12
|
+
listTerminalWorkspaceRefs?: () => Promise<readonly TerminalWorkspaceRef[]>;
|
|
12
13
|
listTerminalWorkspaceRoots?: () => Promise<readonly string[]>;
|
|
13
14
|
clientSupportsWrapReflow?: () => boolean;
|
|
14
15
|
getClientBufferedAmount?: () => number | null;
|
|
15
16
|
}
|
|
17
|
+
interface TerminalWorkspaceRef {
|
|
18
|
+
workspaceId: string;
|
|
19
|
+
cwd: string;
|
|
20
|
+
}
|
|
16
21
|
export interface TerminalSessionControllerMetrics {
|
|
17
22
|
directorySubscriptionCount: number;
|
|
18
23
|
streamSubscriptionCount: number;
|
|
@@ -24,6 +29,7 @@ export declare class TerminalSessionController {
|
|
|
24
29
|
private readonly hasBinaryChannel;
|
|
25
30
|
private readonly isPathWithinRoot;
|
|
26
31
|
private readonly sessionLogger;
|
|
32
|
+
private readonly listTerminalWorkspaceRefs;
|
|
27
33
|
private readonly listTerminalWorkspaceRoots;
|
|
28
34
|
private readonly clientSupportsWrapReflow;
|
|
29
35
|
private readonly getClientBufferedAmount;
|
|
@@ -59,6 +65,7 @@ export declare class TerminalSessionController {
|
|
|
59
65
|
private resolveTerminalOwnerRoot;
|
|
60
66
|
private isSamePath;
|
|
61
67
|
private handleCreateTerminalRequest;
|
|
68
|
+
private resolveLegacyTerminalWorkspaceId;
|
|
62
69
|
private handleRenameTerminalRequest;
|
|
63
70
|
private handleSubscribeTerminalRequest;
|
|
64
71
|
private handleUnsubscribeTerminalRequest;
|
|
@@ -74,4 +81,5 @@ export declare class TerminalSessionController {
|
|
|
74
81
|
private allocateSlot;
|
|
75
82
|
private detachStream;
|
|
76
83
|
}
|
|
84
|
+
export {};
|
|
77
85
|
//# sourceMappingURL=terminal-session-controller.d.ts.map
|
|
@@ -34,7 +34,10 @@ export class TerminalSessionController {
|
|
|
34
34
|
this.hasBinaryChannel = options.hasBinaryChannel;
|
|
35
35
|
this.isPathWithinRoot = options.isPathWithinRoot;
|
|
36
36
|
this.sessionLogger = options.sessionLogger;
|
|
37
|
-
this.
|
|
37
|
+
this.listTerminalWorkspaceRefs = options.listTerminalWorkspaceRefs ?? (async () => []);
|
|
38
|
+
this.listTerminalWorkspaceRoots =
|
|
39
|
+
options.listTerminalWorkspaceRoots ??
|
|
40
|
+
(async () => (await this.listTerminalWorkspaceRefs()).map((workspace) => workspace.cwd));
|
|
38
41
|
this.clientSupportsWrapReflow = options.clientSupportsWrapReflow ?? (() => false);
|
|
39
42
|
this.getClientBufferedAmount = options.getClientBufferedAmount ?? (() => 0);
|
|
40
43
|
}
|
|
@@ -331,7 +334,8 @@ export class TerminalSessionController {
|
|
|
331
334
|
});
|
|
332
335
|
return;
|
|
333
336
|
}
|
|
334
|
-
|
|
337
|
+
const workspaceId = msg.workspaceId ?? (await this.resolveLegacyTerminalWorkspaceId(msg.cwd));
|
|
338
|
+
if (!workspaceId) {
|
|
335
339
|
this.emit({
|
|
336
340
|
type: "create_terminal_response",
|
|
337
341
|
payload: {
|
|
@@ -344,7 +348,7 @@ export class TerminalSessionController {
|
|
|
344
348
|
}
|
|
345
349
|
const session = await this.terminalManager.createTerminal({
|
|
346
350
|
cwd: msg.cwd,
|
|
347
|
-
workspaceId
|
|
351
|
+
workspaceId,
|
|
348
352
|
name: msg.name,
|
|
349
353
|
command: msg.command,
|
|
350
354
|
args: msg.args,
|
|
@@ -378,6 +382,22 @@ export class TerminalSessionController {
|
|
|
378
382
|
});
|
|
379
383
|
}
|
|
380
384
|
}
|
|
385
|
+
async resolveLegacyTerminalWorkspaceId(cwd) {
|
|
386
|
+
const workspaceRefs = await this.listTerminalWorkspaceRefs();
|
|
387
|
+
if (workspaceRefs.length === 0) {
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
const exactMatch = workspaceRefs.find((workspace) => this.isSamePath(workspace.cwd, cwd));
|
|
391
|
+
if (exactMatch) {
|
|
392
|
+
return exactMatch.workspaceId;
|
|
393
|
+
}
|
|
394
|
+
const ownerRoot = this.resolveTerminalOwnerRoot(cwd, workspaceRefs.map((workspace) => workspace.cwd));
|
|
395
|
+
if (!ownerRoot) {
|
|
396
|
+
return null;
|
|
397
|
+
}
|
|
398
|
+
return (workspaceRefs.find((workspace) => this.isSamePath(workspace.cwd, ownerRoot))?.workspaceId ??
|
|
399
|
+
null);
|
|
400
|
+
}
|
|
381
401
|
async handleRenameTerminalRequest(msg) {
|
|
382
402
|
const respond = (success, error) => {
|
|
383
403
|
this.emit({
|
|
@@ -375,7 +375,6 @@ function buildGitDiffArgs(args) {
|
|
|
375
375
|
return ["diff", ...(args.ignoreWhitespace ? ["-w"] : []), ...args.extra];
|
|
376
376
|
}
|
|
377
377
|
const TRACKED_DIFF_NUMSTAT_MAX_BYTES = 2 * 1024 * 1024; // 2MB
|
|
378
|
-
const TRACKED_DIFF_PER_FILE_MAX_CHARS = 1024 * 1024;
|
|
379
378
|
const EMPTY_TREE_OBJECT_ID = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
380
379
|
function isUnbornHeadDiffError(error) {
|
|
381
380
|
return (error instanceof Error &&
|
|
@@ -423,56 +422,20 @@ async function getTrackedNumstatByPath(cwd, refs, ignoreWhitespace = false) {
|
|
|
423
422
|
}
|
|
424
423
|
return stats;
|
|
425
424
|
}
|
|
426
|
-
function
|
|
427
|
-
const
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
}
|
|
435
|
-
return
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
const header = firstLine.startsWith("diff --git ") ? firstLine.slice("diff --git ".length) : "";
|
|
441
|
-
const prefixedPathMatch = header.match(/^a\/(.+) b\/(.+)$/);
|
|
442
|
-
if (prefixedPathMatch) {
|
|
443
|
-
return prefixedPathMatch[2] ?? null;
|
|
444
|
-
}
|
|
445
|
-
const metadataPath = extractTrackedDiffMetadataPath(section, "+++ ") ??
|
|
446
|
-
extractTrackedDiffMetadataPath(section, "--- ");
|
|
447
|
-
if (metadataPath) {
|
|
448
|
-
return metadataPath;
|
|
449
|
-
}
|
|
450
|
-
const pathMatch = header.match(/^(\S+)\s+(\S+)$/);
|
|
451
|
-
return pathMatch?.[2] ?? null;
|
|
452
|
-
}
|
|
453
|
-
function splitTrackedDiffSections(diffText) {
|
|
454
|
-
const starts = [];
|
|
455
|
-
const diffHeaderPattern = /^diff --git /gm;
|
|
456
|
-
let match;
|
|
457
|
-
while ((match = diffHeaderPattern.exec(diffText))) {
|
|
458
|
-
starts.push(match.index);
|
|
459
|
-
}
|
|
460
|
-
const sections = [];
|
|
461
|
-
for (let index = 0; index < starts.length; index += 1) {
|
|
462
|
-
const start = starts[index];
|
|
463
|
-
const end = starts[index + 1] ?? diffText.length;
|
|
464
|
-
const text = diffText.slice(start, end);
|
|
465
|
-
const path = extractTrackedDiffSectionPath(text);
|
|
466
|
-
if (!path) {
|
|
467
|
-
continue;
|
|
468
|
-
}
|
|
469
|
-
sections.push({
|
|
470
|
-
path,
|
|
471
|
-
text,
|
|
472
|
-
isTooLarge: text.length > TRACKED_DIFF_PER_FILE_MAX_CHARS,
|
|
473
|
-
});
|
|
474
|
-
}
|
|
475
|
-
return sections;
|
|
425
|
+
async function getTrackedDiffTextForPath(input) {
|
|
426
|
+
const result = await runGitCommand(buildGitDiffArgs({
|
|
427
|
+
ignoreWhitespace: input.ignoreWhitespace,
|
|
428
|
+
extra: [...getCheckoutDiffRefArgs(input.refsForDiff), "--", input.path],
|
|
429
|
+
}), {
|
|
430
|
+
cwd: input.cwd,
|
|
431
|
+
envOverlay: READ_ONLY_GIT_ENV,
|
|
432
|
+
maxOutputBytes: PER_FILE_DIFF_MAX_BYTES,
|
|
433
|
+
});
|
|
434
|
+
return {
|
|
435
|
+
path: input.path,
|
|
436
|
+
text: result.stdout,
|
|
437
|
+
truncated: result.truncated,
|
|
438
|
+
};
|
|
476
439
|
}
|
|
477
440
|
export class NotGitRepoError extends Error {
|
|
478
441
|
constructor(cwd) {
|
|
@@ -1441,7 +1404,7 @@ export function warmCheckoutShortstatInBackground(cwd, context, onComplete) {
|
|
|
1441
1404
|
});
|
|
1442
1405
|
}
|
|
1443
1406
|
async function appendStructuredTrackedDiffs(input) {
|
|
1444
|
-
const { cwd, trackedChanges, trackedChangeByPath, trackedNumstatByPath, trackedPlaceholderByPath, trackedDiffText,
|
|
1407
|
+
const { cwd, trackedChanges, trackedChangeByPath, trackedNumstatByPath, trackedPlaceholderByPath, trackedDiffText, refsForDiff, ignoreWhitespace, structured, appendTrackedPlaceholderComment, } = input;
|
|
1445
1408
|
const parsedTrackedFiles = trackedDiffText.length > 0
|
|
1446
1409
|
? await parseAndHighlightDiff(trackedDiffText, cwd, {
|
|
1447
1410
|
getOldFileContent: async (file) => {
|
|
@@ -1487,7 +1450,6 @@ async function appendStructuredTrackedDiffs(input) {
|
|
|
1487
1450
|
// whitespace-filtered patch and numstat are both empty. Skip emitting a
|
|
1488
1451
|
// structured placeholder in that case so whitespace-only edits truly disappear.
|
|
1489
1452
|
if (ignoreWhitespace &&
|
|
1490
|
-
!trackedDiffTruncated &&
|
|
1491
1453
|
change.status.startsWith("M") &&
|
|
1492
1454
|
(!stat || (!stat.isBinary && stat.additions === 0 && stat.deletions === 0))) {
|
|
1493
1455
|
continue;
|
|
@@ -1499,7 +1461,7 @@ async function appendStructuredTrackedDiffs(input) {
|
|
|
1499
1461
|
additions: stat?.additions ?? 0,
|
|
1500
1462
|
deletions: stat?.deletions ?? 0,
|
|
1501
1463
|
hunks: [],
|
|
1502
|
-
status:
|
|
1464
|
+
status: "ok",
|
|
1503
1465
|
});
|
|
1504
1466
|
}
|
|
1505
1467
|
}
|
|
@@ -1564,43 +1526,42 @@ async function processTrackedChanges(input) {
|
|
|
1564
1526
|
trackedDiffPaths.push(change.path);
|
|
1565
1527
|
}
|
|
1566
1528
|
let trackedDiffText = "";
|
|
1567
|
-
let
|
|
1529
|
+
let trackedDiffBytes = 0;
|
|
1568
1530
|
if (trackedDiffPaths.length > 0) {
|
|
1569
|
-
const
|
|
1570
|
-
ignoreWhitespace,
|
|
1571
|
-
extra: [...getCheckoutDiffRefArgs(refsForDiff), "--", ...trackedDiffPaths],
|
|
1572
|
-
}), {
|
|
1531
|
+
const trackedDiffs = await Promise.all(trackedDiffPaths.map((path) => getTrackedDiffTextForPath({
|
|
1573
1532
|
cwd,
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1533
|
+
refsForDiff,
|
|
1534
|
+
path,
|
|
1535
|
+
ignoreWhitespace,
|
|
1536
|
+
})));
|
|
1578
1537
|
const visibleTrackedDiffs = [];
|
|
1579
|
-
const
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
const isTruncatedTail = trackedDiffTruncated && index === sections.length - 1;
|
|
1583
|
-
if (section.isTooLarge || isTruncatedTail) {
|
|
1584
|
-
trackedPlaceholderByPath.set(section.path, {
|
|
1538
|
+
for (const fileDiff of trackedDiffs) {
|
|
1539
|
+
if (fileDiff.truncated) {
|
|
1540
|
+
trackedPlaceholderByPath.set(fileDiff.path, {
|
|
1585
1541
|
status: "too_large",
|
|
1586
|
-
stat: trackedNumstatByPath.get(
|
|
1542
|
+
stat: trackedNumstatByPath.get(fileDiff.path) ?? null,
|
|
1587
1543
|
});
|
|
1588
1544
|
continue;
|
|
1589
1545
|
}
|
|
1590
|
-
|
|
1546
|
+
const diffBytes = Buffer.byteLength(fileDiff.text, "utf8");
|
|
1547
|
+
if (trackedDiffBytes + diffBytes > TOTAL_DIFF_MAX_BYTES) {
|
|
1548
|
+
trackedPlaceholderByPath.set(fileDiff.path, {
|
|
1549
|
+
status: "too_large",
|
|
1550
|
+
stat: trackedNumstatByPath.get(fileDiff.path) ?? null,
|
|
1551
|
+
});
|
|
1552
|
+
continue;
|
|
1553
|
+
}
|
|
1554
|
+
trackedDiffBytes += diffBytes;
|
|
1555
|
+
visibleTrackedDiffs.push(fileDiff.text);
|
|
1591
1556
|
}
|
|
1592
1557
|
trackedDiffText = visibleTrackedDiffs.join("");
|
|
1593
1558
|
appendDiff(trackedDiffText);
|
|
1594
|
-
if (trackedDiffTruncated) {
|
|
1595
|
-
appendDiff("# tracked diff truncated\n");
|
|
1596
|
-
}
|
|
1597
1559
|
}
|
|
1598
1560
|
return {
|
|
1599
1561
|
trackedChangeByPath,
|
|
1600
1562
|
trackedNumstatByPath,
|
|
1601
1563
|
trackedPlaceholderByPath,
|
|
1602
1564
|
trackedDiffText,
|
|
1603
|
-
trackedDiffTruncated,
|
|
1604
1565
|
};
|
|
1605
1566
|
}
|
|
1606
1567
|
async function resolveCheckoutDiffRefs(cwd, compare, context) {
|
|
@@ -1690,7 +1651,6 @@ export async function getCheckoutDiff(cwd, compare, context) {
|
|
|
1690
1651
|
trackedNumstatByPath: trackedDiff.trackedNumstatByPath,
|
|
1691
1652
|
trackedPlaceholderByPath: trackedDiff.trackedPlaceholderByPath,
|
|
1692
1653
|
trackedDiffText: trackedDiff.trackedDiffText,
|
|
1693
|
-
trackedDiffTruncated: trackedDiff.trackedDiffTruncated,
|
|
1694
1654
|
refsForDiff: effectiveRefsForDiff,
|
|
1695
1655
|
ignoreWhitespace,
|
|
1696
1656
|
structured,
|
|
@@ -24,6 +24,15 @@ const IGNORED_SUGGESTION_DIRECTORY_NAMES = new Set([
|
|
|
24
24
|
"coverage",
|
|
25
25
|
"vendor",
|
|
26
26
|
"__pycache__",
|
|
27
|
+
".git",
|
|
28
|
+
]);
|
|
29
|
+
const TRAVERSABLE_HIDDEN_WORKSPACE_DIRECTORY_NAMES = new Set([
|
|
30
|
+
".agents",
|
|
31
|
+
".claude",
|
|
32
|
+
".codex",
|
|
33
|
+
".github",
|
|
34
|
+
".paseo",
|
|
35
|
+
".vscode",
|
|
27
36
|
]);
|
|
28
37
|
export async function searchHomeDirectories(options) {
|
|
29
38
|
const query = options.query.trim();
|
|
@@ -71,6 +80,17 @@ export async function searchWorkspaceEntries(options) {
|
|
|
71
80
|
return [];
|
|
72
81
|
}
|
|
73
82
|
const matchMode = options.matchMode ?? "fuzzy";
|
|
83
|
+
const exactEntry = queryParts.isPathQuery && matchMode === "suffix"
|
|
84
|
+
? await resolveWorkspaceExactEntry({
|
|
85
|
+
workspaceRoot,
|
|
86
|
+
query: options.query,
|
|
87
|
+
includeDirectories,
|
|
88
|
+
includeFiles,
|
|
89
|
+
})
|
|
90
|
+
: null;
|
|
91
|
+
if (exactEntry && limit <= 1) {
|
|
92
|
+
return [exactEntry];
|
|
93
|
+
}
|
|
74
94
|
if (queryParts.isPathQuery && matchMode !== "suffix") {
|
|
75
95
|
return searchWorkspaceWithinParentDirectory({
|
|
76
96
|
workspaceRoot,
|
|
@@ -84,7 +104,7 @@ export async function searchWorkspaceEntries(options) {
|
|
|
84
104
|
const searchTerm = matchMode === "suffix"
|
|
85
105
|
? [queryParts.parentPart, queryParts.searchTerm].filter(Boolean).join("/")
|
|
86
106
|
: queryParts.searchTerm;
|
|
87
|
-
|
|
107
|
+
const entries = await searchWorkspaceAcrossTree({
|
|
88
108
|
workspaceRoot,
|
|
89
109
|
searchTerm,
|
|
90
110
|
limit,
|
|
@@ -94,6 +114,53 @@ export async function searchWorkspaceEntries(options) {
|
|
|
94
114
|
maxDepth: options.maxDepth ?? DEFAULT_MAX_DEPTH,
|
|
95
115
|
maxEntriesScanned: options.maxEntriesScanned ?? DEFAULT_MAX_DIRECTORIES_SCANNED,
|
|
96
116
|
});
|
|
117
|
+
return exactEntry ? prependWorkspaceEntry(exactEntry, entries).slice(0, limit) : entries;
|
|
118
|
+
}
|
|
119
|
+
async function resolveWorkspaceExactEntry(input) {
|
|
120
|
+
const normalized = input.query
|
|
121
|
+
.trim()
|
|
122
|
+
.replace(/\\/g, "/")
|
|
123
|
+
.replace(/^\.\/+/, "")
|
|
124
|
+
.replace(/\/{2,}/g, "/");
|
|
125
|
+
if (!normalized) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
const candidatePath = path.isAbsolute(normalized)
|
|
129
|
+
? path.resolve(normalized)
|
|
130
|
+
: path.resolve(input.workspaceRoot, normalized);
|
|
131
|
+
let resolvedPath;
|
|
132
|
+
try {
|
|
133
|
+
resolvedPath = await realpath(candidatePath);
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
if (!isPathInsideRoot(input.workspaceRoot, resolvedPath)) {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
const stats = await stat(resolvedPath).catch(() => null);
|
|
142
|
+
if (!stats) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
if (stats.isFile() && input.includeFiles) {
|
|
146
|
+
return {
|
|
147
|
+
path: normalizeRelativePath(input.workspaceRoot, resolvedPath),
|
|
148
|
+
kind: "file",
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
if (stats.isDirectory() && input.includeDirectories) {
|
|
152
|
+
return {
|
|
153
|
+
path: normalizeRelativePath(input.workspaceRoot, resolvedPath),
|
|
154
|
+
kind: "directory",
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
function prependWorkspaceEntry(entry, entries) {
|
|
160
|
+
return [
|
|
161
|
+
entry,
|
|
162
|
+
...entries.filter((candidate) => candidate.kind !== entry.kind || candidate.path !== entry.path),
|
|
163
|
+
];
|
|
97
164
|
}
|
|
98
165
|
function normalizeLimit(limit) {
|
|
99
166
|
const candidate = limit ?? DEFAULT_LIMIT;
|
|
@@ -185,6 +252,9 @@ async function searchWorkspaceWithinParentDirectory(input) {
|
|
|
185
252
|
if (entry.kind === "file" && !input.includeFiles) {
|
|
186
253
|
continue;
|
|
187
254
|
}
|
|
255
|
+
if (isHiddenWorkspaceSuggestion(entry)) {
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
188
258
|
const rankedEntry = rankWorkspaceEntry({
|
|
189
259
|
absolutePath: entry.absolutePath,
|
|
190
260
|
kind: entry.kind,
|
|
@@ -230,6 +300,10 @@ async function searchWorkspaceAcrossTree(input) {
|
|
|
230
300
|
if (entry.kind === "directory" && !input.includeDirectories) {
|
|
231
301
|
continue;
|
|
232
302
|
}
|
|
303
|
+
// Hidden directories are traversed, but not offered as suggestions.
|
|
304
|
+
if (isHiddenWorkspaceSuggestion(entry)) {
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
233
307
|
if (entry.kind === "file" && !input.includeFiles) {
|
|
234
308
|
continue;
|
|
235
309
|
}
|
|
@@ -618,7 +692,23 @@ async function listWorkspaceChildEntries(input) {
|
|
|
618
692
|
return cached.entries;
|
|
619
693
|
}
|
|
620
694
|
const dirents = await readdir(input.directory, { withFileTypes: true }).catch(() => []);
|
|
621
|
-
const candidates = dirents.filter((dirent) =>
|
|
695
|
+
const candidates = dirents.filter((dirent) => {
|
|
696
|
+
if (isIgnoredSuggestionDirectoryName(dirent.name)) {
|
|
697
|
+
return false;
|
|
698
|
+
}
|
|
699
|
+
if (isHiddenDirectoryName(dirent.name) &&
|
|
700
|
+
!dirent.isFile() &&
|
|
701
|
+
!isTraversableHiddenWorkspaceDirectoryName(dirent.name)) {
|
|
702
|
+
return false;
|
|
703
|
+
}
|
|
704
|
+
// Allowlisted hidden directories remain traversable so file links like
|
|
705
|
+
// `.claude/settings.local.json` can resolve, but hidden files (e.g.
|
|
706
|
+
// `.DS_Store`) should never be suggested.
|
|
707
|
+
if (dirent.isFile() && isHiddenDirectoryName(dirent.name)) {
|
|
708
|
+
return false;
|
|
709
|
+
}
|
|
710
|
+
return true;
|
|
711
|
+
});
|
|
622
712
|
const resolved = await Promise.all(candidates.map(async (dirent) => {
|
|
623
713
|
const candidatePath = path.join(input.directory, dirent.name);
|
|
624
714
|
const entry = await resolveWorkspaceCandidate({
|
|
@@ -687,9 +777,15 @@ async function resolveWorkspaceCandidate(input) {
|
|
|
687
777
|
function isHiddenDirectoryName(name) {
|
|
688
778
|
return name.startsWith(".");
|
|
689
779
|
}
|
|
780
|
+
function isHiddenWorkspaceSuggestion(entry) {
|
|
781
|
+
return isHiddenDirectoryName(entry.name);
|
|
782
|
+
}
|
|
690
783
|
function isIgnoredSuggestionDirectoryName(name) {
|
|
691
784
|
return IGNORED_SUGGESTION_DIRECTORY_NAMES.has(name);
|
|
692
785
|
}
|
|
786
|
+
function isTraversableHiddenWorkspaceDirectoryName(name) {
|
|
787
|
+
return TRAVERSABLE_HIDDEN_WORKSPACE_DIRECTORY_NAMES.has(name);
|
|
788
|
+
}
|
|
693
789
|
function setDirectoryListCache(cacheKey, entry) {
|
|
694
790
|
directoryListCache.set(cacheKey, entry);
|
|
695
791
|
pruneDirectoryListCache();
|