@trim21/personal-pi-extensions 0.1.622 → 0.1.626
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/package.json +3 -3
- package/src/aft/config.ts +1 -1
- package/src/aft/tools.ts +4 -4
- package/src/bwrap/core.ts +1 -1
- package/src/bwrap/holder.js +3 -1
- package/src/bwrap/network-stack.ts +1 -1
- package/src/bwrap/runtime.ts +1 -1
- package/src/claude-code/files.ts +1 -1
- package/src/claude-code/search.ts +2 -3
- package/src/claude-code/session-tools.ts +1 -1
- package/src/claude-code/shell.ts +1 -1
- package/src/lib/cli.ts +1 -1
- package/src/lib/file-reads.ts +1 -1
- package/src/lib/github.ts +4 -4
- package/src/lib/lsp/client.ts +169 -176
- package/src/lib/lsp/lsp.ts +7 -7
- package/src/lib/lsp/rename.ts +1 -1
- package/src/lib/lsp/watcher.ts +1 -1
- package/src/lib/path.ts +1 -1
- package/src/lib/proxy.ts +2 -2
- package/src/lib/request-policy.ts +1 -1
- package/src/openai-cost/config.ts +4 -4
- package/src/openai-cost/cost.ts +2 -2
- package/src/opencode/files.ts +2 -2
- package/src/opencode/grep.ts +1 -1
- package/src/opencode/index.ts +1 -25
- package/src/opencode/question.ts +1 -3
- package/src/opencode/todo.ts +4 -5
- package/src/talk/format.ts +1 -1
- package/src/talk/group.ts +3 -3
- package/src/talk/mailbox.ts +6 -6
- package/src/talk/policy.ts +3 -3
- package/src/talk/registry.ts +4 -4
- package/src/web/config.ts +1 -1
package/src/lib/lsp/lsp.ts
CHANGED
|
@@ -103,7 +103,7 @@ export type LspConfig = Static<typeof lspConfigSchema>;
|
|
|
103
103
|
* 解析期缺省(单一来源)。超时/LRU 与 client.create 共用 clientDefaults;
|
|
104
104
|
* watch 无 client 对应项,数值在此集中。
|
|
105
105
|
*/
|
|
106
|
-
|
|
106
|
+
const configDefaults = {
|
|
107
107
|
watch: {
|
|
108
108
|
enabled: true,
|
|
109
109
|
debounceMs: 300,
|
|
@@ -119,7 +119,7 @@ const RETRY_COOLDOWN_MS = 60_000;
|
|
|
119
119
|
const NOTIFY_INTERVAL_MS = 5 * 60_000;
|
|
120
120
|
|
|
121
121
|
/** 生效的工作区监听配置(缺省值已应用)。 */
|
|
122
|
-
|
|
122
|
+
interface EffectiveWatchConfig {
|
|
123
123
|
enabled: boolean;
|
|
124
124
|
debounceMs: number;
|
|
125
125
|
flushMs: number;
|
|
@@ -388,21 +388,21 @@ interface LspState {
|
|
|
388
388
|
}
|
|
389
389
|
|
|
390
390
|
/** 渲染 LSP status 文本的回调(传入 undefined 表示清除)。 */
|
|
391
|
-
|
|
391
|
+
type StatusRenderer = (text: string | undefined) => void;
|
|
392
392
|
|
|
393
|
-
|
|
393
|
+
type LspInspectQuery = "definition" | "references" | "hover";
|
|
394
394
|
|
|
395
395
|
/**
|
|
396
396
|
* 返回类型与 query 关联:query 为 "hover" 时返回 hover 内容,否则返回位置列表。
|
|
397
397
|
* 关联由 `inspect` 的重载签名建立(泛型参数下 TS 无法验证分支与返回体的对应关系,
|
|
398
398
|
* 所以接口按 query 分重载,实现体只需返回这个联合)。
|
|
399
399
|
*/
|
|
400
|
-
|
|
400
|
+
type LspInspectResult<Q extends LspInspectQuery = LspInspectQuery> = Q extends "hover"
|
|
401
401
|
? { serverID: string; query: "hover"; hover: Hover | null }
|
|
402
402
|
: { serverID: string; query: "definition" | "references"; locations: InspectLocation[] };
|
|
403
403
|
|
|
404
404
|
/** 只读符号查询的入参:query 决定返回体(见 LspInspectResult)。line / character 为 0-based。 */
|
|
405
|
-
|
|
405
|
+
interface LspInspectRequest<Q extends LspInspectQuery = LspInspectQuery> {
|
|
406
406
|
file: string;
|
|
407
407
|
cwd: string;
|
|
408
408
|
line: number;
|
|
@@ -411,7 +411,7 @@ export interface LspInspectRequest<Q extends LspInspectQuery = LspInspectQuery>
|
|
|
411
411
|
options?: LspRequestOptions;
|
|
412
412
|
}
|
|
413
413
|
|
|
414
|
-
|
|
414
|
+
interface LspRequestOptions {
|
|
415
415
|
notify?: ExtensionUIContext["notify"];
|
|
416
416
|
/** 中止时提前结束诊断等待(已中止时直接跳过诊断)。 */
|
|
417
417
|
signal?: AbortSignal;
|
package/src/lib/lsp/rename.ts
CHANGED
|
@@ -196,7 +196,7 @@ export function canonicalizeEdit(edit: WorkspaceEdit): string {
|
|
|
196
196
|
const WORD_PATTERN = /[\p{L}\p{N}_$]+/gu;
|
|
197
197
|
|
|
198
198
|
/** 两个路径集合是否一致(用于判断 references 结果是否收敛)。 */
|
|
199
|
-
|
|
199
|
+
function samePathSet(a: ReadonlySet<string>, b: ReadonlySet<string>): boolean {
|
|
200
200
|
if (a.size !== b.size) {
|
|
201
201
|
return false;
|
|
202
202
|
}
|
package/src/lib/lsp/watcher.ts
CHANGED
package/src/lib/path.ts
CHANGED
|
@@ -73,7 +73,7 @@ export function formatDisplayPath(cwd: string, filePath: string): string {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
/** subtitle 中路径的最大显示长度,超过时退化为 `parent/basename`。 */
|
|
76
|
-
|
|
76
|
+
const MAX_SUBTITLE_PATH_LENGTH = 30;
|
|
77
77
|
|
|
78
78
|
function shortenSubtitlePath(filePath: string, display: string): string {
|
|
79
79
|
if (display.length <= MAX_SUBTITLE_PATH_LENGTH) {
|
package/src/lib/proxy.ts
CHANGED
|
@@ -37,7 +37,7 @@ export interface HttpProxySettings {
|
|
|
37
37
|
noProxy?: string;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
function proxyConfigPath(): string {
|
|
41
41
|
return join(homedir(), ".pi", "agent", "proxy.json");
|
|
42
42
|
}
|
|
43
43
|
|
|
@@ -89,7 +89,7 @@ function normalizeProxy(value: string): string {
|
|
|
89
89
|
* 这里是仓库里允许的同步例外)。文件不存在 = 未配置;文件读不了、JSON 非法或
|
|
90
90
|
* 字段不符都直接抛。
|
|
91
91
|
*/
|
|
92
|
-
|
|
92
|
+
function readProxySettings(
|
|
93
93
|
configPath: string = proxyConfigPath(),
|
|
94
94
|
env: NodeJS.ProcessEnv = process.env,
|
|
95
95
|
): HttpProxySettings {
|
|
@@ -20,7 +20,7 @@ export const REQUEST_POLICY_CHANNEL = "bwrap:request-policy";
|
|
|
20
20
|
|
|
21
21
|
const requestPolicyMessageSchema = Type.Object({ denyRequests: Type.Boolean() });
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
type RequestPolicyMessage = Static<typeof requestPolicyMessageSchema>;
|
|
24
24
|
|
|
25
25
|
export interface RequestPolicy {
|
|
26
26
|
/** 是否拒绝一切非沙盒请求(不弹审批框,直接拒绝)。 */
|
|
@@ -9,9 +9,9 @@ import { join } from "node:path";
|
|
|
9
9
|
import { type Static, Type } from "typebox";
|
|
10
10
|
import { Value } from "typebox/value";
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
const DEFAULT_PROVIDER_ID = "openai-cost";
|
|
13
|
+
const DEFAULT_PROVIDER_NAME = "OpenAI Cost";
|
|
14
|
+
const DEFAULT_API_KEY_ENV = "OPENAI_COST_API_KEY";
|
|
15
15
|
export const DEFAULT_CONTEXT_WINDOW = 128000;
|
|
16
16
|
export const DEFAULT_MAX_TOKENS = 8192;
|
|
17
17
|
export const ZERO_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 } as const;
|
|
@@ -54,7 +54,7 @@ export interface OpenaiCostConfig {
|
|
|
54
54
|
models?: OpenaiCostModelConfig[];
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
function openaiCostConfigPath(): string {
|
|
58
58
|
return join(homedir(), ".pi", "agent", "openai-cost.json");
|
|
59
59
|
}
|
|
60
60
|
|
package/src/openai-cost/cost.ts
CHANGED
|
@@ -98,7 +98,7 @@ export function costFromSseLine(line: string): number | undefined {
|
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
|
|
101
|
+
async function scanSseCost(body: ReadableStream<Uint8Array>): Promise<number | undefined> {
|
|
102
102
|
const reader = body.getReader();
|
|
103
103
|
const decoder = new TextDecoder();
|
|
104
104
|
let buffer = "";
|
|
@@ -196,7 +196,7 @@ function messageOf(event: AssistantMessageEvent): AssistantMessage {
|
|
|
196
196
|
return event.partial;
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
-
|
|
199
|
+
function applyCostToEvent(
|
|
200
200
|
event: AssistantMessageEvent,
|
|
201
201
|
reported: number | undefined,
|
|
202
202
|
): AssistantMessageEvent {
|
package/src/opencode/files.ts
CHANGED
|
@@ -200,7 +200,7 @@ function isBinaryFileBySample(sample: Uint8Array): boolean {
|
|
|
200
200
|
return nonPrintableCount / sample.length > 0.3;
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
-
|
|
203
|
+
interface TruncationResult {
|
|
204
204
|
content: string;
|
|
205
205
|
truncated: boolean;
|
|
206
206
|
truncatedBy: "lines" | "bytes" | null;
|
|
@@ -872,7 +872,7 @@ function registerWriteTool(
|
|
|
872
872
|
|
|
873
873
|
// ── 入口 ─────────────────────────────────────────────────────────────────────
|
|
874
874
|
|
|
875
|
-
|
|
875
|
+
function registerFileTools(
|
|
876
876
|
pi: ExtensionAPI,
|
|
877
877
|
getService: () => LspService,
|
|
878
878
|
policy: RequestPolicy,
|
package/src/opencode/grep.ts
CHANGED
|
@@ -130,7 +130,7 @@ export function renderGrepOutput(matches: readonly GrepMatch[], truncated: boole
|
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
/** 搜索根:绝对路径原样,相对路径按调用 cwd 解析。 */
|
|
133
|
-
|
|
133
|
+
function resolveSearchRoot(path: string | undefined, cwd: string): string {
|
|
134
134
|
if (path === undefined || path === "") {
|
|
135
135
|
return cwd;
|
|
136
136
|
}
|
package/src/opencode/index.ts
CHANGED
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
* opencode —— 统一注册 opencode 风格工具扩展。
|
|
3
3
|
*
|
|
4
4
|
* 聚合 files(read / edit / write 统一构建,共享 LSP service)、grep /
|
|
5
|
-
* glob(ripgrep 搜索)、todo / question / bash
|
|
6
|
-
* 公开 API(匹配引擎、纯函数等)也从这里重新导出,方便测试与其他模块
|
|
7
|
-
* (如 lib/write-guard)引用。
|
|
5
|
+
* glob(ripgrep 搜索)、todo / question / bash,一次加载全部注册。
|
|
8
6
|
*
|
|
9
7
|
* Usage:
|
|
10
8
|
* pi -e ./opencode/index.ts
|
|
@@ -24,28 +22,6 @@ import opencodeGrep from "./grep.js";
|
|
|
24
22
|
import opencodeQuestion from "./question.js";
|
|
25
23
|
import opencodeTodo from "./todo.js";
|
|
26
24
|
|
|
27
|
-
export { default as opencodeBash } from "./bash.js";
|
|
28
|
-
export {
|
|
29
|
-
applyEdit,
|
|
30
|
-
convertToLineEnding,
|
|
31
|
-
detectLineEnding,
|
|
32
|
-
normalizeForEdit,
|
|
33
|
-
normalizeToLF,
|
|
34
|
-
replace,
|
|
35
|
-
restoreLineEndings,
|
|
36
|
-
stripBom,
|
|
37
|
-
} from "./edit-engine.js";
|
|
38
|
-
export {
|
|
39
|
-
default as opencodeFileTools,
|
|
40
|
-
readLines,
|
|
41
|
-
resolveBom,
|
|
42
|
-
type TruncationResult,
|
|
43
|
-
} from "./files.js";
|
|
44
|
-
export { default as opencodeGlob } from "./glob.js";
|
|
45
|
-
export { default as opencodeGrep } from "./grep.js";
|
|
46
|
-
export { default as opencodeQuestion } from "./question.js";
|
|
47
|
-
export { default as opencodeTodo } from "./todo.js";
|
|
48
|
-
|
|
49
25
|
export default function opencode(pi: ExtensionAPI) {
|
|
50
26
|
// 非沙盒请求策略由本入口创建,注入文件工具与 bash runtime 共享同一份;
|
|
51
27
|
// 独立入口(web/fetch.ts 等)各自创建一份并经 pi.events 保持同步。
|
package/src/opencode/question.ts
CHANGED
|
@@ -35,7 +35,7 @@ export const CUSTOM_LABEL = "Type your own answer.";
|
|
|
35
35
|
export const DONE_LABEL = "✓ Done";
|
|
36
36
|
|
|
37
37
|
/** 与 opencode question.txt 语义一致的描述 */
|
|
38
|
-
|
|
38
|
+
const QUESTION_DESCRIPTION = [
|
|
39
39
|
"Use this tool when you need to ask the user questions during execution. This allows you to:",
|
|
40
40
|
"1. Gather user preferences or requirements",
|
|
41
41
|
"2. Clarify ambiguous instructions",
|
|
@@ -68,8 +68,6 @@ export const questionParamsSchema = Type.Object({
|
|
|
68
68
|
|
|
69
69
|
// ── types ────────────────────────────────────────────────────────────────────
|
|
70
70
|
|
|
71
|
-
export type QuestionOption = Static<typeof optionSchema>;
|
|
72
|
-
|
|
73
71
|
/** 与 schema 对齐的输入形状(multiple 可选) */
|
|
74
72
|
export type QuestionInput = Static<typeof questionSchema>;
|
|
75
73
|
|
package/src/opencode/todo.ts
CHANGED
|
@@ -32,14 +32,13 @@ import { type ToolPendant } from "../lib/pendant.js";
|
|
|
32
32
|
|
|
33
33
|
export const TOOL_NAME = "todowrite";
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
const TODO_STATUSES = ["pending", "in_progress", "completed", "cancelled"] as const;
|
|
36
|
+
const TODO_PRIORITIES = ["high", "medium", "low"] as const;
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
export type TodoPriority = (typeof TODO_PRIORITIES)[number];
|
|
38
|
+
type TodoStatus = (typeof TODO_STATUSES)[number];
|
|
40
39
|
|
|
41
40
|
/** opencode 的 todowrite 描述(语义等价),补上「整体替换」这一关键规则 */
|
|
42
|
-
|
|
41
|
+
const TODOWRITE_DESCRIPTION = [
|
|
43
42
|
"Create and maintain a structured task list for the current coding session. Tracks progress, organizes multi-step work, and surfaces status to the user.",
|
|
44
43
|
"",
|
|
45
44
|
"This tool REPLACES the entire todo list: pass the full updated list of todos on every call.",
|
package/src/talk/format.ts
CHANGED
|
@@ -35,7 +35,7 @@ export function formatDelivery(letter: Letter, now: number = Date.now()): string
|
|
|
35
35
|
/** One agent as the model sees it in a listing. `id` is the stable pi
|
|
36
36
|
* agent uuid; `name` is the display name when one was set; `self` marks
|
|
37
37
|
* the calling agent itself. */
|
|
38
|
-
|
|
38
|
+
interface AgentListItem {
|
|
39
39
|
status: string;
|
|
40
40
|
work_dir: string;
|
|
41
41
|
id: string;
|
package/src/talk/group.ts
CHANGED
|
@@ -20,7 +20,7 @@ import { Value } from "typebox/value";
|
|
|
20
20
|
|
|
21
21
|
import type { TalkStorage } from "./storage.js";
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
const GroupSchema = Type.Object({
|
|
24
24
|
id: Type.String(),
|
|
25
25
|
/** pi agent uuids of the members. */
|
|
26
26
|
members: Type.Array(Type.String()),
|
|
@@ -29,7 +29,7 @@ export const GroupSchema = Type.Object({
|
|
|
29
29
|
});
|
|
30
30
|
export type Group = Static<typeof GroupSchema>;
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
const GROUPS_NS = "groups";
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
35
|
* Group names are user-facing and become storage keys, so they are
|
|
@@ -38,7 +38,7 @@ export const GROUPS_NS = "groups";
|
|
|
38
38
|
*/
|
|
39
39
|
const GROUP_ID_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/;
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
function assertGroupId(id: string): void {
|
|
42
42
|
if (!GROUP_ID_PATTERN.test(id)) {
|
|
43
43
|
throw new TypeError(`Invalid group name: ${id}`);
|
|
44
44
|
}
|
package/src/talk/mailbox.ts
CHANGED
|
@@ -22,7 +22,7 @@ import { isRecord } from "../lib/narrow.js";
|
|
|
22
22
|
import { asksNs, assertAddress, inboxNs } from "./registry.js";
|
|
23
23
|
import type { TalkStorage } from "./storage.js";
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
const LetterSchema = Type.Object({
|
|
26
26
|
id: Type.String(),
|
|
27
27
|
from: Type.Object({
|
|
28
28
|
addr: Type.String(),
|
|
@@ -48,7 +48,7 @@ export type LetterKind = Letter["kind"];
|
|
|
48
48
|
* sender's pi session id as `from.sessionId`. Current letters pass through
|
|
49
49
|
* unchanged; anything else returns null.
|
|
50
50
|
*/
|
|
51
|
-
|
|
51
|
+
function normalizeLetter(value: unknown): Letter | null {
|
|
52
52
|
if (Value.Check(LetterSchema, value)) {
|
|
53
53
|
return value;
|
|
54
54
|
}
|
|
@@ -69,7 +69,7 @@ export function normalizeLetter(value: unknown): Letter | null {
|
|
|
69
69
|
return Value.Check(LetterSchema, migrated) ? migrated : null;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
const OutAskSchema = Type.Object({
|
|
73
73
|
askId: Type.String(),
|
|
74
74
|
toAddr: Type.String(),
|
|
75
75
|
body: Type.String(),
|
|
@@ -77,7 +77,7 @@ export const OutAskSchema = Type.Object({
|
|
|
77
77
|
});
|
|
78
78
|
export type OutAsk = Static<typeof OutAskSchema>;
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
const AuditRecordSchema = Type.Object({
|
|
81
81
|
ts: Type.Number(),
|
|
82
82
|
event: Type.Union([
|
|
83
83
|
Type.Literal("deposit"),
|
|
@@ -112,7 +112,7 @@ function assertMessageId(id: string): void {
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
/** The storage key for a letter: `<ts>-<id>.json`, sorted oldest-first. */
|
|
115
|
-
|
|
115
|
+
function letterFileName(letter: Letter): string {
|
|
116
116
|
assertMessageId(letter.id);
|
|
117
117
|
if (!Number.isSafeInteger(letter.ts) || letter.ts < 0) {
|
|
118
118
|
throw new TypeError(`Invalid talk letter timestamp: ${letter.ts}`);
|
|
@@ -121,7 +121,7 @@ export function letterFileName(letter: Letter): string {
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
/** Structural validation plus filename safety (id/timestamp shape). */
|
|
124
|
-
|
|
124
|
+
function isValidLetter(value: unknown): value is Letter {
|
|
125
125
|
if (!Value.Check(LetterSchema, value)) {
|
|
126
126
|
return false;
|
|
127
127
|
}
|
package/src/talk/policy.ts
CHANGED
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
|
|
9
9
|
import { MAX_BODY_CHARS } from "./mailbox.js";
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
const DEDUPE_WINDOW_MS = 10_000;
|
|
12
|
+
const RATE_LIMIT_MAX = 8;
|
|
13
|
+
const RATE_LIMIT_WINDOW_MS = 30_000;
|
|
14
14
|
export const BACKLOG_CAP = 50;
|
|
15
15
|
|
|
16
16
|
export type OutboundVerdict = { ok: true } | { ok: false; reason: string };
|
package/src/talk/registry.ts
CHANGED
|
@@ -32,7 +32,7 @@ const STATUS_SCHEMA = Type.Union([
|
|
|
32
32
|
Type.Literal("waiting-talk-message"),
|
|
33
33
|
]);
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
const AgentRecordSchema = Type.Object({
|
|
36
36
|
addr: Type.String(),
|
|
37
37
|
agentId: Type.String(),
|
|
38
38
|
name: Type.String(),
|
|
@@ -69,9 +69,9 @@ const LegacyAgentRecordSchema = Type.Object({
|
|
|
69
69
|
export type Presence = "live" | "offline";
|
|
70
70
|
|
|
71
71
|
/** Sweep leaves a record alone until its last activity was this long ago. */
|
|
72
|
-
|
|
72
|
+
const SWEEP_OFFLINE_GRACE_MS = 24 * 60 * 60 * 1000;
|
|
73
73
|
/** A mailbox holding undelivered mail is kept this long after last contact. */
|
|
74
|
-
|
|
74
|
+
const SWEEP_MAIL_KEEP_MS = 30 * 24 * 60 * 60 * 1000;
|
|
75
75
|
|
|
76
76
|
const ADDRESS_PATTERN = /^[a-f0-9]{12}$/;
|
|
77
77
|
|
|
@@ -88,7 +88,7 @@ export function assertAddress(addr: string): void {
|
|
|
88
88
|
|
|
89
89
|
// ── Storage namespaces ───────────────────────────────────────────────────
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
const RECORDS_NS = "records";
|
|
92
92
|
|
|
93
93
|
export function inboxNs(addr: string): string {
|
|
94
94
|
assertAddress(addr);
|
package/src/web/config.ts
CHANGED