@dyyz1993/pi-coding-agent 0.74.24 → 0.74.27

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.
Files changed (157) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/core/agent-session.d.ts.map +1 -1
  3. package/dist/core/agent-session.js +3 -0
  4. package/dist/core/agent-session.js.map +1 -1
  5. package/dist/core/session-manager.d.ts +5 -0
  6. package/dist/core/session-manager.d.ts.map +1 -1
  7. package/dist/core/session-manager.js +8 -0
  8. package/dist/core/session-manager.js.map +1 -1
  9. package/dist/extensions/agent-permissions/index.ts +235 -0
  10. package/dist/extensions/ask-tools/index.ts +115 -0
  11. package/dist/extensions/auto-memory/contract.d.ts +51 -0
  12. package/dist/extensions/auto-memory/contract.d.ts.map +1 -0
  13. package/dist/extensions/auto-memory/contract.js +2 -0
  14. package/dist/extensions/auto-memory/contract.js.map +1 -0
  15. package/dist/extensions/auto-memory/contract.ts +56 -0
  16. package/dist/extensions/auto-memory/index.ts +969 -0
  17. package/dist/extensions/auto-memory/prompts.ts +202 -0
  18. package/dist/extensions/auto-memory/skip-rules.ts +297 -0
  19. package/dist/extensions/auto-memory/utils.ts +208 -0
  20. package/dist/extensions/auto-session-title/index.ts +83 -0
  21. package/dist/extensions/bash-ext/contract.d.ts +79 -0
  22. package/dist/extensions/bash-ext/contract.d.ts.map +1 -0
  23. package/dist/extensions/bash-ext/contract.js +2 -0
  24. package/dist/extensions/bash-ext/contract.js.map +1 -0
  25. package/dist/extensions/bash-ext/contract.ts +69 -0
  26. package/dist/extensions/bash-ext/index.ts +858 -0
  27. package/dist/extensions/claude-hooks-compat/config-loader.ts +49 -0
  28. package/dist/extensions/claude-hooks-compat/handler-runner.ts +377 -0
  29. package/dist/extensions/claude-hooks-compat/if-parser.ts +53 -0
  30. package/dist/extensions/claude-hooks-compat/index.ts +178 -0
  31. package/dist/extensions/claude-hooks-compat/matcher.ts +17 -0
  32. package/dist/extensions/claude-hooks-compat/stdin-builder.ts +27 -0
  33. package/dist/extensions/claude-hooks-compat/types.ts +77 -0
  34. package/dist/extensions/compaction-manager/config.ts +47 -0
  35. package/dist/extensions/compaction-manager/context-fold.ts +63 -0
  36. package/dist/extensions/compaction-manager/index.ts +151 -0
  37. package/dist/extensions/compaction-manager/microcompact.ts +49 -0
  38. package/dist/extensions/compaction-manager/reactive.ts +9 -0
  39. package/dist/extensions/compaction-manager/session-memory.ts +48 -0
  40. package/dist/extensions/coordinator/INTEGRATION.md +376 -0
  41. package/dist/extensions/coordinator/handler.test.ts +277 -0
  42. package/dist/extensions/coordinator/handler.ts +189 -0
  43. package/dist/extensions/coordinator/index.ts +261 -0
  44. package/dist/extensions/coordinator/types.d.ts +100 -0
  45. package/dist/extensions/coordinator/types.d.ts.map +1 -0
  46. package/dist/extensions/coordinator/types.js +2 -0
  47. package/dist/extensions/coordinator/types.js.map +1 -0
  48. package/dist/extensions/coordinator/types.ts +72 -0
  49. package/dist/extensions/file-snapshot/index.ts +131 -0
  50. package/dist/extensions/file-time-guard/README.md +133 -0
  51. package/dist/extensions/file-time-guard/config.ts +13 -0
  52. package/dist/extensions/file-time-guard/index.ts +171 -0
  53. package/dist/extensions/hooks-engine/index.ts +117 -0
  54. package/dist/extensions/lsp/lsp/client/file-tracker.ts +70 -0
  55. package/dist/extensions/lsp/lsp/client/registry.ts +305 -0
  56. package/dist/extensions/lsp/lsp/client/runtime.ts +832 -0
  57. package/dist/extensions/lsp/lsp/config/resolver.ts +573 -0
  58. package/dist/extensions/lsp/lsp/contract.d.ts +101 -0
  59. package/dist/extensions/lsp/lsp/contract.d.ts.map +1 -0
  60. package/dist/extensions/lsp/lsp/contract.js +2 -0
  61. package/dist/extensions/lsp/lsp/contract.js.map +1 -0
  62. package/dist/extensions/lsp/lsp/contract.ts +103 -0
  63. package/dist/extensions/lsp/lsp/hooks/agent-end.ts +169 -0
  64. package/dist/extensions/lsp/lsp/hooks/diagnostics-mode.d.ts +10 -0
  65. package/dist/extensions/lsp/lsp/hooks/diagnostics-mode.d.ts.map +1 -0
  66. package/dist/extensions/lsp/lsp/hooks/diagnostics-mode.js +30 -0
  67. package/dist/extensions/lsp/lsp/hooks/diagnostics-mode.js.map +1 -0
  68. package/dist/extensions/lsp/lsp/hooks/diagnostics-mode.ts +41 -0
  69. package/dist/extensions/lsp/lsp/hooks/writethrough.ts +342 -0
  70. package/dist/extensions/lsp/lsp/index.ts +310 -0
  71. package/dist/extensions/lsp/lsp/lsp.test.ts +684 -0
  72. package/dist/extensions/lsp/lsp/monitoring/server-metrics.ts +176 -0
  73. package/dist/extensions/lsp/lsp/tools/lsp-tool.ts +402 -0
  74. package/dist/extensions/lsp/lsp/utils/dependency-resolver.ts +147 -0
  75. package/dist/extensions/lsp/lsp/utils/diagnostics-wait.ts +41 -0
  76. package/dist/extensions/lsp/lsp/utils/lsp-helpers.d.ts +20 -0
  77. package/dist/extensions/lsp/lsp/utils/lsp-helpers.d.ts.map +1 -0
  78. package/dist/extensions/lsp/lsp/utils/lsp-helpers.js +64 -0
  79. package/dist/extensions/lsp/lsp/utils/lsp-helpers.js.map +1 -0
  80. package/dist/extensions/lsp/lsp/utils/lsp-helpers.ts +76 -0
  81. package/dist/extensions/message-bridge/GUIDE.md +210 -0
  82. package/dist/extensions/message-bridge/index.ts +222 -0
  83. package/dist/extensions/output-guard/index.ts +446 -0
  84. package/dist/extensions/preview/index.ts +278 -0
  85. package/dist/extensions/rules-engine/MATCH_HISTORY_RECONCILIATION.md +111 -0
  86. package/dist/extensions/rules-engine/RULES-ENGINE-GUIDE.md +470 -0
  87. package/dist/extensions/rules-engine/cache.js +232 -0
  88. package/dist/extensions/rules-engine/cache.ts +38 -0
  89. package/dist/extensions/rules-engine/config.js +63 -0
  90. package/dist/extensions/rules-engine/config.ts +70 -0
  91. package/dist/extensions/rules-engine/index.js +1530 -0
  92. package/dist/extensions/rules-engine/index.ts +552 -0
  93. package/dist/extensions/rules-engine/injector.js +68 -0
  94. package/dist/extensions/rules-engine/injector.ts +74 -0
  95. package/dist/extensions/rules-engine/loader.js +179 -0
  96. package/dist/extensions/rules-engine/loader.ts +205 -0
  97. package/dist/extensions/rules-engine/matcher.js +534 -0
  98. package/dist/extensions/rules-engine/matcher.ts +52 -0
  99. package/dist/extensions/rules-engine/types.d.ts +156 -0
  100. package/dist/extensions/rules-engine/types.d.ts.map +1 -0
  101. package/dist/extensions/rules-engine/types.js +2 -0
  102. package/dist/extensions/rules-engine/types.js.map +1 -0
  103. package/dist/extensions/rules-engine/types.ts +169 -0
  104. package/dist/extensions/session-supervisor/checker.ts +116 -0
  105. package/dist/extensions/session-supervisor/config.ts +45 -0
  106. package/dist/extensions/session-supervisor/index.ts +726 -0
  107. package/dist/extensions/session-supervisor/prompts.ts +132 -0
  108. package/dist/extensions/session-supervisor/scheduler.ts +69 -0
  109. package/dist/extensions/session-supervisor/types.ts +215 -0
  110. package/dist/extensions/subagent/README.md +172 -0
  111. package/dist/extensions/subagent/agents/explorer.md +25 -0
  112. package/dist/extensions/subagent/agents/guide.md +27 -0
  113. package/dist/extensions/subagent/agents/planner.md +37 -0
  114. package/dist/extensions/subagent/agents/reviewer.md +35 -0
  115. package/dist/extensions/subagent/agents/scout.md +50 -0
  116. package/dist/extensions/subagent/agents/verification.md +35 -0
  117. package/dist/extensions/subagent/agents/worker.md +24 -0
  118. package/dist/extensions/subagent/agents.ts +25 -0
  119. package/dist/extensions/subagent/index.ts +987 -0
  120. package/dist/extensions/subagent/prompts/implement-and-review.md +10 -0
  121. package/dist/extensions/subagent/prompts/implement.md +10 -0
  122. package/dist/extensions/subagent/prompts/scout-and-plan.md +9 -0
  123. package/dist/extensions/subagent-ext/contract.d.ts +2 -0
  124. package/dist/extensions/subagent-ext/contract.d.ts.map +1 -0
  125. package/dist/extensions/subagent-ext/contract.js +2 -0
  126. package/dist/extensions/subagent-ext/contract.js.map +1 -0
  127. package/dist/extensions/subagent-ext/contract.ts +1 -0
  128. package/dist/extensions/subagent-ext/index.ts +347 -0
  129. package/dist/extensions/subagent-shared/contract.d.ts +25 -0
  130. package/dist/extensions/subagent-shared/contract.d.ts.map +1 -0
  131. package/dist/extensions/subagent-shared/contract.js +2 -0
  132. package/dist/extensions/subagent-shared/contract.js.map +1 -0
  133. package/dist/extensions/subagent-shared/contract.ts +28 -0
  134. package/dist/extensions/subagent-shared/index.ts +4 -0
  135. package/dist/extensions/subagent-shared/render.ts +166 -0
  136. package/dist/extensions/subagent-shared/types.ts +35 -0
  137. package/dist/extensions/subagent-shared/utils.ts +112 -0
  138. package/dist/extensions/subagent-v2/contract.d.ts +2 -0
  139. package/dist/extensions/subagent-v2/contract.d.ts.map +1 -0
  140. package/dist/extensions/subagent-v2/contract.js +2 -0
  141. package/dist/extensions/subagent-v2/contract.js.map +1 -0
  142. package/dist/extensions/subagent-v2/contract.ts +1 -0
  143. package/dist/extensions/subagent-v2/index.ts +599 -0
  144. package/dist/extensions/todo-ext/contract.d.ts +27 -0
  145. package/dist/extensions/todo-ext/contract.d.ts.map +1 -0
  146. package/dist/extensions/todo-ext/contract.js +2 -0
  147. package/dist/extensions/todo-ext/contract.js.map +1 -0
  148. package/dist/extensions/todo-ext/contract.ts +30 -0
  149. package/dist/extensions/todo-ext/index.ts +419 -0
  150. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  151. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  152. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  153. package/examples/extensions/sandbox/package-lock.json +2 -2
  154. package/examples/extensions/sandbox/package.json +1 -1
  155. package/examples/extensions/with-deps/package-lock.json +2 -2
  156. package/examples/extensions/with-deps/package.json +1 -1
  157. package/package.json +6 -5
@@ -0,0 +1,147 @@
1
+ import { readFile, readdir } from "node:fs/promises";
2
+ import { basename, dirname, extname, join, relative, resolve } from "node:path";
3
+
4
+ const SUPPORTED_EXTENSIONS = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
5
+ const SKIP_DIRS = new Set(["node_modules", ".git", "dist", "build", ".next", ".nuxt", "coverage", "__pycache__"]);
6
+
7
+ const IMPORT_PATTERNS = [
8
+ /import\s+(?:type\s+)?(?:[\w$*,\s{}]+)\s+from\s+['"]([^'"]+)['"]/g,
9
+ /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
10
+ ];
11
+
12
+ export interface DependencyResolverOptions {
13
+ cwd?: string;
14
+ maxFilesToScan?: number;
15
+ maxDependents?: number;
16
+ }
17
+
18
+ export interface DependencyResolver {
19
+ resolveDependents(touchedFiles: string[]): Promise<string[]>;
20
+ }
21
+
22
+ export function createDependencyResolver(options: DependencyResolverOptions = {}): DependencyResolver {
23
+ const cwd = options.cwd ?? process.cwd();
24
+ const maxFilesToScan = options.maxFilesToScan ?? 300;
25
+ const maxDependents = options.maxDependents ?? 20;
26
+
27
+ return {
28
+ async resolveDependents(touchedFiles: string[]): Promise<string[]> {
29
+ if (touchedFiles.length === 0) return [];
30
+
31
+ const specifiers = buildModuleSpecifiers(touchedFiles, cwd);
32
+ if (specifiers.size === 0) return [];
33
+
34
+ const projectFiles = await collectProjectFiles(cwd, maxFilesToScan);
35
+ if (projectFiles.length === 0) return [];
36
+
37
+ const dependents = new Set<string>();
38
+
39
+ for (const projectFile of projectFiles) {
40
+ if (dependents.size >= maxDependents) break;
41
+
42
+ const normalized = projectFile.replace(/\\/g, "/");
43
+ if (touchedFiles.some((tf) => tf.replace(/\\/g, "/") === normalized)) continue;
44
+
45
+ try {
46
+ const content = await readFile(resolve(cwd, projectFile), "utf8");
47
+ const imports = extractImports(content);
48
+
49
+ for (const imp of imports) {
50
+ if (specifiers.has(imp)) {
51
+ dependents.add(normalized);
52
+ break;
53
+ }
54
+ }
55
+ } catch {
56
+ continue;
57
+ }
58
+ }
59
+
60
+ return [...dependents];
61
+ },
62
+ };
63
+ }
64
+
65
+ function buildModuleSpecifiers(touchedFiles: string[], cwd: string): Set<string> {
66
+ const specifiers = new Set<string>();
67
+
68
+ for (const filePath of touchedFiles) {
69
+ const absPath = resolve(cwd, filePath);
70
+ const dir = dirname(absPath);
71
+ const nameWithoutExt = basename(absPath, extname(absPath));
72
+ const indexName = nameWithoutExt === "index" ? "" : undefined;
73
+
74
+ const relDir = relative(cwd, dir).replace(/\\/g, "/");
75
+
76
+ if (relDir === "" || relDir === ".") {
77
+ specifiers.add(`./${nameWithoutExt}`);
78
+ if (indexName !== undefined) specifiers.add("./");
79
+ } else {
80
+ specifiers.add(`./${relDir}/${nameWithoutExt}`);
81
+ specifiers.add(`../${relDir}/${nameWithoutExt}`);
82
+ specifiers.add(relDir + "/" + nameWithoutExt);
83
+ if (indexName !== undefined) {
84
+ specifiers.add(`./${relDir}/`);
85
+ specifiers.add(`../${relDir}/`);
86
+ specifiers.add(relDir + "/");
87
+ }
88
+ }
89
+
90
+ if (nameWithoutExt === "index") {
91
+ const parentDir = basename(dir);
92
+ specifiers.add(`./${parentDir}`);
93
+ specifiers.add(parentDir);
94
+ }
95
+ }
96
+
97
+ return specifiers;
98
+ }
99
+
100
+ async function collectProjectFiles(cwd: string, maxFiles: number): Promise<string[]> {
101
+ const files: string[] = [];
102
+
103
+ async function walk(dir: string, depth: number): Promise<void> {
104
+ if (files.length >= maxFiles || depth > 10) return;
105
+
106
+ let entries;
107
+ try {
108
+ entries = await readdir(dir, { withFileTypes: true });
109
+ } catch {
110
+ return;
111
+ }
112
+
113
+ for (const entry of entries) {
114
+ if (files.length >= maxFiles) return;
115
+
116
+ if (entry.isDirectory()) {
117
+ if (SKIP_DIRS.has(entry.name)) continue;
118
+ await walk(join(dir, entry.name), depth + 1);
119
+ } else if (entry.isFile()) {
120
+ const ext = extname(entry.name).toLowerCase();
121
+ if (SUPPORTED_EXTENSIONS.has(ext)) {
122
+ files.push(relative(cwd, join(dir, entry.name)).replace(/\\/g, "/"));
123
+ }
124
+ }
125
+ }
126
+ }
127
+
128
+ await walk(cwd, 0);
129
+ return files;
130
+ }
131
+
132
+ function extractImports(content: string): string[] {
133
+ const imports: string[] = [];
134
+
135
+ for (const pattern of IMPORT_PATTERNS) {
136
+ const regex = new RegExp(pattern.source, "g");
137
+ let match: RegExpExecArray | null;
138
+ while ((match = regex.exec(content)) !== null) {
139
+ const specifier = match[1];
140
+ if (specifier && (specifier.startsWith(".") || specifier.startsWith("/"))) {
141
+ imports.push(specifier);
142
+ }
143
+ }
144
+ }
145
+
146
+ return imports;
147
+ }
@@ -0,0 +1,41 @@
1
+ import type { LspRuntimeRegistry } from "../client/registry.js";
2
+
3
+ export interface DiagnosticsWaitOptions {
4
+ initialDelayMs?: number;
5
+ pollIntervalMs?: number;
6
+ maxWaitMs?: number;
7
+ }
8
+
9
+ const DEFAULT_INITIAL_DELAY_MS = 500;
10
+ const DEFAULT_POLL_INTERVAL_MS = 300;
11
+ const DEFAULT_MAX_WAIT_MS = 2500;
12
+
13
+ export async function waitForPushDiagnostics(
14
+ runtime: LspRuntimeRegistry,
15
+ filePath: string,
16
+ options: DiagnosticsWaitOptions = {},
17
+ ): Promise<void> {
18
+ const initialDelayMs = options.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS;
19
+ const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
20
+ const maxWaitMs = options.maxWaitMs ?? DEFAULT_MAX_WAIT_MS;
21
+
22
+ await sleep(initialDelayMs);
23
+
24
+ const start = Date.now();
25
+ let previousCount = runtime.getPublishedDiagnostics(filePath).length;
26
+
27
+ if (previousCount > 0) return;
28
+
29
+ while (Date.now() - start < maxWaitMs) {
30
+ await sleep(pollIntervalMs);
31
+ const currentCount = runtime.getPublishedDiagnostics(filePath).length;
32
+ if (currentCount > 0) return;
33
+ if (currentCount !== previousCount) {
34
+ previousCount = currentCount;
35
+ }
36
+ }
37
+ }
38
+
39
+ function sleep(ms: number): Promise<void> {
40
+ return new Promise((resolve) => setTimeout(resolve, ms));
41
+ }
@@ -0,0 +1,20 @@
1
+ export interface LspPosition {
2
+ line: number;
3
+ character: number;
4
+ }
5
+ export interface LspRange {
6
+ start: LspPosition;
7
+ end: LspPosition;
8
+ }
9
+ export interface LspDiagnostic {
10
+ range: LspRange;
11
+ severity?: number;
12
+ code?: string | number;
13
+ source?: string;
14
+ message: string;
15
+ }
16
+ export declare function normalizeRange(raw: unknown): LspRange | undefined;
17
+ export declare function normalizePosition(raw: unknown): LspPosition | undefined;
18
+ export declare function languageIdFromPath(filePath: string): string;
19
+ export declare function extractPullDiagnostics(payload: unknown): LspDiagnostic[];
20
+ //# sourceMappingURL=lsp-helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lsp-helpers.d.ts","sourceRoot":"","sources":["lsp-helpers.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,QAAQ;IACxB,KAAK,EAAE,WAAW,CAAC;IACnB,GAAG,EAAE,WAAW,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC7B,KAAK,EAAE,QAAQ,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,QAAQ,GAAG,SAAS,CAOjE;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,WAAW,GAAG,SAAS,CAKvE;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAmB3D;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,aAAa,EAAE,CAoBxE","sourcesContent":["export interface LspPosition {\n\tline: number;\n\tcharacter: number;\n}\n\nexport interface LspRange {\n\tstart: LspPosition;\n\tend: LspPosition;\n}\n\nexport interface LspDiagnostic {\n\trange: LspRange;\n\tseverity?: number;\n\tcode?: string | number;\n\tsource?: string;\n\tmessage: string;\n}\n\nexport function normalizeRange(raw: unknown): LspRange | undefined {\n\tif (!raw || typeof raw !== \"object\") return undefined;\n\tconst record = raw as Record<string, unknown>;\n\tconst start = normalizePosition(record.start);\n\tconst end = normalizePosition(record.end);\n\tif (!start || !end) return undefined;\n\treturn { start, end };\n}\n\nexport function normalizePosition(raw: unknown): LspPosition | undefined {\n\tif (!raw || typeof raw !== \"object\") return undefined;\n\tconst record = raw as Record<string, unknown>;\n\tif (typeof record.line !== \"number\" || typeof record.character !== \"number\") return undefined;\n\treturn { line: record.line, character: record.character };\n}\n\nexport function languageIdFromPath(filePath: string): string {\n\tconst ext = filePath.split(\".\").pop()?.toLowerCase() ?? \"\";\n\tconst map: Record<string, string> = {\n\t\tts: \"typescript\",\n\t\ttsx: \"typescriptreact\",\n\t\tjs: \"javascript\",\n\t\tjsx: \"javascriptreact\",\n\t\tjson: \"json\",\n\t\tcss: \"css\",\n\t\thtml: \"html\",\n\t\tmd: \"markdown\",\n\t\tpy: \"python\",\n\t\trs: \"rust\",\n\t\tgo: \"go\",\n\t\tc: \"c\",\n\t\tcpp: \"cpp\",\n\t\tlua: \"lua\",\n\t};\n\treturn map[ext] ?? ext;\n}\n\nexport function extractPullDiagnostics(payload: unknown): LspDiagnostic[] {\n\tif (!payload || typeof payload !== \"object\") return [];\n\tconst record = payload as Record<string, unknown>;\n\tconst items = Array.isArray(record.items) ? record.items : [];\n\tconst diagnostics: LspDiagnostic[] = [];\n\tfor (const item of items) {\n\t\tif (!item || typeof item !== \"object\") continue;\n\t\tconst d = item as Record<string, unknown>;\n\t\tif (typeof d.message !== \"string\") continue;\n\t\tconst range = normalizeRange(d.range);\n\t\tif (!range) continue;\n\t\tdiagnostics.push({\n\t\t\trange,\n\t\t\tmessage: d.message,\n\t\t\tseverity: typeof d.severity === \"number\" ? d.severity : undefined,\n\t\t\tcode: typeof d.code === \"string\" || typeof d.code === \"number\" ? d.code : undefined,\n\t\t\tsource: typeof d.source === \"string\" ? d.source : undefined,\n\t\t});\n\t}\n\treturn diagnostics;\n}\n"]}
@@ -0,0 +1,64 @@
1
+ export function normalizeRange(raw) {
2
+ if (!raw || typeof raw !== "object")
3
+ return undefined;
4
+ const record = raw;
5
+ const start = normalizePosition(record.start);
6
+ const end = normalizePosition(record.end);
7
+ if (!start || !end)
8
+ return undefined;
9
+ return { start, end };
10
+ }
11
+ export function normalizePosition(raw) {
12
+ if (!raw || typeof raw !== "object")
13
+ return undefined;
14
+ const record = raw;
15
+ if (typeof record.line !== "number" || typeof record.character !== "number")
16
+ return undefined;
17
+ return { line: record.line, character: record.character };
18
+ }
19
+ export function languageIdFromPath(filePath) {
20
+ const ext = filePath.split(".").pop()?.toLowerCase() ?? "";
21
+ const map = {
22
+ ts: "typescript",
23
+ tsx: "typescriptreact",
24
+ js: "javascript",
25
+ jsx: "javascriptreact",
26
+ json: "json",
27
+ css: "css",
28
+ html: "html",
29
+ md: "markdown",
30
+ py: "python",
31
+ rs: "rust",
32
+ go: "go",
33
+ c: "c",
34
+ cpp: "cpp",
35
+ lua: "lua",
36
+ };
37
+ return map[ext] ?? ext;
38
+ }
39
+ export function extractPullDiagnostics(payload) {
40
+ if (!payload || typeof payload !== "object")
41
+ return [];
42
+ const record = payload;
43
+ const items = Array.isArray(record.items) ? record.items : [];
44
+ const diagnostics = [];
45
+ for (const item of items) {
46
+ if (!item || typeof item !== "object")
47
+ continue;
48
+ const d = item;
49
+ if (typeof d.message !== "string")
50
+ continue;
51
+ const range = normalizeRange(d.range);
52
+ if (!range)
53
+ continue;
54
+ diagnostics.push({
55
+ range,
56
+ message: d.message,
57
+ severity: typeof d.severity === "number" ? d.severity : undefined,
58
+ code: typeof d.code === "string" || typeof d.code === "number" ? d.code : undefined,
59
+ source: typeof d.source === "string" ? d.source : undefined,
60
+ });
61
+ }
62
+ return diagnostics;
63
+ }
64
+ //# sourceMappingURL=lsp-helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lsp-helpers.js","sourceRoot":"","sources":["lsp-helpers.ts"],"names":[],"mappings":"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","sourcesContent":["export interface LspPosition {\n\tline: number;\n\tcharacter: number;\n}\n\nexport interface LspRange {\n\tstart: LspPosition;\n\tend: LspPosition;\n}\n\nexport interface LspDiagnostic {\n\trange: LspRange;\n\tseverity?: number;\n\tcode?: string | number;\n\tsource?: string;\n\tmessage: string;\n}\n\nexport function normalizeRange(raw: unknown): LspRange | undefined {\n\tif (!raw || typeof raw !== \"object\") return undefined;\n\tconst record = raw as Record<string, unknown>;\n\tconst start = normalizePosition(record.start);\n\tconst end = normalizePosition(record.end);\n\tif (!start || !end) return undefined;\n\treturn { start, end };\n}\n\nexport function normalizePosition(raw: unknown): LspPosition | undefined {\n\tif (!raw || typeof raw !== \"object\") return undefined;\n\tconst record = raw as Record<string, unknown>;\n\tif (typeof record.line !== \"number\" || typeof record.character !== \"number\") return undefined;\n\treturn { line: record.line, character: record.character };\n}\n\nexport function languageIdFromPath(filePath: string): string {\n\tconst ext = filePath.split(\".\").pop()?.toLowerCase() ?? \"\";\n\tconst map: Record<string, string> = {\n\t\tts: \"typescript\",\n\t\ttsx: \"typescriptreact\",\n\t\tjs: \"javascript\",\n\t\tjsx: \"javascriptreact\",\n\t\tjson: \"json\",\n\t\tcss: \"css\",\n\t\thtml: \"html\",\n\t\tmd: \"markdown\",\n\t\tpy: \"python\",\n\t\trs: \"rust\",\n\t\tgo: \"go\",\n\t\tc: \"c\",\n\t\tcpp: \"cpp\",\n\t\tlua: \"lua\",\n\t};\n\treturn map[ext] ?? ext;\n}\n\nexport function extractPullDiagnostics(payload: unknown): LspDiagnostic[] {\n\tif (!payload || typeof payload !== \"object\") return [];\n\tconst record = payload as Record<string, unknown>;\n\tconst items = Array.isArray(record.items) ? record.items : [];\n\tconst diagnostics: LspDiagnostic[] = [];\n\tfor (const item of items) {\n\t\tif (!item || typeof item !== \"object\") continue;\n\t\tconst d = item as Record<string, unknown>;\n\t\tif (typeof d.message !== \"string\") continue;\n\t\tconst range = normalizeRange(d.range);\n\t\tif (!range) continue;\n\t\tdiagnostics.push({\n\t\t\trange,\n\t\t\tmessage: d.message,\n\t\t\tseverity: typeof d.severity === \"number\" ? d.severity : undefined,\n\t\t\tcode: typeof d.code === \"string\" || typeof d.code === \"number\" ? d.code : undefined,\n\t\t\tsource: typeof d.source === \"string\" ? d.source : undefined,\n\t\t});\n\t}\n\treturn diagnostics;\n}\n"]}
@@ -0,0 +1,76 @@
1
+ export interface LspPosition {
2
+ line: number;
3
+ character: number;
4
+ }
5
+
6
+ export interface LspRange {
7
+ start: LspPosition;
8
+ end: LspPosition;
9
+ }
10
+
11
+ export interface LspDiagnostic {
12
+ range: LspRange;
13
+ severity?: number;
14
+ code?: string | number;
15
+ source?: string;
16
+ message: string;
17
+ }
18
+
19
+ export function normalizeRange(raw: unknown): LspRange | undefined {
20
+ if (!raw || typeof raw !== "object") return undefined;
21
+ const record = raw as Record<string, unknown>;
22
+ const start = normalizePosition(record.start);
23
+ const end = normalizePosition(record.end);
24
+ if (!start || !end) return undefined;
25
+ return { start, end };
26
+ }
27
+
28
+ export function normalizePosition(raw: unknown): LspPosition | undefined {
29
+ if (!raw || typeof raw !== "object") return undefined;
30
+ const record = raw as Record<string, unknown>;
31
+ if (typeof record.line !== "number" || typeof record.character !== "number") return undefined;
32
+ return { line: record.line, character: record.character };
33
+ }
34
+
35
+ export function languageIdFromPath(filePath: string): string {
36
+ const ext = filePath.split(".").pop()?.toLowerCase() ?? "";
37
+ const map: Record<string, string> = {
38
+ ts: "typescript",
39
+ tsx: "typescriptreact",
40
+ js: "javascript",
41
+ jsx: "javascriptreact",
42
+ json: "json",
43
+ css: "css",
44
+ html: "html",
45
+ md: "markdown",
46
+ py: "python",
47
+ rs: "rust",
48
+ go: "go",
49
+ c: "c",
50
+ cpp: "cpp",
51
+ lua: "lua",
52
+ };
53
+ return map[ext] ?? ext;
54
+ }
55
+
56
+ export function extractPullDiagnostics(payload: unknown): LspDiagnostic[] {
57
+ if (!payload || typeof payload !== "object") return [];
58
+ const record = payload as Record<string, unknown>;
59
+ const items = Array.isArray(record.items) ? record.items : [];
60
+ const diagnostics: LspDiagnostic[] = [];
61
+ for (const item of items) {
62
+ if (!item || typeof item !== "object") continue;
63
+ const d = item as Record<string, unknown>;
64
+ if (typeof d.message !== "string") continue;
65
+ const range = normalizeRange(d.range);
66
+ if (!range) continue;
67
+ diagnostics.push({
68
+ range,
69
+ message: d.message,
70
+ severity: typeof d.severity === "number" ? d.severity : undefined,
71
+ code: typeof d.code === "string" || typeof d.code === "number" ? d.code : undefined,
72
+ source: typeof d.source === "string" ? d.source : undefined,
73
+ });
74
+ }
75
+ return diagnostics;
76
+ }
@@ -0,0 +1,210 @@
1
+ # Message Bridge UI 拦截插件
2
+
3
+ ## 背景
4
+
5
+ pi-coding-agent 的扩展系统支持 `ui` 事件拦截机制。扩展可以通过 `pi.on("ui", handler)` 拦截 `ctx.ui.confirm()`、`ctx.ui.select()`、`ctx.ui.input()`、`ctx.ui.notify()` 调用,将 UI 交互转发到外部系统(如移动端、Web 控制台),实现远程裁决。
6
+
7
+ 本插件将 pi 的 UI 交互桥接到 Message Bridge 服务,使 Agent 在执行过程中可以:
8
+ 1. 将 confirm/select/input/notify 推送到移动端或 Web 控制台
9
+ 2. 等待用户远程回复后注入结果
10
+ 3. Agent 完成后将最终输出推送,用户可回复触发新任务
11
+
12
+ ## Message Bridge 服务文档
13
+
14
+ > 详见:[Message Bridge README](../../extensions/message-bridge/README.md)
15
+ >
16
+ > 服务地址:`https://message-bridge.docker.19930810.xyz:8443`
17
+ >
18
+ > API 端点:
19
+ > - `POST /push` — 推送问题,返回消息 ID
20
+ > - `GET /pull/{msg_id}` — 长轮询拉取回复(阻塞直到用户回复)
21
+ > - `POST /answer/{msg_id}` — 提交回复
22
+ > - `GET /messages` — 获取消息历史
23
+
24
+ ## 类型映射
25
+
26
+ | pi `ctx.ui` 方法 | Message Bridge 推送格式 | 回复解析 |
27
+ |---|---|---|
28
+ | `confirm(title, message)` | `{type: "confirm", question: "..."}` | `"【确认】: 确定"` → `confirmed: true`<br>`"【确认】: 取消"` → `confirmed: false` |
29
+ | `select(title, options)` | `{type: "radio", question: "...", options: [{label, description}]}` | `"【问题】: 选项A"` → `value: "选项A"` |
30
+ | `input(title, placeholder)` | 纯文本 `"{title}\n\nPlaceholder: {placeholder}"` | 直接返回 answer |
31
+ | `notify(message)` | 纯文本 | fire-and-forget,不等待回复 |
32
+
33
+ ## 事件处理流程
34
+
35
+ ### 1. UI 拦截(confirm/select/input)
36
+
37
+ ```
38
+ Agent 调用 ctx.ui.confirm("Permission", "Deploy?")
39
+ → pi 触发 ui 事件,调用 handler
40
+ → handler 返回 {action: "responded", confirmed: true}
41
+ (short-circuit:原始 UI 不会被调用)
42
+ → 工具拿到 confirmed=true,继续执行
43
+ ```
44
+
45
+ 推荐写法(直接 await,不触发 race):
46
+
47
+ ```typescript
48
+ pi.on("ui", async (event, ctx) => {
49
+ if (event.method === "confirm") {
50
+ const question = { type: "confirm", question: `${event.title} - ${event.message}` };
51
+ const id = await pushQuestion(question, sessionId);
52
+ const answer = await pullAnswer(id);
53
+ return { action: "responded", confirmed: parseConfirmAnswer(answer) };
54
+ }
55
+ // select, input 同理...
56
+ return undefined;
57
+ });
58
+ ```
59
+
60
+ ### 2. Agent 结束推送
61
+
62
+ ```
63
+ Agent 完成所有任务 → 触发 agent_end 事件
64
+ → 插件将 assistant 回复拼接为纯文本
65
+ → push 到 Message Bridge
66
+ → pullAnswer 阻塞等待用户指令
67
+ → 用户回复 "继续做XXX" → pi.sendUserMessage("继续做XXX")
68
+ → Agent 开始新一轮处理
69
+ ```
70
+
71
+ ```typescript
72
+ pi.on("agent_end", async (event) => {
73
+ const texts = event.messages
74
+ .filter(m => m.role === "assistant")
75
+ .map(m => extractText(m))
76
+ .filter(t => t.trim());
77
+ if (texts.length === 0) return;
78
+
79
+ const id = await pushQuestion(texts.join("\n\n---\n\n"), sessionId);
80
+ const answer = await pullAnswer(id);
81
+ if (answer?.trim()) {
82
+ pi.sendUserMessage(answer.trim());
83
+ }
84
+ });
85
+ ```
86
+
87
+ ### 3. 时序图
88
+
89
+ ```mermaid
90
+ sequenceDiagram
91
+ participant Agent as pi Agent
92
+ participant Tool as ask-confirm Tool
93
+ participant Runner as ExtensionRunner
94
+ participant Plugin as message-bridge Plugin
95
+ participant Bridge as Message Bridge
96
+ participant User as User (Mobile/Web)
97
+
98
+ Agent->>Tool: LLM 调用 ask-confirm
99
+ Tool->>Runner: ctx.ui.confirm("Permission", "Deploy?")
100
+ Runner->>Plugin: emitUIEvent({method:"confirm", ...})
101
+ Plugin->>Bridge: POST /push {type:"confirm", question:"..."}
102
+ Bridge-->>User: 推送通知
103
+ User->>Bridge: POST /answer "【确认】: 确定"
104
+ Bridge-->>Plugin: GET /pull 返回 answer
105
+ Plugin->>Runner: return {action:"responded", confirmed:true}
106
+ Runner->>Tool: return true (short-circuit)
107
+ Tool->>Agent: tool result
108
+
109
+ Note over Agent: Agent 完成...
110
+
111
+ Agent->>Plugin: agent_end event
112
+ Plugin->>Bridge: POST /push (最终文本)
113
+ Bridge-->>User: 推送通知
114
+ User->>Bridge: POST /answer "继续做XXX"
115
+ Bridge-->>Plugin: GET /pull 返回 answer
116
+ Plugin->>Agent: pi.sendUserMessage("继续做XXX")
117
+ Note over Agent: 开始新一轮处理
118
+ ```
119
+
120
+ ## pi 扩展 API 参考
121
+
122
+ ### 注册事件
123
+
124
+ ```typescript
125
+ export default function myExtension(pi) {
126
+ // 拦截 UI 事件
127
+ pi.on("ui", async (event, ctx) => {
128
+ // event.type: "ui"
129
+ // event.id: string (唯一标识)
130
+ // event.method: "confirm" | "select" | "input" | "notify"
131
+ // event.title: string
132
+ // event.message?: string
133
+ // event.options?: string[] (select 方法)
134
+ // event.placeholder?: string (input 方法)
135
+
136
+ // 返回 {action:"responded", ...} → short-circuit,原始 UI 不调用
137
+ // 返回 undefined → 放行到原始 UI
138
+ return { action: "responded", confirmed: true };
139
+ });
140
+
141
+ // 监听 Agent 结束
142
+ pi.on("agent_end", async (event) => {
143
+ // event.messages: AgentMessage[]
144
+ // 可以提取 assistant 文本推送
145
+ });
146
+ }
147
+ ```
148
+
149
+ ### 注册工具
150
+
151
+ ```typescript
152
+ pi.registerTool({
153
+ name: "ask-confirm",
154
+ label: "Ask Confirm",
155
+ description: "Asks a yes/no question",
156
+ parameters: Type.Object({ question: Type.String() }),
157
+ execute: async (id, params, signal, onUpdate, ctx) => {
158
+ // ctx.ui.confirm() → 被 ui 事件拦截
159
+ const confirmed = await ctx.ui.confirm("Title", params.question);
160
+ return { content: [{ type: "text", text: confirmed ? "yes" : "no" }] };
161
+ },
162
+ });
163
+ ```
164
+
165
+ ### UIEvent 类型
166
+
167
+ ```typescript
168
+ interface UIEvent {
169
+ type: "ui";
170
+ id: string;
171
+ method: "confirm" | "select" | "input" | "notify";
172
+ title: string;
173
+ message?: string;
174
+ options?: string[]; // select
175
+ placeholder?: string; // input
176
+ notifyType?: "info" | "warning" | "error";
177
+ }
178
+
179
+ type UIEventResult = { action: "responded"; confirmed?: boolean; value?: string } | undefined;
180
+ ```
181
+
182
+ ## 环境变量
183
+
184
+ | 变量 | 默认值 | 说明 |
185
+ |---|---|---|
186
+ | `MESSAGE_BRIDGE_URL` | `https://message-bridge.docker.19930810.xyz:8443` | Bridge 服务地址 |
187
+ | `MESSAGE_BRIDGE_SESSION_ID` | (空) | 可选 session 过滤 |
188
+
189
+ ## 启用方式
190
+
191
+ ```bash
192
+ # CLI
193
+ pi --extension ./extensions/message-bridge/index.ts
194
+
195
+ # settings.json
196
+ { "extensions": ["./extensions/message-bridge/index.ts"] }
197
+ ```
198
+
199
+ ## 已验证场景
200
+
201
+ | # | 场景 | 方式 |
202
+ |---|---|---|
203
+ | 1 | confirm → confirmed=true | 单元测试 + E2E + 真实 pi |
204
+ | 2 | confirm → confirmed=false (取消) | 单元测试 + E2E |
205
+ | 3 | select → radio 单选 | 单元测试 + E2E + 真实 pi |
206
+ | 4 | select → 多选 JSON 解析 | 单元测试 |
207
+ | 5 | input → 纯文本回复 | 单元测试 + E2E |
208
+ | 6 | notify → fire-and-forget | 单元测试 + E2E |
209
+ | 7 | agent_end → 推送最终文本 | 真实 pi |
210
+ | 8 | agent_end → 回复触发 sendUserMessage 新任务 | 真实 pi |