@ian-pascoe/pi-codemode 0.1.0
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/LICENSE +21 -0
- package/README.md +241 -0
- package/package.json +59 -0
- package/src/codemode-cell-transform.ts +612 -0
- package/src/codemode-deno-launch.ts +59 -0
- package/src/codemode-deno-process.ts +208 -0
- package/src/codemode-observer-ui.ts +517 -0
- package/src/codemode-presentation-output.ts +16 -0
- package/src/codemode-runtime.ts +24 -0
- package/src/codemode-session-coordinator.ts +1297 -0
- package/src/codemode-session-files.ts +80 -0
- package/src/codemode-tool-catalog.ts +350 -0
- package/src/codemode-tool-contract.ts +480 -0
- package/src/codemode-tool-exposure.ts +159 -0
- package/src/codemode-tool-rendering.ts +487 -0
- package/src/codemode-worker-protocol.ts +480 -0
- package/src/codemode-worker.ts +1092 -0
- package/src/index.ts +1 -0
- package/src/pi-agent-session-capture.ts +157 -0
- package/src/pi-codemode-extension.ts +469 -0
- package/src/pi-codemode-settings.ts +168 -0
- package/src/pi-tool-bridge.ts +744 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import type { SettingsManager } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Minimatch } from "minimatch";
|
|
3
|
+
import { Type } from "typebox";
|
|
4
|
+
import { Value } from "typebox/value";
|
|
5
|
+
|
|
6
|
+
const DEFAULT_MAX_SESSIONS = 8;
|
|
7
|
+
const EXPOSURE_MODES = ["codemode-only", "direct-and-codemode", "direct-only"] as const;
|
|
8
|
+
const JsonObjectSchema = Type.Record(Type.String(), Type.Any());
|
|
9
|
+
const NonEmptyStringSchema = Type.String({ minLength: 1 });
|
|
10
|
+
const PositiveSafeIntegerSchema = Type.Integer({ minimum: 1, maximum: Number.MAX_SAFE_INTEGER });
|
|
11
|
+
const SettingsDocumentSchema = Type.Object(
|
|
12
|
+
{ codemode: Type.Optional(Type.Any()) },
|
|
13
|
+
{ additionalProperties: true },
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
type ExposureMode = (typeof EXPOSURE_MODES)[number];
|
|
17
|
+
type JsonValue =
|
|
18
|
+
| null
|
|
19
|
+
| boolean
|
|
20
|
+
| number
|
|
21
|
+
| string
|
|
22
|
+
| readonly JsonValue[]
|
|
23
|
+
| { readonly [key: string]: JsonValue };
|
|
24
|
+
type PiSettingsDocument = ReturnType<SettingsManager["getGlobalSettings"]>;
|
|
25
|
+
type SettingsScope = "global" | "project";
|
|
26
|
+
type ParsedRuleResult =
|
|
27
|
+
| { readonly ok: true; readonly rule: CodeModeExposureRule }
|
|
28
|
+
| { readonly ok: false; readonly warning: string };
|
|
29
|
+
type ParsedLayerResult =
|
|
30
|
+
| {
|
|
31
|
+
readonly ok: true;
|
|
32
|
+
readonly maxSessions: number | undefined;
|
|
33
|
+
readonly rules: readonly CodeModeExposureRule[] | undefined;
|
|
34
|
+
}
|
|
35
|
+
| { readonly ok: false; readonly warning: string };
|
|
36
|
+
|
|
37
|
+
/** One configured rule that classifies exact registered Pi tool names. */
|
|
38
|
+
export interface CodeModeExposureRule {
|
|
39
|
+
readonly exposure: ExposureMode;
|
|
40
|
+
readonly pattern: string;
|
|
41
|
+
/** Reports whether this rule matches one case-sensitive registered tool name. */
|
|
42
|
+
matches(toolName: string): boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Pi's settings shape extended with the CodeMode-owned configuration key. */
|
|
46
|
+
export type CodeModeSettingsDocumentInput = PiSettingsDocument | { readonly codemode?: JsonValue };
|
|
47
|
+
|
|
48
|
+
/** Reads Pi's already trust-filtered global and project settings documents. */
|
|
49
|
+
export interface CodeModeSettingsReader {
|
|
50
|
+
getGlobalSettings(): CodeModeSettingsDocumentInput;
|
|
51
|
+
getProjectSettings(): CodeModeSettingsDocumentInput;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Resolved CodeMode settings, or the single boundary warning that disabled CodeMode. */
|
|
55
|
+
export type ResolvedCodeModeSettings =
|
|
56
|
+
| {
|
|
57
|
+
readonly enabled: true;
|
|
58
|
+
readonly maxSessions: number;
|
|
59
|
+
readonly rules: readonly CodeModeExposureRule[];
|
|
60
|
+
}
|
|
61
|
+
| { readonly enabled: false; readonly warning: string };
|
|
62
|
+
|
|
63
|
+
function isJsonObject(value: JsonValue): value is Readonly<Record<string, JsonValue>> {
|
|
64
|
+
return Value.Check(JsonObjectSchema, value);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function isExposureMode(value: JsonValue | undefined): value is ExposureMode {
|
|
68
|
+
return EXPOSURE_MODES.some((exposure) => exposure === value);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function parseRule(value: JsonValue, path: string): ParsedRuleResult {
|
|
72
|
+
if (!isJsonObject(value)) return { ok: false, warning: `${path}: expected an object` };
|
|
73
|
+
const unknownField = Object.keys(value).find(
|
|
74
|
+
(field) => field !== "pattern" && field !== "exposure",
|
|
75
|
+
);
|
|
76
|
+
if (unknownField !== undefined) {
|
|
77
|
+
return { ok: false, warning: `${path}.${unknownField}: unknown field` };
|
|
78
|
+
}
|
|
79
|
+
if (!Value.Check(NonEmptyStringSchema, value.pattern)) {
|
|
80
|
+
return { ok: false, warning: `${path}.pattern: expected a non-empty string` };
|
|
81
|
+
}
|
|
82
|
+
if (!isExposureMode(value.exposure)) {
|
|
83
|
+
return {
|
|
84
|
+
ok: false,
|
|
85
|
+
warning: `${path}.exposure: expected codemode-only, direct-and-codemode, or direct-only`,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let matcher: Minimatch;
|
|
90
|
+
try {
|
|
91
|
+
matcher = new Minimatch(value.pattern);
|
|
92
|
+
if (matcher.makeRe() === false) {
|
|
93
|
+
return { ok: false, warning: `${path}.pattern: invalid minimatch pattern` };
|
|
94
|
+
}
|
|
95
|
+
} catch {
|
|
96
|
+
return { ok: false, warning: `${path}.pattern: invalid minimatch pattern` };
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
ok: true,
|
|
100
|
+
rule: {
|
|
101
|
+
exposure: value.exposure,
|
|
102
|
+
pattern: value.pattern,
|
|
103
|
+
matches: (toolName) => matcher.match(toolName),
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function parseLayer(
|
|
109
|
+
settings: CodeModeSettingsDocumentInput,
|
|
110
|
+
scope: SettingsScope,
|
|
111
|
+
): ParsedLayerResult {
|
|
112
|
+
if (!Value.Check(SettingsDocumentSchema, settings)) {
|
|
113
|
+
return { ok: false, warning: `${scope} settings: expected an object` };
|
|
114
|
+
}
|
|
115
|
+
const codemode = "codemode" in settings ? settings.codemode : undefined;
|
|
116
|
+
if (codemode === undefined) {
|
|
117
|
+
return { ok: true, maxSessions: undefined, rules: undefined };
|
|
118
|
+
}
|
|
119
|
+
if (!isJsonObject(codemode)) {
|
|
120
|
+
return { ok: false, warning: `${scope} codemode: expected an object` };
|
|
121
|
+
}
|
|
122
|
+
const unknownField = Object.keys(codemode).find(
|
|
123
|
+
(field) => field !== "maxSessions" && field !== "tools",
|
|
124
|
+
);
|
|
125
|
+
if (unknownField !== undefined) {
|
|
126
|
+
return { ok: false, warning: `${scope} codemode.${unknownField}: unknown field` };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let maxSessions: number | undefined;
|
|
130
|
+
if (Object.hasOwn(codemode, "maxSessions")) {
|
|
131
|
+
if (!Value.Check(PositiveSafeIntegerSchema, codemode.maxSessions)) {
|
|
132
|
+
return {
|
|
133
|
+
ok: false,
|
|
134
|
+
warning: `${scope} codemode.maxSessions: expected a positive safe integer`,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
maxSessions = codemode.maxSessions;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
let rules: readonly CodeModeExposureRule[] | undefined;
|
|
141
|
+
if (Object.hasOwn(codemode, "tools")) {
|
|
142
|
+
if (!Array.isArray(codemode.tools)) {
|
|
143
|
+
return { ok: false, warning: `${scope} codemode.tools: expected an array` };
|
|
144
|
+
}
|
|
145
|
+
const parsedRules: CodeModeExposureRule[] = [];
|
|
146
|
+
for (const [index, value] of codemode.tools.entries()) {
|
|
147
|
+
const parsed = parseRule(value, `${scope} codemode.tools[${index}]`);
|
|
148
|
+
if (!parsed.ok) return parsed;
|
|
149
|
+
parsedRules.push(parsed.rule);
|
|
150
|
+
}
|
|
151
|
+
rules = parsedRules;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return { ok: true, maxSessions, rules };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Resolves independently trusted settings layers into CodeMode session and exposure policy. */
|
|
158
|
+
export function resolveCodeModeSettings(reader: CodeModeSettingsReader): ResolvedCodeModeSettings {
|
|
159
|
+
const globalLayer = parseLayer(reader.getGlobalSettings(), "global");
|
|
160
|
+
if (!globalLayer.ok) return { enabled: false, warning: globalLayer.warning };
|
|
161
|
+
const projectLayer = parseLayer(reader.getProjectSettings(), "project");
|
|
162
|
+
if (!projectLayer.ok) return { enabled: false, warning: projectLayer.warning };
|
|
163
|
+
return {
|
|
164
|
+
enabled: true,
|
|
165
|
+
maxSessions: projectLayer.maxSessions ?? globalLayer.maxSessions ?? DEFAULT_MAX_SESSIONS,
|
|
166
|
+
rules: projectLayer.rules ?? globalLayer.rules ?? [],
|
|
167
|
+
};
|
|
168
|
+
}
|