@trim21/personal-pi-extensions 0.1.621 → 0.1.624
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 +1 -1
- 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 +5 -5
- 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/package.json
CHANGED
package/src/aft/config.ts
CHANGED
|
@@ -24,7 +24,7 @@ import { parseWithSchema } from "../lib/parse-with-schema.js";
|
|
|
24
24
|
/** aft 侧走 HTTP embeddings 端点的后端;fastembed(本地 ONNX)有意排除在外。 */
|
|
25
25
|
const REMOTE_SEMANTIC_BACKENDS = ["openai_compatible", "ollama"] as const;
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
type RemoteSemanticBackend = (typeof REMOTE_SEMANTIC_BACKENDS)[number];
|
|
28
28
|
|
|
29
29
|
/** 就绪的外部 embedding 后端:类型受支持且配了 base_url。 */
|
|
30
30
|
export interface SemanticRemote {
|
package/src/aft/tools.ts
CHANGED
|
@@ -61,7 +61,7 @@ export interface AftToolContext {
|
|
|
61
61
|
getState(): AftState;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
function bridgeFor(ctx: AftToolContext): AftProjectTransport {
|
|
65
65
|
return ctx.getState().pool.pool.getBridge(ctx.cwd);
|
|
66
66
|
}
|
|
67
67
|
|
|
@@ -79,7 +79,7 @@ export function compactArgs(args: Record<string, unknown>): Record<string, unkno
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
/** 人类视角的调用记录:input params + 与 LLM 相同的输出结果。 */
|
|
82
|
-
|
|
82
|
+
function buildPendantMarkdown(params: {
|
|
83
83
|
title: string;
|
|
84
84
|
input: unknown;
|
|
85
85
|
output: string;
|
|
@@ -327,7 +327,7 @@ const CALLGRAPH_SOFT_CODES = new Set(["symbol_not_found", "callgraph_building"])
|
|
|
327
327
|
* 以及冷构建本身超过内联窗口。这里在扩展侧重试,预算耗尽才把 building
|
|
328
328
|
* 文本交给模型。
|
|
329
329
|
*/
|
|
330
|
-
|
|
330
|
+
const CALLGRAPH_BUILD_RETRY_BUDGET_MS = 90_000;
|
|
331
331
|
const CALLGRAPH_BUILD_RETRY_INTERVAL_MS = 3_000;
|
|
332
332
|
|
|
333
333
|
export async function callCallgraphWithBuildRetry(
|
|
@@ -434,7 +434,7 @@ interface StatusSubscribableBridge {
|
|
|
434
434
|
* 运行时能力探测:`AftProjectTransport` 接口没声明 subscribeStatus,但实际实现
|
|
435
435
|
* (BinaryBridge)有。用 in 收窄做真检查,而不是断言成「一定支持」。
|
|
436
436
|
*/
|
|
437
|
-
|
|
437
|
+
function isStatusSubscribable(
|
|
438
438
|
bridge: AftProjectTransport,
|
|
439
439
|
): bridge is AftProjectTransport & StatusSubscribableBridge {
|
|
440
440
|
return "subscribeStatus" in bridge && typeof bridge.subscribeStatus === "function";
|
package/src/bwrap/core.ts
CHANGED
|
@@ -85,7 +85,7 @@ const bwrapConfigProperties = {
|
|
|
85
85
|
|
|
86
86
|
// 配置文件容忍未知字段:schema 之外的字段(如新版本扩展新增的配置)会被忽略,
|
|
87
87
|
// 避免整个 bwrap 配置因单个未知字段失效;已声明字段仍做类型/取值校验。
|
|
88
|
-
|
|
88
|
+
const bwrapConfigSchema = Type.Object(bwrapConfigProperties, {
|
|
89
89
|
additionalProperties: true,
|
|
90
90
|
});
|
|
91
91
|
|
package/src/bwrap/holder.js
CHANGED
|
@@ -18,7 +18,9 @@ function tap0Exists() {
|
|
|
18
18
|
}
|
|
19
19
|
async function waitFor(attempts, delayMs, predicate) {
|
|
20
20
|
for (let i = 0; i < attempts; i++) {
|
|
21
|
-
if (await predicate())
|
|
21
|
+
if (await predicate()) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
22
24
|
await sleep(delayMs);
|
|
23
25
|
}
|
|
24
26
|
return false;
|
|
@@ -31,7 +31,7 @@ export interface NetworkStackOptions {
|
|
|
31
31
|
readonly onHolderOutput?: (chunk: string) => void;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
interface NetworkStackExecOptions {
|
|
35
35
|
readonly command: string;
|
|
36
36
|
readonly cwd: string;
|
|
37
37
|
readonly bwrapPath: string;
|
package/src/bwrap/runtime.ts
CHANGED
|
@@ -368,7 +368,7 @@ export function describeSandbox(
|
|
|
368
368
|
* 状态块不再附——提权请求刚被用户拒绝,再提示 dangerouslyDisableSandbox 只会
|
|
369
369
|
* 诱导重复请求,作用域描述由本提醒承担。
|
|
370
370
|
*/
|
|
371
|
-
|
|
371
|
+
function describeSandboxChoice(resolved: ResolvedBwrap): string {
|
|
372
372
|
return [
|
|
373
373
|
"<system-reminder>",
|
|
374
374
|
`The user ran this command in the sandbox instead of approving unsandboxed execution: ${describeLimits(resolved)}.`,
|
package/src/claude-code/files.ts
CHANGED
|
@@ -16,7 +16,6 @@ export function registerSearchTools(pi: ExtensionAPI): void {
|
|
|
16
16
|
registerGrepTool(pi);
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
// Re-exported for tests
|
|
20
|
-
//
|
|
21
|
-
export { globFiles } from "./glob.js";
|
|
19
|
+
// Re-exported for tests that import pure functions from "./search.js"; the
|
|
20
|
+
// implementations live in the split files above.
|
|
22
21
|
export { buildGrepArguments, pageGrepOutput } from "./grep.js";
|
package/src/claude-code/shell.ts
CHANGED
|
@@ -72,7 +72,7 @@ function appendTruncationNotice(
|
|
|
72
72
|
* 截断时追加 `[Showing lines X-Y of N. Full output: path]` 提示。
|
|
73
73
|
* opencode 套件的 bash 工具复用同一逻辑。
|
|
74
74
|
*/
|
|
75
|
-
|
|
75
|
+
function formatBashSuccess(result: Awaited<ReturnType<BwrapRuntime["execute"]>>): {
|
|
76
76
|
content: { type: "text"; text: string }[];
|
|
77
77
|
details: BashToolDetails | undefined;
|
|
78
78
|
} {
|
package/src/lib/cli.ts
CHANGED
|
@@ -39,7 +39,7 @@ import { shlexSplit } from "./cli-args.js";
|
|
|
39
39
|
import { isRecord, isUnknownArray } from "./narrow.js";
|
|
40
40
|
|
|
41
41
|
/** Per-flag CLI metadata on top of the typebox schema. */
|
|
42
|
-
|
|
42
|
+
interface FlagMeta {
|
|
43
43
|
/** Optional short alias (single character), e.g. "n" for `-n`. */
|
|
44
44
|
short?: string;
|
|
45
45
|
/** Help line for this flag; defaults to the schema `description`. */
|
package/src/lib/file-reads.ts
CHANGED
|
@@ -86,7 +86,7 @@ export async function readStateKey(filePath: string): Promise<string> {
|
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
function snapshotsEqual(left: FileSnapshot, right: FileSnapshot): boolean {
|
|
90
90
|
return left.digest === right.digest;
|
|
91
91
|
}
|
|
92
92
|
|
package/src/lib/github.ts
CHANGED
|
@@ -254,7 +254,7 @@ function ghAuthToken(): Promise<string> {
|
|
|
254
254
|
* Error thrown when the GitHub search API rejects the request. Carries the
|
|
255
255
|
* original toolcall params so the model can see the exact input.
|
|
256
256
|
*/
|
|
257
|
-
|
|
257
|
+
class GithubSearchError extends Error {
|
|
258
258
|
readonly params: SearchParams;
|
|
259
259
|
readonly status: number | undefined;
|
|
260
260
|
|
|
@@ -279,7 +279,7 @@ function describeHttpError(status: number | undefined): string {
|
|
|
279
279
|
return `GitHub API error${status === undefined ? "" : ` (HTTP ${status})`}`;
|
|
280
280
|
}
|
|
281
281
|
|
|
282
|
-
|
|
282
|
+
interface GithubApi {
|
|
283
283
|
/** Run an octokit request; retries once with a fresh token on 401. */
|
|
284
284
|
call<T>(fn: (octokit: Octokit) => Promise<T>): Promise<T>;
|
|
285
285
|
}
|
|
@@ -299,7 +299,7 @@ export interface GithubClientOptions {
|
|
|
299
299
|
* module-level state. A stale cached token can produce 401s; the cache is
|
|
300
300
|
* dropped and the request retried once in that case.
|
|
301
301
|
*/
|
|
302
|
-
|
|
302
|
+
function createGithubApi(options: GithubClientOptions = {}): GithubApi {
|
|
303
303
|
let client: Octokit | undefined;
|
|
304
304
|
|
|
305
305
|
async function getClient(): Promise<Octokit> {
|
|
@@ -400,7 +400,7 @@ export interface ActionJob {
|
|
|
400
400
|
}
|
|
401
401
|
|
|
402
402
|
/** One step of a workflow run job, as the REST API reports it. */
|
|
403
|
-
|
|
403
|
+
interface RunJobStep {
|
|
404
404
|
readonly name: string;
|
|
405
405
|
readonly number: number;
|
|
406
406
|
readonly status: string;
|
package/src/lib/lsp/client.ts
CHANGED
|
@@ -48,7 +48,7 @@ export const WATCH_KIND_DELETE = 4;
|
|
|
48
48
|
const WATCH_KIND_ALL = WATCH_KIND_CREATE | WATCH_KIND_CHANGE | WATCH_KIND_DELETE;
|
|
49
49
|
|
|
50
50
|
/** 服务器通过 client/registerCapability 注册的单个 watcher。 */
|
|
51
|
-
|
|
51
|
+
interface WatcherGlob {
|
|
52
52
|
pattern: string;
|
|
53
53
|
kind: number;
|
|
54
54
|
}
|
|
@@ -168,7 +168,7 @@ interface PrepareRenameResponse {
|
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
/** renameSymbol 的请求与结果(line / character 为 0-based LSP position)。 */
|
|
171
|
-
|
|
171
|
+
interface RenameSymbolRequest {
|
|
172
172
|
path: string;
|
|
173
173
|
line: number;
|
|
174
174
|
character: number;
|
|
@@ -177,14 +177,14 @@ export interface RenameSymbolRequest {
|
|
|
177
177
|
signal?: AbortSignal;
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
-
|
|
180
|
+
interface RenameSymbolResult {
|
|
181
181
|
edit: WorkspaceEdit;
|
|
182
182
|
/** prepareRename 返回的符号当前名;服务器未提供 prepare 时缺省。 */
|
|
183
183
|
placeholder?: string;
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
/** definition / references / hover 请求的输入(line / character 为 0-based LSP position)。 */
|
|
187
|
-
|
|
187
|
+
interface InspectPositionRequest {
|
|
188
188
|
path: string;
|
|
189
189
|
line: number;
|
|
190
190
|
character: number;
|
|
@@ -272,7 +272,7 @@ function describeStartupFailure(
|
|
|
272
272
|
return parts.join("; ");
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
-
|
|
275
|
+
class InitializeError extends Error {
|
|
276
276
|
readonly serverID: string;
|
|
277
277
|
constructor(serverID: string, cause: unknown, detail?: string) {
|
|
278
278
|
super(`Failed to initialize LSP server ${serverID}${detail ? `: ${detail}` : ""}`, { cause });
|
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