@dyyz1993/pi-coding-agent 0.69.12 → 0.69.14
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/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +13 -11
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +5 -4
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +73 -25
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/server-channel.d.ts +27 -0
- package/dist/core/extensions/server-channel.d.ts.map +1 -0
- package/dist/core/extensions/server-channel.js +40 -0
- package/dist/core/extensions/server-channel.js.map +1 -0
- package/dist/core/extensions/types.d.ts +19 -29
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/file-store/internal-git.d.ts +31 -0
- package/dist/core/file-store/internal-git.d.ts.map +1 -0
- package/dist/core/file-store/internal-git.js +176 -0
- package/dist/core/file-store/internal-git.js.map +1 -0
- package/dist/core/session-manager.d.ts +1 -0
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +7 -2
- package/dist/core/session-manager.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +1 -0
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +25 -1
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +26 -3
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +33 -2
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +22 -0
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/rules-engine/cache.d.ts +4 -0
- package/dist/rules-engine/cache.d.ts.map +1 -0
- package/dist/rules-engine/cache.js +32 -0
- package/dist/rules-engine/cache.js.map +1 -0
- package/dist/rules-engine/config.d.ts +8 -0
- package/dist/rules-engine/config.d.ts.map +1 -0
- package/dist/rules-engine/config.js +56 -0
- package/dist/rules-engine/config.js.map +1 -0
- package/dist/rules-engine/index.d.ts +10 -0
- package/dist/rules-engine/index.d.ts.map +1 -0
- package/dist/rules-engine/index.js +404 -0
- package/dist/rules-engine/index.js.map +1 -0
- package/dist/rules-engine/injector.d.ts +5 -0
- package/dist/rules-engine/injector.d.ts.map +1 -0
- package/dist/rules-engine/injector.js +57 -0
- package/dist/rules-engine/injector.js.map +1 -0
- package/dist/rules-engine/loader.d.ts +8 -0
- package/dist/rules-engine/loader.d.ts.map +1 -0
- package/dist/rules-engine/loader.js +190 -0
- package/dist/rules-engine/loader.js.map +1 -0
- package/dist/rules-engine/matcher.d.ts +3 -0
- package/dist/rules-engine/matcher.d.ts.map +1 -0
- package/dist/rules-engine/matcher.js +48 -0
- package/dist/rules-engine/matcher.js.map +1 -0
- package/dist/rules-engine/types.d.ts +150 -0
- package/dist/rules-engine/types.d.ts.map +1 -0
- package/dist/rules-engine/types.js +2 -0
- package/dist/rules-engine/types.js.map +1 -0
- package/docs/extensions.md +56 -56
- package/docs/file-rollback-design.md +287 -0
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/custom-provider-qwen-cli/package.json +1 -1
- package/examples/extensions/file-snapshot.ts +407 -0
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
function splitComma(val) {
|
|
4
|
+
const result = [];
|
|
5
|
+
let depth = 0;
|
|
6
|
+
let current = "";
|
|
7
|
+
for (const ch of val) {
|
|
8
|
+
if (ch === "{" || ch === "(" || ch === "[")
|
|
9
|
+
depth++;
|
|
10
|
+
else if (ch === "}" || ch === ")" || ch === "]")
|
|
11
|
+
depth--;
|
|
12
|
+
if (ch === "," && depth === 0) {
|
|
13
|
+
const trimmed = current.trim().replace(/^["']|["']$/g, "");
|
|
14
|
+
if (trimmed)
|
|
15
|
+
result.push(trimmed);
|
|
16
|
+
current = "";
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
current += ch;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const trimmed = current.trim().replace(/^["']|["']$/g, "");
|
|
23
|
+
if (trimmed)
|
|
24
|
+
result.push(trimmed);
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
export function parseFrontmatter(content) {
|
|
28
|
+
const frontmatterRegex = /^---\r?\n([\s\S]*?)\n?---\r?\n([\s\S]*)$/;
|
|
29
|
+
const match = content.match(frontmatterRegex);
|
|
30
|
+
if (!match) {
|
|
31
|
+
return { data: {}, body: content };
|
|
32
|
+
}
|
|
33
|
+
const [, frontmatterStr, body] = match;
|
|
34
|
+
const data = {};
|
|
35
|
+
const lines = frontmatterStr.split("\n");
|
|
36
|
+
let i = 0;
|
|
37
|
+
while (i < lines.length) {
|
|
38
|
+
const line = lines[i];
|
|
39
|
+
const colonIndex = line.indexOf(":");
|
|
40
|
+
if (colonIndex === -1) {
|
|
41
|
+
i++;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const rawKey = line.slice(0, colonIndex).trim();
|
|
45
|
+
let value = line.slice(colonIndex + 1).trim();
|
|
46
|
+
if (value === "" || value === "null" || value === "undefined") {
|
|
47
|
+
const listItems = [];
|
|
48
|
+
let j = i + 1;
|
|
49
|
+
while (j < lines.length) {
|
|
50
|
+
const subLine = lines[j];
|
|
51
|
+
if (subLine.match(/^\s*-\s+/)) {
|
|
52
|
+
listItems.push(subLine
|
|
53
|
+
.replace(/^\s*-\s+/, "")
|
|
54
|
+
.trim()
|
|
55
|
+
.replace(/^["']|["']$/g, ""));
|
|
56
|
+
j++;
|
|
57
|
+
}
|
|
58
|
+
else if (subLine.trim() === "" || subLine.match(/^\s+/)) {
|
|
59
|
+
j++;
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (listItems.length > 0) {
|
|
66
|
+
const camelKey = rawKey.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
67
|
+
data[camelKey] = listItems;
|
|
68
|
+
i = j;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
value = null;
|
|
72
|
+
}
|
|
73
|
+
else if (value.startsWith("[") && value.endsWith("]")) {
|
|
74
|
+
try {
|
|
75
|
+
value = JSON.parse(value.replace(/'/g, '"'));
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
value = value
|
|
79
|
+
.slice(1, -1)
|
|
80
|
+
.split(",")
|
|
81
|
+
.map((v) => v.trim().replace(/^["']|["']$/g, ""));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else if (value.startsWith('"') && value.endsWith('"')) {
|
|
85
|
+
value = value.slice(1, -1);
|
|
86
|
+
}
|
|
87
|
+
else if (value.startsWith("'") && value.endsWith("'")) {
|
|
88
|
+
value = value.slice(1, -1);
|
|
89
|
+
}
|
|
90
|
+
const camelKey = rawKey.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
91
|
+
if (camelKey === "paths" && typeof value === "string") {
|
|
92
|
+
data[camelKey] = splitComma(value);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
data[camelKey] = value;
|
|
96
|
+
}
|
|
97
|
+
i++;
|
|
98
|
+
}
|
|
99
|
+
return { data, body: body.trim() };
|
|
100
|
+
}
|
|
101
|
+
function extractTitle(body) {
|
|
102
|
+
for (const line of body.split("\n")) {
|
|
103
|
+
const trimmed = line.trim();
|
|
104
|
+
if (trimmed) {
|
|
105
|
+
return trimmed.replace(/^#+\s*/, "").replace(/\*\*/g, "");
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return "Untitled Rule";
|
|
109
|
+
}
|
|
110
|
+
function parsePaths(raw) {
|
|
111
|
+
if (!raw)
|
|
112
|
+
return [];
|
|
113
|
+
if (typeof raw === "string") {
|
|
114
|
+
return raw
|
|
115
|
+
.split(",")
|
|
116
|
+
.map((g) => g.trim())
|
|
117
|
+
.filter(Boolean);
|
|
118
|
+
}
|
|
119
|
+
if (Array.isArray(raw))
|
|
120
|
+
return raw;
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
export function parseRuleFile(filePath, content) {
|
|
124
|
+
const { data, body } = parseFrontmatter(content);
|
|
125
|
+
const paths = parsePaths(data.paths);
|
|
126
|
+
const isUnconditional = paths.length === 0 || (paths.length === 1 && paths[0] === "**");
|
|
127
|
+
const frontmatter = {};
|
|
128
|
+
if (data.description && typeof data.description === "string")
|
|
129
|
+
frontmatter.description = data.description;
|
|
130
|
+
if (data.severity && typeof data.severity === "string")
|
|
131
|
+
frontmatter.severity = data.severity;
|
|
132
|
+
if (data.paths)
|
|
133
|
+
frontmatter.paths = paths;
|
|
134
|
+
if (data.allowedTools)
|
|
135
|
+
frontmatter.allowedTools =
|
|
136
|
+
typeof data.allowedTools === "string" ? [data.allowedTools] : data.allowedTools;
|
|
137
|
+
if (data.whenToUse && typeof data.whenToUse === "string")
|
|
138
|
+
frontmatter.whenToUse = data.whenToUse;
|
|
139
|
+
if (data.notifyOnMatch !== undefined)
|
|
140
|
+
frontmatter.notifyOnMatch = data.notifyOnMatch === "true" || data.notifyOnMatch === true;
|
|
141
|
+
if (data.skipInPrompt !== undefined)
|
|
142
|
+
frontmatter.skipInPrompt = data.skipInPrompt === "true" || data.skipInPrompt === true;
|
|
143
|
+
return {
|
|
144
|
+
name: path.basename(filePath, ".md"),
|
|
145
|
+
filePath,
|
|
146
|
+
title: extractTitle(body),
|
|
147
|
+
content: body.trim(),
|
|
148
|
+
scope: "project",
|
|
149
|
+
source: "",
|
|
150
|
+
frontmatter,
|
|
151
|
+
isUnconditional,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function scanDir(dir, files = []) {
|
|
155
|
+
if (!fs.existsSync(dir))
|
|
156
|
+
return files;
|
|
157
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
158
|
+
for (const entry of entries) {
|
|
159
|
+
const fullPath = path.join(dir, entry.name);
|
|
160
|
+
if (entry.isDirectory()) {
|
|
161
|
+
scanDir(fullPath, files);
|
|
162
|
+
}
|
|
163
|
+
else if (entry.isFile() && (entry.name.endsWith(".md") || entry.name.endsWith(".mdc"))) {
|
|
164
|
+
files.push(fullPath);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return files;
|
|
168
|
+
}
|
|
169
|
+
export function loadRules(rulesDir) {
|
|
170
|
+
const rules = [];
|
|
171
|
+
const unconditional = [];
|
|
172
|
+
const conditional = [];
|
|
173
|
+
if (!fs.existsSync(rulesDir)) {
|
|
174
|
+
return { rules, unconditional, conditional, loadedAt: Date.now() };
|
|
175
|
+
}
|
|
176
|
+
const files = scanDir(rulesDir);
|
|
177
|
+
for (const filePath of files) {
|
|
178
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
179
|
+
const rule = parseRuleFile(filePath, content);
|
|
180
|
+
rules.push(rule);
|
|
181
|
+
if (rule.isUnconditional) {
|
|
182
|
+
unconditional.push(rule);
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
conditional.push(rule);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return { rules, unconditional, conditional, loadedAt: Date.now() };
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/rules-engine/loader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAGlC,SAAS,UAAU,CAAC,GAAW,EAAY;IAC1C,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,OAAO,GAAG,EAAE,CAAC;IACjB,KAAK,MAAM,EAAE,IAAI,GAAG,EAAE,CAAC;QACtB,IAAI,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,GAAG;YAAE,KAAK,EAAE,CAAC;aAC/C,IAAI,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,GAAG;YAAE,KAAK,EAAE,CAAC;QAEzD,IAAI,EAAE,KAAK,GAAG,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;YAC/B,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC;YAC3D,IAAI,OAAO;gBAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAClC,OAAO,GAAG,EAAE,CAAC;QACd,CAAC;aAAM,CAAC;YACP,OAAO,IAAI,EAAE,CAAC;QACf,CAAC;IACF,CAAC;IACD,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC;IAC3D,IAAI,OAAO;QAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAClC,OAAO,MAAM,CAAC;AAAA,CACd;AAED,MAAM,UAAU,gBAAgB,CAAC,OAAe,EAAmD;IAClG,MAAM,gBAAgB,GAAG,0CAA0C,CAAC;IACpE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;IAE9C,IAAI,CAAC,KAAK,EAAE,CAAC;QACZ,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IACpC,CAAC;IAED,MAAM,CAAC,EAAE,cAAc,EAAE,IAAI,CAAC,GAAG,KAAK,CAAC;IACvC,MAAM,IAAI,GAA4B,EAAE,CAAC;IAEzC,MAAM,KAAK,GAAG,cAAc,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACzC,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,OAAO,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACtB,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACrC,IAAI,UAAU,KAAK,CAAC,CAAC,EAAE,CAAC;YACvB,CAAC,EAAE,CAAC;YACJ,SAAS;QACV,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,IAAI,EAAE,CAAC;QAChD,IAAI,KAAK,GAA6B,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAExE,IAAI,KAAK,KAAK,EAAE,IAAI,KAAK,KAAK,MAAM,IAAI,KAAK,KAAK,WAAW,EAAE,CAAC;YAC/D,MAAM,SAAS,GAAa,EAAE,CAAC;YAC/B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACd,OAAO,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;gBACzB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;gBACzB,IAAI,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC;oBAC/B,SAAS,CAAC,IAAI,CACb,OAAO;yBACL,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC;yBACvB,IAAI,EAAE;yBACN,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAC7B,CAAC;oBACF,CAAC,EAAE,CAAC;gBACL,CAAC;qBAAM,IAAI,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;oBAC3D,CAAC,EAAE,CAAC;gBACL,CAAC;qBAAM,CAAC;oBACP,MAAM;gBACP,CAAC;YACF,CAAC;YACD,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC1B,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC;gBAClF,IAAI,CAAC,QAAQ,CAAC,GAAG,SAAS,CAAC;gBAC3B,CAAC,GAAG,CAAC,CAAC;gBACN,SAAS;YACV,CAAC;YACD,KAAK,GAAG,IAAI,CAAC;QACd,CAAC;aAAM,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACzD,IAAI,CAAC;gBACJ,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;YAC9C,CAAC;YAAC,MAAM,CAAC;gBACR,KAAK,GAAI,KAAgB;qBACvB,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;qBACZ,KAAK,CAAC,GAAG,CAAC;qBACV,GAAG,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC,CAAC;YAC5D,CAAC;QACF,CAAC;aAAM,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACzD,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC5B,CAAC;aAAM,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACzD,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC5B,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC;QAElF,IAAI,QAAQ,KAAK,OAAO,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YACvD,IAAI,CAAC,QAAQ,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;QACpC,CAAC;aAAM,CAAC;YACP,IAAI,CAAC,QAAQ,CAAC,GAAG,KAAK,CAAC;QACxB,CAAC;QACD,CAAC,EAAE,CAAC;IACL,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;AAAA,CACnC;AAED,SAAS,YAAY,CAAC,IAAY,EAAU;IAC3C,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACrC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,OAAO,EAAE,CAAC;YACb,OAAO,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QAC3D,CAAC;IACF,CAAC;IACD,OAAO,eAAe,CAAC;AAAA,CACvB;AAED,SAAS,UAAU,CAAC,GAAY,EAAY;IAC3C,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,CAAC;IACpB,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC7B,OAAO,GAAG;aACR,KAAK,CAAC,GAAG,CAAC;aACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;aACpB,MAAM,CAAC,OAAO,CAAC,CAAC;IACnB,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;QAAE,OAAO,GAAe,CAAC;IAC/C,OAAO,EAAE,CAAC;AAAA,CACV;AAED,MAAM,UAAU,aAAa,CAAC,QAAgB,EAAE,OAAe,EAAc;IAC5E,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IACjD,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACrC,MAAM,eAAe,GAAG,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;IAExF,MAAM,WAAW,GAAoB,EAAE,CAAC;IACxC,IAAI,IAAI,CAAC,WAAW,IAAI,OAAO,IAAI,CAAC,WAAW,KAAK,QAAQ;QAAE,WAAW,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC;IACzG,IAAI,IAAI,CAAC,QAAQ,IAAI,OAAO,IAAI,CAAC,QAAQ,KAAK,QAAQ;QACrD,WAAW,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAiD,CAAC;IAC/E,IAAI,IAAI,CAAC,KAAK;QAAE,WAAW,CAAC,KAAK,GAAG,KAAK,CAAC;IAC1C,IAAI,IAAI,CAAC,YAAY;QACpB,WAAW,CAAC,YAAY;YACvB,OAAO,IAAI,CAAC,YAAY,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,CAAE,IAAI,CAAC,YAAyB,CAAC;IAChG,IAAI,IAAI,CAAC,SAAS,IAAI,OAAO,IAAI,CAAC,SAAS,KAAK,QAAQ;QAAE,WAAW,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;IACjG,IAAI,IAAI,CAAC,aAAa,KAAK,SAAS;QACnC,WAAW,CAAC,aAAa,GAAG,IAAI,CAAC,aAAa,KAAK,MAAM,IAAI,IAAI,CAAC,aAAa,KAAK,IAAI,CAAC;IAC1F,IAAI,IAAI,CAAC,YAAY,KAAK,SAAS;QAClC,WAAW,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,KAAK,MAAM,IAAI,IAAI,CAAC,YAAY,KAAK,IAAI,CAAC;IAEvF,OAAO;QACN,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC;QACpC,QAAQ;QACR,KAAK,EAAE,YAAY,CAAC,IAAI,CAAC;QACzB,OAAO,EAAE,IAAI,CAAC,IAAI,EAAE;QACpB,KAAK,EAAE,SAAS;QAChB,MAAM,EAAE,EAAE;QACV,WAAW;QACX,eAAe;KACf,CAAC;AAAA,CACF;AAED,SAAS,OAAO,CAAC,GAAW,EAAE,KAAK,GAAa,EAAE,EAAY;IAC7D,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IACtC,MAAM,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7D,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QAC5C,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YACzB,OAAO,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QAC1B,CAAC;aAAM,IAAI,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;YAC1F,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACtB,CAAC;IACF,CAAC;IACD,OAAO,KAAK,CAAC;AAAA,CACb;AAED,MAAM,UAAU,SAAS,CAAC,QAAgB,EAAa;IACtD,MAAM,KAAK,GAAiB,EAAE,CAAC;IAC/B,MAAM,aAAa,GAAiB,EAAE,CAAC;IACvC,MAAM,WAAW,GAAiB,EAAE,CAAC;IAErC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC9B,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;IACpE,CAAC;IAED,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAEhC,KAAK,MAAM,QAAQ,IAAI,KAAK,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACnD,MAAM,IAAI,GAAG,aAAa,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC9C,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjB,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YAC1B,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1B,CAAC;aAAM,CAAC;YACP,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxB,CAAC;IACF,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;AAAA,CACnE","sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { ParsedRule, RuleCache, RuleFrontmatter } from \"./types.js\";\n\nfunction splitComma(val: string): string[] {\n\tconst result: string[] = [];\n\tlet depth = 0;\n\tlet current = \"\";\n\tfor (const ch of val) {\n\t\tif (ch === \"{\" || ch === \"(\" || ch === \"[\") depth++;\n\t\telse if (ch === \"}\" || ch === \")\" || ch === \"]\") depth--;\n\n\t\tif (ch === \",\" && depth === 0) {\n\t\t\tconst trimmed = current.trim().replace(/^[\"']|[\"']$/g, \"\");\n\t\t\tif (trimmed) result.push(trimmed);\n\t\t\tcurrent = \"\";\n\t\t} else {\n\t\t\tcurrent += ch;\n\t\t}\n\t}\n\tconst trimmed = current.trim().replace(/^[\"']|[\"']$/g, \"\");\n\tif (trimmed) result.push(trimmed);\n\treturn result;\n}\n\nexport function parseFrontmatter(content: string): { data: Record<string, unknown>; body: string } {\n\tconst frontmatterRegex = /^---\\r?\\n([\\s\\S]*?)\\n?---\\r?\\n([\\s\\S]*)$/;\n\tconst match = content.match(frontmatterRegex);\n\n\tif (!match) {\n\t\treturn { data: {}, body: content };\n\t}\n\n\tconst [, frontmatterStr, body] = match;\n\tconst data: Record<string, unknown> = {};\n\n\tconst lines = frontmatterStr.split(\"\\n\");\n\tlet i = 0;\n\twhile (i < lines.length) {\n\t\tconst line = lines[i];\n\t\tconst colonIndex = line.indexOf(\":\");\n\t\tif (colonIndex === -1) {\n\t\t\ti++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst rawKey = line.slice(0, colonIndex).trim();\n\t\tlet value: string | string[] | null = line.slice(colonIndex + 1).trim();\n\n\t\tif (value === \"\" || value === \"null\" || value === \"undefined\") {\n\t\t\tconst listItems: string[] = [];\n\t\t\tlet j = i + 1;\n\t\t\twhile (j < lines.length) {\n\t\t\t\tconst subLine = lines[j];\n\t\t\t\tif (subLine.match(/^\\s*-\\s+/)) {\n\t\t\t\t\tlistItems.push(\n\t\t\t\t\t\tsubLine\n\t\t\t\t\t\t\t.replace(/^\\s*-\\s+/, \"\")\n\t\t\t\t\t\t\t.trim()\n\t\t\t\t\t\t\t.replace(/^[\"']|[\"']$/g, \"\"),\n\t\t\t\t\t);\n\t\t\t\t\tj++;\n\t\t\t\t} else if (subLine.trim() === \"\" || subLine.match(/^\\s+/)) {\n\t\t\t\t\tj++;\n\t\t\t\t} else {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (listItems.length > 0) {\n\t\t\t\tconst camelKey = rawKey.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());\n\t\t\t\tdata[camelKey] = listItems;\n\t\t\t\ti = j;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tvalue = null;\n\t\t} else if (value.startsWith(\"[\") && value.endsWith(\"]\")) {\n\t\t\ttry {\n\t\t\t\tvalue = JSON.parse(value.replace(/'/g, '\"'));\n\t\t\t} catch {\n\t\t\t\tvalue = (value as string)\n\t\t\t\t\t.slice(1, -1)\n\t\t\t\t\t.split(\",\")\n\t\t\t\t\t.map((v: string) => v.trim().replace(/^[\"']|[\"']$/g, \"\"));\n\t\t\t}\n\t\t} else if (value.startsWith('\"') && value.endsWith('\"')) {\n\t\t\tvalue = value.slice(1, -1);\n\t\t} else if (value.startsWith(\"'\") && value.endsWith(\"'\")) {\n\t\t\tvalue = value.slice(1, -1);\n\t\t}\n\n\t\tconst camelKey = rawKey.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());\n\n\t\tif (camelKey === \"paths\" && typeof value === \"string\") {\n\t\t\tdata[camelKey] = splitComma(value);\n\t\t} else {\n\t\t\tdata[camelKey] = value;\n\t\t}\n\t\ti++;\n\t}\n\n\treturn { data, body: body.trim() };\n}\n\nfunction extractTitle(body: string): string {\n\tfor (const line of body.split(\"\\n\")) {\n\t\tconst trimmed = line.trim();\n\t\tif (trimmed) {\n\t\t\treturn trimmed.replace(/^#+\\s*/, \"\").replace(/\\*\\*/g, \"\");\n\t\t}\n\t}\n\treturn \"Untitled Rule\";\n}\n\nfunction parsePaths(raw: unknown): string[] {\n\tif (!raw) return [];\n\tif (typeof raw === \"string\") {\n\t\treturn raw\n\t\t\t.split(\",\")\n\t\t\t.map((g) => g.trim())\n\t\t\t.filter(Boolean);\n\t}\n\tif (Array.isArray(raw)) return raw as string[];\n\treturn [];\n}\n\nexport function parseRuleFile(filePath: string, content: string): ParsedRule {\n\tconst { data, body } = parseFrontmatter(content);\n\tconst paths = parsePaths(data.paths);\n\tconst isUnconditional = paths.length === 0 || (paths.length === 1 && paths[0] === \"**\");\n\n\tconst frontmatter: RuleFrontmatter = {};\n\tif (data.description && typeof data.description === \"string\") frontmatter.description = data.description;\n\tif (data.severity && typeof data.severity === \"string\")\n\t\tfrontmatter.severity = data.severity as ParsedRule[\"frontmatter\"][\"severity\"];\n\tif (data.paths) frontmatter.paths = paths;\n\tif (data.allowedTools)\n\t\tfrontmatter.allowedTools =\n\t\t\ttypeof data.allowedTools === \"string\" ? [data.allowedTools] : (data.allowedTools as string[]);\n\tif (data.whenToUse && typeof data.whenToUse === \"string\") frontmatter.whenToUse = data.whenToUse;\n\tif (data.notifyOnMatch !== undefined)\n\t\tfrontmatter.notifyOnMatch = data.notifyOnMatch === \"true\" || data.notifyOnMatch === true;\n\tif (data.skipInPrompt !== undefined)\n\t\tfrontmatter.skipInPrompt = data.skipInPrompt === \"true\" || data.skipInPrompt === true;\n\n\treturn {\n\t\tname: path.basename(filePath, \".md\"),\n\t\tfilePath,\n\t\ttitle: extractTitle(body),\n\t\tcontent: body.trim(),\n\t\tscope: \"project\",\n\t\tsource: \"\",\n\t\tfrontmatter,\n\t\tisUnconditional,\n\t};\n}\n\nfunction scanDir(dir: string, files: string[] = []): string[] {\n\tif (!fs.existsSync(dir)) return files;\n\tconst entries = fs.readdirSync(dir, { withFileTypes: true });\n\tfor (const entry of entries) {\n\t\tconst fullPath = path.join(dir, entry.name);\n\t\tif (entry.isDirectory()) {\n\t\t\tscanDir(fullPath, files);\n\t\t} else if (entry.isFile() && (entry.name.endsWith(\".md\") || entry.name.endsWith(\".mdc\"))) {\n\t\t\tfiles.push(fullPath);\n\t\t}\n\t}\n\treturn files;\n}\n\nexport function loadRules(rulesDir: string): RuleCache {\n\tconst rules: ParsedRule[] = [];\n\tconst unconditional: ParsedRule[] = [];\n\tconst conditional: ParsedRule[] = [];\n\n\tif (!fs.existsSync(rulesDir)) {\n\t\treturn { rules, unconditional, conditional, loadedAt: Date.now() };\n\t}\n\n\tconst files = scanDir(rulesDir);\n\n\tfor (const filePath of files) {\n\t\tconst content = fs.readFileSync(filePath, \"utf-8\");\n\t\tconst rule = parseRuleFile(filePath, content);\n\t\trules.push(rule);\n\t\tif (rule.isUnconditional) {\n\t\t\tunconditional.push(rule);\n\t\t} else {\n\t\t\tconditional.push(rule);\n\t\t}\n\t}\n\n\treturn { rules, unconditional, conditional, loadedAt: Date.now() };\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"matcher.d.ts","sourceRoot":"","sources":["../../src/rules-engine/matcher.ts"],"names":[],"mappings":"AAAA,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAoClE;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAIzE","sourcesContent":["export function matchGlob(pattern: string, target: string): boolean {\n\tif (!pattern || !target) return false;\n\n\tconst normalizedTarget = target.replace(/\\\\/g, \"/\");\n\tlet i = 0;\n\tlet re = \"\";\n\n\twhile (i < pattern.length) {\n\t\tconst ch = pattern[i];\n\t\tif (ch === \"*\") {\n\t\t\tif (pattern[i + 1] === \"*\") {\n\t\t\t\tre += \".*\";\n\t\t\t\ti += 2;\n\t\t\t\tif (pattern[i] === \"/\") i++;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tre += \"[^/]*\";\n\t\t} else if (ch === \"?\") {\n\t\t\tre += \"[^/]\";\n\t\t} else if (ch === \".\") {\n\t\t\tre += \"\\\\.\";\n\t\t} else if (ch === \"{\") {\n\t\t\tconst close = pattern.indexOf(\"}\", i);\n\t\t\tif (close !== -1) {\n\t\t\t\tre += `(${pattern.slice(i + 1, close).replace(/,/g, \"|\")})`;\n\t\t\t\ti = close;\n\t\t\t} else {\n\t\t\t\tre += ch;\n\t\t\t}\n\t\t} else {\n\t\t\tre += ch;\n\t\t}\n\t\ti++;\n\t}\n\n\treturn new RegExp(`^${re}$`).test(normalizedTarget);\n}\n\nexport function matchesAnyGlob(globs: string[], filePath: string): boolean {\n\tif (!filePath || globs.length === 0) return false;\n\tconst normalized = filePath.replace(/\\\\/g, \"/\");\n\treturn globs.some((g) => matchGlob(g, normalized));\n}\n"]}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export function matchGlob(pattern, target) {
|
|
2
|
+
if (!pattern || !target)
|
|
3
|
+
return false;
|
|
4
|
+
const normalizedTarget = target.replace(/\\/g, "/");
|
|
5
|
+
let i = 0;
|
|
6
|
+
let re = "";
|
|
7
|
+
while (i < pattern.length) {
|
|
8
|
+
const ch = pattern[i];
|
|
9
|
+
if (ch === "*") {
|
|
10
|
+
if (pattern[i + 1] === "*") {
|
|
11
|
+
re += ".*";
|
|
12
|
+
i += 2;
|
|
13
|
+
if (pattern[i] === "/")
|
|
14
|
+
i++;
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
re += "[^/]*";
|
|
18
|
+
}
|
|
19
|
+
else if (ch === "?") {
|
|
20
|
+
re += "[^/]";
|
|
21
|
+
}
|
|
22
|
+
else if (ch === ".") {
|
|
23
|
+
re += "\\.";
|
|
24
|
+
}
|
|
25
|
+
else if (ch === "{") {
|
|
26
|
+
const close = pattern.indexOf("}", i);
|
|
27
|
+
if (close !== -1) {
|
|
28
|
+
re += `(${pattern.slice(i + 1, close).replace(/,/g, "|")})`;
|
|
29
|
+
i = close;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
re += ch;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
re += ch;
|
|
37
|
+
}
|
|
38
|
+
i++;
|
|
39
|
+
}
|
|
40
|
+
return new RegExp(`^${re}$`).test(normalizedTarget);
|
|
41
|
+
}
|
|
42
|
+
export function matchesAnyGlob(globs, filePath) {
|
|
43
|
+
if (!filePath || globs.length === 0)
|
|
44
|
+
return false;
|
|
45
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
46
|
+
return globs.some((g) => matchGlob(g, normalized));
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=matcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"matcher.js","sourceRoot":"","sources":["../../src/rules-engine/matcher.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,SAAS,CAAC,OAAe,EAAE,MAAc,EAAW;IACnE,IAAI,CAAC,OAAO,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAEtC,MAAM,gBAAgB,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IACpD,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,IAAI,EAAE,GAAG,EAAE,CAAC;IAEZ,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;QAC3B,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;QACtB,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YAChB,IAAI,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;gBAC5B,EAAE,IAAI,IAAI,CAAC;gBACX,CAAC,IAAI,CAAC,CAAC;gBACP,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,GAAG;oBAAE,CAAC,EAAE,CAAC;gBAC5B,SAAS;YACV,CAAC;YACD,EAAE,IAAI,OAAO,CAAC;QACf,CAAC;aAAM,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YACvB,EAAE,IAAI,MAAM,CAAC;QACd,CAAC;aAAM,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YACvB,EAAE,IAAI,KAAK,CAAC;QACb,CAAC;aAAM,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YACvB,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;YACtC,IAAI,KAAK,KAAK,CAAC,CAAC,EAAE,CAAC;gBAClB,EAAE,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,GAAG,CAAC;gBAC5D,CAAC,GAAG,KAAK,CAAC;YACX,CAAC;iBAAM,CAAC;gBACP,EAAE,IAAI,EAAE,CAAC;YACV,CAAC;QACF,CAAC;aAAM,CAAC;YACP,EAAE,IAAI,EAAE,CAAC;QACV,CAAC;QACD,CAAC,EAAE,CAAC;IACL,CAAC;IAED,OAAO,IAAI,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;AAAA,CACpD;AAED,MAAM,UAAU,cAAc,CAAC,KAAe,EAAE,QAAgB,EAAW;IAC1E,IAAI,CAAC,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAClD,MAAM,UAAU,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAChD,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC;AAAA,CACnD","sourcesContent":["export function matchGlob(pattern: string, target: string): boolean {\n\tif (!pattern || !target) return false;\n\n\tconst normalizedTarget = target.replace(/\\\\/g, \"/\");\n\tlet i = 0;\n\tlet re = \"\";\n\n\twhile (i < pattern.length) {\n\t\tconst ch = pattern[i];\n\t\tif (ch === \"*\") {\n\t\t\tif (pattern[i + 1] === \"*\") {\n\t\t\t\tre += \".*\";\n\t\t\t\ti += 2;\n\t\t\t\tif (pattern[i] === \"/\") i++;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tre += \"[^/]*\";\n\t\t} else if (ch === \"?\") {\n\t\t\tre += \"[^/]\";\n\t\t} else if (ch === \".\") {\n\t\t\tre += \"\\\\.\";\n\t\t} else if (ch === \"{\") {\n\t\t\tconst close = pattern.indexOf(\"}\", i);\n\t\t\tif (close !== -1) {\n\t\t\t\tre += `(${pattern.slice(i + 1, close).replace(/,/g, \"|\")})`;\n\t\t\t\ti = close;\n\t\t\t} else {\n\t\t\t\tre += ch;\n\t\t\t}\n\t\t} else {\n\t\t\tre += ch;\n\t\t}\n\t\ti++;\n\t}\n\n\treturn new RegExp(`^${re}$`).test(normalizedTarget);\n}\n\nexport function matchesAnyGlob(globs: string[], filePath: string): boolean {\n\tif (!filePath || globs.length === 0) return false;\n\tconst normalized = filePath.replace(/\\\\/g, \"/\");\n\treturn globs.some((g) => matchGlob(g, normalized));\n}\n"]}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
export type RuleSeverity = "critical" | "high" | "medium" | "low" | "hint";
|
|
2
|
+
export type RuleScope = "user" | "pi" | "project" | "managed";
|
|
3
|
+
export interface RuleFrontmatter {
|
|
4
|
+
paths?: string[];
|
|
5
|
+
description?: string;
|
|
6
|
+
severity?: RuleSeverity;
|
|
7
|
+
allowedTools?: string[];
|
|
8
|
+
whenToUse?: string;
|
|
9
|
+
version?: string;
|
|
10
|
+
model?: string;
|
|
11
|
+
skills?: string;
|
|
12
|
+
effort?: string;
|
|
13
|
+
userInvocable?: string;
|
|
14
|
+
context?: "inline" | "fork";
|
|
15
|
+
agent?: string;
|
|
16
|
+
shell?: string;
|
|
17
|
+
notifyOnMatch?: boolean;
|
|
18
|
+
skipInPrompt?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface ParsedRule {
|
|
21
|
+
name: string;
|
|
22
|
+
filePath: string;
|
|
23
|
+
title: string;
|
|
24
|
+
content: string;
|
|
25
|
+
scope: RuleScope;
|
|
26
|
+
source: string;
|
|
27
|
+
frontmatter: RuleFrontmatter;
|
|
28
|
+
isUnconditional: boolean;
|
|
29
|
+
}
|
|
30
|
+
export interface RuleCache {
|
|
31
|
+
rules: ParsedRule[];
|
|
32
|
+
unconditional: ParsedRule[];
|
|
33
|
+
conditional: ParsedRule[];
|
|
34
|
+
loadedAt: number;
|
|
35
|
+
}
|
|
36
|
+
export interface CachedRules {
|
|
37
|
+
rules: ParsedRule[];
|
|
38
|
+
loadedAt: number;
|
|
39
|
+
}
|
|
40
|
+
export interface RulesConfig {
|
|
41
|
+
cacheTTL: number;
|
|
42
|
+
notifyOnLoad: boolean;
|
|
43
|
+
notifyOnMatch: boolean;
|
|
44
|
+
dirs?: {
|
|
45
|
+
user?: string[];
|
|
46
|
+
pi?: string[];
|
|
47
|
+
project?: string[];
|
|
48
|
+
managed?: string[];
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export interface RuleDetail {
|
|
52
|
+
name: string;
|
|
53
|
+
title: string;
|
|
54
|
+
filePath: string;
|
|
55
|
+
scope: RuleScope;
|
|
56
|
+
source: string;
|
|
57
|
+
severity: RuleSeverity;
|
|
58
|
+
isUnconditional: boolean;
|
|
59
|
+
paths: string[];
|
|
60
|
+
description?: string;
|
|
61
|
+
}
|
|
62
|
+
export interface ScannedDir {
|
|
63
|
+
dir: string;
|
|
64
|
+
fileCount: number;
|
|
65
|
+
ruleNames: string[];
|
|
66
|
+
}
|
|
67
|
+
export interface MatchedRuleDetail {
|
|
68
|
+
name: string;
|
|
69
|
+
title: string;
|
|
70
|
+
severity: RuleSeverity;
|
|
71
|
+
matchedGlob: string;
|
|
72
|
+
}
|
|
73
|
+
export interface MatchRecord {
|
|
74
|
+
filePath: string;
|
|
75
|
+
ruleNames: string[];
|
|
76
|
+
toolName: string;
|
|
77
|
+
toolCallId: string;
|
|
78
|
+
severity: "info" | "warning";
|
|
79
|
+
timestamp: number;
|
|
80
|
+
matchedRuleDetails?: MatchedRuleDetail[];
|
|
81
|
+
}
|
|
82
|
+
export interface LifecycleEntry {
|
|
83
|
+
event: "loaded" | "restored" | "injected" | "reloaded" | "unloaded" | "expired";
|
|
84
|
+
message: string;
|
|
85
|
+
ruleCount?: number;
|
|
86
|
+
timestamp: number;
|
|
87
|
+
details?: {
|
|
88
|
+
scannedDirs?: ScannedDir[];
|
|
89
|
+
configSource?: string;
|
|
90
|
+
cacheHit?: boolean;
|
|
91
|
+
injectedRules?: Array<{
|
|
92
|
+
name: string;
|
|
93
|
+
promptDelta: number;
|
|
94
|
+
}>;
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
export interface SnapshotPayload {
|
|
98
|
+
type: "snapshot";
|
|
99
|
+
rules: RuleDetail[];
|
|
100
|
+
injectedRuleNames: string[];
|
|
101
|
+
totalRules: number;
|
|
102
|
+
unconditionalCount: number;
|
|
103
|
+
conditionalCount: number;
|
|
104
|
+
matchHistory: MatchRecord[];
|
|
105
|
+
lifecycleLog: LifecycleEntry[];
|
|
106
|
+
loadedAt: number;
|
|
107
|
+
cacheTTL: number;
|
|
108
|
+
}
|
|
109
|
+
export interface MatchedPayload {
|
|
110
|
+
type: "matched";
|
|
111
|
+
filePath: string;
|
|
112
|
+
matchedRules: MatchedRuleDetail[];
|
|
113
|
+
toolName: string;
|
|
114
|
+
toolCallId: string;
|
|
115
|
+
severity: "info" | "warning";
|
|
116
|
+
timestamp: number;
|
|
117
|
+
}
|
|
118
|
+
export interface InjectedPayload {
|
|
119
|
+
type: "injected";
|
|
120
|
+
ruleNames: string[];
|
|
121
|
+
systemPromptLength: number;
|
|
122
|
+
}
|
|
123
|
+
export interface ReloadedPayload {
|
|
124
|
+
type: "reloaded";
|
|
125
|
+
rules: RuleDetail[];
|
|
126
|
+
loadedAt: number;
|
|
127
|
+
}
|
|
128
|
+
export interface UnloadedPayload {
|
|
129
|
+
type: "unloaded";
|
|
130
|
+
reason: string;
|
|
131
|
+
}
|
|
132
|
+
export type RulesChannelEvent = SnapshotPayload | MatchedPayload | InjectedPayload | ReloadedPayload | UnloadedPayload;
|
|
133
|
+
export interface RulesChannelContract {
|
|
134
|
+
methods: {
|
|
135
|
+
getSnapshot: {
|
|
136
|
+
params: {
|
|
137
|
+
cwd?: string;
|
|
138
|
+
};
|
|
139
|
+
return: SnapshotPayload;
|
|
140
|
+
};
|
|
141
|
+
};
|
|
142
|
+
events: {
|
|
143
|
+
snapshot: SnapshotPayload;
|
|
144
|
+
matched: MatchedPayload;
|
|
145
|
+
injected: InjectedPayload;
|
|
146
|
+
reloaded: ReloadedPayload;
|
|
147
|
+
unloaded: UnloadedPayload;
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/rules-engine/types.ts"],"names":[],"mappings":"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","sourcesContent":["export type RuleSeverity = \"critical\" | \"high\" | \"medium\" | \"low\" | \"hint\";\n\nexport type RuleScope = \"user\" | \"pi\" | \"project\" | \"managed\";\n\nexport interface RuleFrontmatter {\n\tpaths?: string[];\n\tdescription?: string;\n\tseverity?: RuleSeverity;\n\tallowedTools?: string[];\n\twhenToUse?: string;\n\tversion?: string;\n\tmodel?: string;\n\tskills?: string;\n\teffort?: string;\n\tuserInvocable?: string;\n\tcontext?: \"inline\" | \"fork\";\n\tagent?: string;\n\tshell?: string;\n\tnotifyOnMatch?: boolean;\n\tskipInPrompt?: boolean;\n}\n\nexport interface ParsedRule {\n\tname: string;\n\tfilePath: string;\n\ttitle: string;\n\tcontent: string;\n\tscope: RuleScope;\n\tsource: string;\n\tfrontmatter: RuleFrontmatter;\n\tisUnconditional: boolean;\n}\n\nexport interface RuleCache {\n\trules: ParsedRule[];\n\tunconditional: ParsedRule[];\n\tconditional: ParsedRule[];\n\tloadedAt: number;\n}\n\nexport interface CachedRules {\n\trules: ParsedRule[];\n\tloadedAt: number;\n}\n\nexport interface RulesConfig {\n\tcacheTTL: number;\n\tnotifyOnLoad: boolean;\n\tnotifyOnMatch: boolean;\n\tdirs?: {\n\t\tuser?: string[];\n\t\tpi?: string[];\n\t\tproject?: string[];\n\t\tmanaged?: string[];\n\t};\n}\n\nexport interface RuleDetail {\n\tname: string;\n\ttitle: string;\n\tfilePath: string;\n\tscope: RuleScope;\n\tsource: string;\n\tseverity: RuleSeverity;\n\tisUnconditional: boolean;\n\tpaths: string[];\n\tdescription?: string;\n}\n\nexport interface ScannedDir {\n\tdir: string;\n\tfileCount: number;\n\truleNames: string[];\n}\n\nexport interface MatchedRuleDetail {\n\tname: string;\n\ttitle: string;\n\tseverity: RuleSeverity;\n\tmatchedGlob: string;\n}\n\nexport interface MatchRecord {\n\tfilePath: string;\n\truleNames: string[];\n\ttoolName: string;\n\ttoolCallId: string;\n\tseverity: \"info\" | \"warning\";\n\ttimestamp: number;\n\tmatchedRuleDetails?: MatchedRuleDetail[];\n}\n\nexport interface LifecycleEntry {\n\tevent: \"loaded\" | \"restored\" | \"injected\" | \"reloaded\" | \"unloaded\" | \"expired\";\n\tmessage: string;\n\truleCount?: number;\n\ttimestamp: number;\n\tdetails?: {\n\t\tscannedDirs?: ScannedDir[];\n\t\tconfigSource?: string;\n\t\tcacheHit?: boolean;\n\t\tinjectedRules?: Array<{ name: string; promptDelta: number }>;\n\t};\n}\n\nexport interface SnapshotPayload {\n\ttype: \"snapshot\";\n\trules: RuleDetail[];\n\tinjectedRuleNames: string[];\n\ttotalRules: number;\n\tunconditionalCount: number;\n\tconditionalCount: number;\n\tmatchHistory: MatchRecord[];\n\tlifecycleLog: LifecycleEntry[];\n\tloadedAt: number;\n\tcacheTTL: number;\n}\n\nexport interface MatchedPayload {\n\ttype: \"matched\";\n\tfilePath: string;\n\tmatchedRules: MatchedRuleDetail[];\n\ttoolName: string;\n\ttoolCallId: string;\n\tseverity: \"info\" | \"warning\";\n\ttimestamp: number;\n}\n\nexport interface InjectedPayload {\n\ttype: \"injected\";\n\truleNames: string[];\n\tsystemPromptLength: number;\n}\n\nexport interface ReloadedPayload {\n\ttype: \"reloaded\";\n\trules: RuleDetail[];\n\tloadedAt: number;\n}\n\nexport interface UnloadedPayload {\n\ttype: \"unloaded\";\n\treason: string;\n}\n\nexport type RulesChannelEvent = SnapshotPayload | MatchedPayload | InjectedPayload | ReloadedPayload | UnloadedPayload;\n\nexport interface RulesChannelContract {\n\tmethods: {\n\t\tgetSnapshot: {\n\t\t\tparams: { cwd?: string };\n\t\t\treturn: SnapshotPayload;\n\t\t};\n\t};\n\tevents: {\n\t\tsnapshot: SnapshotPayload;\n\t\tmatched: MatchedPayload;\n\t\tinjected: InjectedPayload;\n\t\treloaded: ReloadedPayload;\n\t\tunloaded: UnloadedPayload;\n\t};\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/rules-engine/types.ts"],"names":[],"mappings":"","sourcesContent":["export type RuleSeverity = \"critical\" | \"high\" | \"medium\" | \"low\" | \"hint\";\n\nexport type RuleScope = \"user\" | \"pi\" | \"project\" | \"managed\";\n\nexport interface RuleFrontmatter {\n\tpaths?: string[];\n\tdescription?: string;\n\tseverity?: RuleSeverity;\n\tallowedTools?: string[];\n\twhenToUse?: string;\n\tversion?: string;\n\tmodel?: string;\n\tskills?: string;\n\teffort?: string;\n\tuserInvocable?: string;\n\tcontext?: \"inline\" | \"fork\";\n\tagent?: string;\n\tshell?: string;\n\tnotifyOnMatch?: boolean;\n\tskipInPrompt?: boolean;\n}\n\nexport interface ParsedRule {\n\tname: string;\n\tfilePath: string;\n\ttitle: string;\n\tcontent: string;\n\tscope: RuleScope;\n\tsource: string;\n\tfrontmatter: RuleFrontmatter;\n\tisUnconditional: boolean;\n}\n\nexport interface RuleCache {\n\trules: ParsedRule[];\n\tunconditional: ParsedRule[];\n\tconditional: ParsedRule[];\n\tloadedAt: number;\n}\n\nexport interface CachedRules {\n\trules: ParsedRule[];\n\tloadedAt: number;\n}\n\nexport interface RulesConfig {\n\tcacheTTL: number;\n\tnotifyOnLoad: boolean;\n\tnotifyOnMatch: boolean;\n\tdirs?: {\n\t\tuser?: string[];\n\t\tpi?: string[];\n\t\tproject?: string[];\n\t\tmanaged?: string[];\n\t};\n}\n\nexport interface RuleDetail {\n\tname: string;\n\ttitle: string;\n\tfilePath: string;\n\tscope: RuleScope;\n\tsource: string;\n\tseverity: RuleSeverity;\n\tisUnconditional: boolean;\n\tpaths: string[];\n\tdescription?: string;\n}\n\nexport interface ScannedDir {\n\tdir: string;\n\tfileCount: number;\n\truleNames: string[];\n}\n\nexport interface MatchedRuleDetail {\n\tname: string;\n\ttitle: string;\n\tseverity: RuleSeverity;\n\tmatchedGlob: string;\n}\n\nexport interface MatchRecord {\n\tfilePath: string;\n\truleNames: string[];\n\ttoolName: string;\n\ttoolCallId: string;\n\tseverity: \"info\" | \"warning\";\n\ttimestamp: number;\n\tmatchedRuleDetails?: MatchedRuleDetail[];\n}\n\nexport interface LifecycleEntry {\n\tevent: \"loaded\" | \"restored\" | \"injected\" | \"reloaded\" | \"unloaded\" | \"expired\";\n\tmessage: string;\n\truleCount?: number;\n\ttimestamp: number;\n\tdetails?: {\n\t\tscannedDirs?: ScannedDir[];\n\t\tconfigSource?: string;\n\t\tcacheHit?: boolean;\n\t\tinjectedRules?: Array<{ name: string; promptDelta: number }>;\n\t};\n}\n\nexport interface SnapshotPayload {\n\ttype: \"snapshot\";\n\trules: RuleDetail[];\n\tinjectedRuleNames: string[];\n\ttotalRules: number;\n\tunconditionalCount: number;\n\tconditionalCount: number;\n\tmatchHistory: MatchRecord[];\n\tlifecycleLog: LifecycleEntry[];\n\tloadedAt: number;\n\tcacheTTL: number;\n}\n\nexport interface MatchedPayload {\n\ttype: \"matched\";\n\tfilePath: string;\n\tmatchedRules: MatchedRuleDetail[];\n\ttoolName: string;\n\ttoolCallId: string;\n\tseverity: \"info\" | \"warning\";\n\ttimestamp: number;\n}\n\nexport interface InjectedPayload {\n\ttype: \"injected\";\n\truleNames: string[];\n\tsystemPromptLength: number;\n}\n\nexport interface ReloadedPayload {\n\ttype: \"reloaded\";\n\trules: RuleDetail[];\n\tloadedAt: number;\n}\n\nexport interface UnloadedPayload {\n\ttype: \"unloaded\";\n\treason: string;\n}\n\nexport type RulesChannelEvent = SnapshotPayload | MatchedPayload | InjectedPayload | ReloadedPayload | UnloadedPayload;\n\nexport interface RulesChannelContract {\n\tmethods: {\n\t\tgetSnapshot: {\n\t\t\tparams: { cwd?: string };\n\t\t\treturn: SnapshotPayload;\n\t\t};\n\t};\n\tevents: {\n\t\tsnapshot: SnapshotPayload;\n\t\tmatched: MatchedPayload;\n\t\tinjected: InjectedPayload;\n\t\treloaded: ReloadedPayload;\n\t\tunloaded: UnloadedPayload;\n\t};\n}\n"]}
|
package/docs/extensions.md
CHANGED
|
@@ -832,89 +832,89 @@ Transforms chain across handlers. See [input-transform.ts](../examples/extension
|
|
|
832
832
|
|
|
833
833
|
### UI Interception Events
|
|
834
834
|
|
|
835
|
-
Intercept `ctx.ui.confirm()`, `ctx.ui.select()`, and `ctx.ui.input()` calls from **any extension** or the agent itself. This allows a single extension to
|
|
835
|
+
Intercept `ctx.ui.confirm()`, `ctx.ui.select()`, and `ctx.ui.input()` calls from **any extension** or the agent itself. This allows a single extension to observe and respond to all UI dialogs -- for example, to forward permission requests to a remote service and wait for a response.
|
|
836
836
|
|
|
837
837
|
Without UI interception, each extension calls `ctx.ui.confirm()` independently and the results are invisible to other extensions. With UI interception, one extension can observe and respond to all UI dialogs on behalf of the user.
|
|
838
838
|
|
|
839
|
-
####
|
|
839
|
+
#### ui
|
|
840
840
|
|
|
841
|
-
Fired when any code calls `ctx.ui.confirm()
|
|
841
|
+
Fired when any code calls `ctx.ui.confirm()`, `ctx.ui.select()`, or `ctx.ui.input()`. The `method` field distinguishes which UI call triggered the event.
|
|
842
842
|
|
|
843
843
|
```typescript
|
|
844
|
-
pi.on("
|
|
845
|
-
// event.
|
|
846
|
-
// event.
|
|
847
|
-
// event.
|
|
848
|
-
// event.
|
|
844
|
+
pi.on("ui", async (event, ctx) => {
|
|
845
|
+
// event.id - unique identifier for this UI request
|
|
846
|
+
// event.method - "confirm" | "select" | "input" | "notify"
|
|
847
|
+
// event.title - dialog title (for notify, this is the message)
|
|
848
|
+
// event.message - dialog message (confirm only)
|
|
849
|
+
// event.options - string[] of options (select only)
|
|
850
|
+
// event.placeholder - placeholder text (input only)
|
|
851
|
+
// event.notifyType - "info" | "warning" | "error" (notify only)
|
|
852
|
+
// event.signal - AbortSignal if the caller provided one
|
|
853
|
+
// event.timeout - timeout in ms if the caller provided one
|
|
849
854
|
|
|
850
|
-
// Example:
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
}
|
|
859
|
-
```
|
|
860
|
-
|
|
861
|
-
**Results:**
|
|
862
|
-
- `{ action: "responded", confirmed: boolean }` - answer the dialog immediately (first handler to respond wins)
|
|
863
|
-
- `undefined` - pass through to the original UI implementation
|
|
864
|
-
|
|
865
|
-
#### ui_select
|
|
866
|
-
|
|
867
|
-
Fired when any code calls `ctx.ui.select()`. Return `{ action: "responded", value }` to provide a selection without showing the UI.
|
|
868
|
-
|
|
869
|
-
```typescript
|
|
870
|
-
pi.on("ui_select", async (event, ctx) => {
|
|
871
|
-
// event.title - dialog title
|
|
872
|
-
// event.options - string[] of selectable options
|
|
873
|
-
// event.signal - AbortSignal if provided
|
|
874
|
-
// event.timeout - timeout in ms if provided
|
|
855
|
+
// Example: intercept all confirmations and forward to a remote service
|
|
856
|
+
if (event.method === "confirm") {
|
|
857
|
+
const response = await fetch("https://my-server/api/confirm", {
|
|
858
|
+
method: "POST",
|
|
859
|
+
body: JSON.stringify({ id: event.id, title: event.title, message: event.message }),
|
|
860
|
+
});
|
|
861
|
+
const decision = await response.json();
|
|
862
|
+
return { action: "responded", confirmed: decision.allowed };
|
|
863
|
+
}
|
|
875
864
|
|
|
876
|
-
// Example:
|
|
877
|
-
if (event.title.includes("Dangerous")) {
|
|
865
|
+
// Example: intercept select dialogs with a remote decision
|
|
866
|
+
if (event.method === "select" && event.title.includes("Dangerous")) {
|
|
878
867
|
const choice = await askRemote(event);
|
|
879
868
|
return { action: "responded", value: choice };
|
|
880
869
|
}
|
|
881
870
|
|
|
882
|
-
|
|
871
|
+
// Example: forward notifications to a remote service (return value is ignored for notify)
|
|
872
|
+
if (event.method === "notify") {
|
|
873
|
+
sendToRemote({ type: "notification", message: event.message, level: event.notifyType });
|
|
874
|
+
return undefined; // original notify still fires
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
return undefined; // pass through to the original UI
|
|
883
878
|
});
|
|
884
879
|
```
|
|
885
880
|
|
|
886
881
|
**Results:**
|
|
887
|
-
- `{ action: "responded",
|
|
888
|
-
- `undefined` -
|
|
882
|
+
- `{ action: "responded", confirmed: boolean }` - answer a confirm dialog (first handler to respond wins)
|
|
883
|
+
- `{ action: "responded", value: string | undefined }` - answer a select or input dialog (undefined = dismissed/cancelled)
|
|
884
|
+
- `undefined` - pass through to the original UI implementation
|
|
885
|
+
- For `notify`, the return value is ignored. The original `notify` always fires regardless.
|
|
889
886
|
|
|
890
|
-
####
|
|
887
|
+
#### Short-circuit behavior
|
|
891
888
|
|
|
892
|
-
|
|
889
|
+
The `ui` event uses first-responder semantics: the first handler to return `{ action: "responded" }` wins. Remaining handlers are not called. If no handler responds, the original UI implementation runs (TUI dialog, RPC protocol, or no-op depending on the mode).
|
|
893
890
|
|
|
894
|
-
|
|
895
|
-
pi.on("ui_input", async (event, ctx) => {
|
|
896
|
-
// event.title - dialog title
|
|
897
|
-
// event.placeholder - placeholder text if provided
|
|
898
|
-
// event.signal - AbortSignal if provided
|
|
899
|
-
// event.timeout - timeout in ms if provided
|
|
891
|
+
If a handler throws, the error is reported via the error listener and the next handler is tried. If all handlers fail, the original UI runs as fallback.
|
|
900
892
|
|
|
901
|
-
|
|
902
|
-
});
|
|
903
|
-
```
|
|
893
|
+
#### ctx.respondUI(id, result) -- async response injection
|
|
904
894
|
|
|
905
|
-
**
|
|
906
|
-
- `{ action: "responded", value: string | undefined }` - provide the input (undefined = cancelled)
|
|
907
|
-
- `undefined` - pass through to the original UI
|
|
895
|
+
When a handler returns `undefined`, the original UI is invoked -- but the handler can also capture the `event.id` and call `ctx.respondUI(id, result)` later to inject a response asynchronously. This creates a **race** between the original UI and the async response: first one wins, the other is ignored.
|
|
908
896
|
|
|
909
|
-
|
|
897
|
+
```typescript
|
|
898
|
+
pi.on("ui", async (event, ctx) => {
|
|
899
|
+
if (event.method === "confirm") {
|
|
900
|
+
// Forward to remote service (fire-and-forget)
|
|
901
|
+
sendToRemote({ id: event.id, message: event.message });
|
|
902
|
+
// Return undefined to let original UI also show
|
|
903
|
+
return undefined;
|
|
904
|
+
}
|
|
905
|
+
});
|
|
910
906
|
|
|
911
|
-
|
|
907
|
+
// When remote responds (e.g. via channel, webhook, etc.):
|
|
908
|
+
someChannel.onReceive((response) => {
|
|
909
|
+
ctx.respondUI(response.id, { action: "responded", confirmed: response.allowed });
|
|
910
|
+
});
|
|
911
|
+
```
|
|
912
912
|
|
|
913
|
-
If
|
|
913
|
+
If the user responds from the TUI first, the `respondUI` call is ignored. If the remote service responds first, the TUI dialog is cancelled. First response wins.
|
|
914
914
|
|
|
915
915
|
#### Use cases
|
|
916
916
|
|
|
917
|
-
- **Remote permission control** -- forward all `confirm`/`select` dialogs to a web dashboard or mobile app,
|
|
917
|
+
- **Remote permission control** -- forward all `confirm`/`select` dialogs to a web dashboard or mobile app, race with the local UI
|
|
918
918
|
- **Audit logging** -- record all UI interactions without intercepting them (return `undefined` after logging)
|
|
919
919
|
- **Headless automation** -- auto-approve or auto-deny dialogs based on rules, enabling unattended operation
|
|
920
920
|
- **Custom approval workflows** -- require multiple approvers, time-based auto-approval, or integration with ticketing systems
|