@rosetears/aili-pi 0.1.0 → 0.1.3
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/README.md +15 -6
- package/THIRD_PARTY_NOTICES.md +14 -3
- package/extensions/header/index.ts +92 -0
- package/extensions/matrix/index.ts +375 -0
- package/extensions/zentui/config.ts +1014 -0
- package/extensions/zentui/extension-status.ts +96 -0
- package/extensions/zentui/fixed-editor/cluster.ts +98 -0
- package/extensions/zentui/fixed-editor/compositor.ts +719 -0
- package/extensions/zentui/fixed-editor/index.ts +223 -0
- package/extensions/zentui/fixed-editor/input.ts +85 -0
- package/extensions/zentui/fixed-editor/pi-compat.ts +296 -0
- package/extensions/zentui/fixed-editor/selection.ts +217 -0
- package/extensions/zentui/fixed-editor/terminal-modes.ts +75 -0
- package/extensions/zentui/fixed-editor/types.ts +37 -0
- package/extensions/zentui/footer-format.ts +279 -0
- package/extensions/zentui/footer.ts +595 -0
- package/extensions/zentui/format.ts +434 -0
- package/extensions/zentui/git.ts +384 -0
- package/extensions/zentui/gradient.ts +70 -0
- package/extensions/zentui/icons.ts +252 -0
- package/extensions/zentui/index.ts +580 -0
- package/extensions/zentui/live-context.ts +75 -0
- package/extensions/zentui/package-version.ts +650 -0
- package/extensions/zentui/project-refresh.ts +104 -0
- package/extensions/zentui/project-state.ts +59 -0
- package/extensions/zentui/prototype-patch-registry.ts +111 -0
- package/extensions/zentui/runtime.ts +841 -0
- package/extensions/zentui/selector-border.ts +77 -0
- package/extensions/zentui/session-lifecycle.ts +60 -0
- package/extensions/zentui/settings-command.ts +897 -0
- package/extensions/zentui/state.ts +55 -0
- package/extensions/zentui/style.ts +332 -0
- package/extensions/zentui/thinking-message.ts +159 -0
- package/extensions/zentui/tool-execution.ts +189 -0
- package/extensions/zentui/ui.ts +618 -0
- package/extensions/zentui/user-message.ts +252 -0
- package/licenses/pi-sakura-cyberdeck-MIT.txt +21 -0
- package/licenses/pi-zentui-MIT.txt +21 -0
- package/manifests/capabilities.json +1 -1
- package/manifests/live-verification.json +14 -8
- package/manifests/provenance.json +18 -5
- package/manifests/sbom.json +19 -3
- package/notices/pi-sakura-cyberdeck-NOTICE.txt +6 -0
- package/package.json +12 -3
- package/scripts/local-package-e2e.ts +1 -1
- package/scripts/sync-global-skills.d.mts +13 -0
- package/scripts/sync-global-skills.mjs +134 -0
- package/src/runtime/credential-guard.ts +58 -0
- package/src/runtime/doctor.ts +1 -1
- package/src/runtime/index.ts +2 -2
- package/src/runtime/path-boundaries.ts +1 -1
- package/src/runtime/registry.ts +6 -2
- package/src/runtime/rem-head.txt +38 -0
- package/src/runtime/rose-context.ts +1 -1
- package/src/runtime/subagents.ts +74 -403
- package/templates/APPEND_SYSTEM.md +10 -8
- package/themes/rem-cyberdeck.json +32 -0
- package/upstream/opencode-global-agents.lock.json +34 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { cp, lstat, readdir, rename, rm } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
6
|
+
|
|
7
|
+
const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
8
|
+
const PACKAGE_NAME_PATH = ["@rosetears", "aili-pi"];
|
|
9
|
+
|
|
10
|
+
async function directoryEntry(path) {
|
|
11
|
+
try {
|
|
12
|
+
const info = await lstat(path);
|
|
13
|
+
if (info.isSymbolicLink()) return "other";
|
|
14
|
+
return info.isDirectory() ? "directory" : "other";
|
|
15
|
+
} catch (error) {
|
|
16
|
+
if (error?.code === "ENOENT") return "missing";
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function skillNames(sourceRoot) {
|
|
22
|
+
const entries = await readdir(sourceRoot, { withFileTypes: true });
|
|
23
|
+
const names = [];
|
|
24
|
+
for (const entry of entries) {
|
|
25
|
+
if (!entry.isDirectory() || entry.isSymbolicLink()) continue;
|
|
26
|
+
const skillFile = join(sourceRoot, entry.name, "SKILL.md");
|
|
27
|
+
try {
|
|
28
|
+
const info = await lstat(skillFile);
|
|
29
|
+
if (info.isFile() && !info.isSymbolicLink()) names.push(entry.name);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
if (error?.code !== "ENOENT") throw error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return names.sort((left, right) => left.localeCompare(right));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function temporaryPath(parent, name, purpose) {
|
|
38
|
+
return join(parent, `.${name}.aili-pi-${purpose}-${process.pid}-${randomUUID()}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** True only for the two locations owned by Pi's package manager. */
|
|
42
|
+
export function isPiManagedNpmPackageRoot(packageRoot, home = homedir()) {
|
|
43
|
+
const expectedRoots = [
|
|
44
|
+
join(home, ".pi", "agent", "npm", "node_modules", ...PACKAGE_NAME_PATH),
|
|
45
|
+
join(home, ".pi", "npm", "node_modules", ...PACKAGE_NAME_PATH),
|
|
46
|
+
].map((path) => resolve(path));
|
|
47
|
+
return expectedRoots.includes(resolve(packageRoot));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Replace only existing, real skill directories that have the same name as an
|
|
52
|
+
* embedded AILI snapshot skill. Unmatched user skills and package-only skills
|
|
53
|
+
* are intentionally left alone.
|
|
54
|
+
*/
|
|
55
|
+
export async function syncExistingGlobalSkills(options = {}) {
|
|
56
|
+
const packageRoot = resolve(options.packageRoot ?? PACKAGE_ROOT);
|
|
57
|
+
const home = resolve(options.home ?? homedir());
|
|
58
|
+
const sourceRoot = join(packageRoot, "skills");
|
|
59
|
+
const targetRoot = join(home, ".agents", "skills");
|
|
60
|
+
const sourceNames = await skillNames(sourceRoot);
|
|
61
|
+
const rootKind = await directoryEntry(targetRoot);
|
|
62
|
+
if (rootKind === "missing") {
|
|
63
|
+
return { scanned: sourceNames.length, updated: [], skippedMissing: sourceNames, skippedUnsafe: [] };
|
|
64
|
+
}
|
|
65
|
+
if (rootKind !== "directory") {
|
|
66
|
+
throw new Error(`refusing to sync skills through a non-directory or symlink target: ${targetRoot}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const matched = [];
|
|
70
|
+
const skippedMissing = [];
|
|
71
|
+
const skippedUnsafe = [];
|
|
72
|
+
for (const name of sourceNames) {
|
|
73
|
+
const targetKind = await directoryEntry(join(targetRoot, name));
|
|
74
|
+
if (targetKind === "directory") matched.push(name);
|
|
75
|
+
else if (targetKind === "missing") skippedMissing.push(name);
|
|
76
|
+
else skippedUnsafe.push(name);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const stages = new Map();
|
|
80
|
+
try {
|
|
81
|
+
for (const name of matched) {
|
|
82
|
+
const stage = temporaryPath(targetRoot, name, "stage");
|
|
83
|
+
await cp(join(sourceRoot, name), stage, { recursive: true, errorOnExist: true, force: false });
|
|
84
|
+
stages.set(name, stage);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const updated = [];
|
|
88
|
+
for (const name of matched) {
|
|
89
|
+
const target = join(targetRoot, name);
|
|
90
|
+
const stage = stages.get(name);
|
|
91
|
+
const previous = temporaryPath(targetRoot, name, "previous");
|
|
92
|
+
let movedPrevious = false;
|
|
93
|
+
try {
|
|
94
|
+
await rename(target, previous);
|
|
95
|
+
movedPrevious = true;
|
|
96
|
+
await rename(stage, target);
|
|
97
|
+
stages.delete(name);
|
|
98
|
+
await rm(previous, { recursive: true, force: true });
|
|
99
|
+
updated.push(name);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
if (movedPrevious) {
|
|
102
|
+
const targetKind = await directoryEntry(target);
|
|
103
|
+
if (targetKind === "missing") await rename(previous, target).catch(() => undefined);
|
|
104
|
+
}
|
|
105
|
+
throw error;
|
|
106
|
+
} finally {
|
|
107
|
+
await rm(previous, { recursive: true, force: true });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return { scanned: sourceNames.length, updated, skippedMissing, skippedUnsafe };
|
|
112
|
+
} finally {
|
|
113
|
+
await Promise.all([...stages.values()].map((path) => rm(path, { recursive: true, force: true })));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function main() {
|
|
118
|
+
const args = process.argv.slice(2);
|
|
119
|
+
if (args.length > 1 || (args.length === 1 && args[0] !== "--if-pi-managed")) {
|
|
120
|
+
throw new Error("usage: sync-global-skills.mjs [--if-pi-managed]");
|
|
121
|
+
}
|
|
122
|
+
if (args[0] === "--if-pi-managed" && !isPiManagedNpmPackageRoot(PACKAGE_ROOT)) {
|
|
123
|
+
console.log("AILI skills: global sync skipped outside a Pi-managed npm package root");
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const report = await syncExistingGlobalSkills();
|
|
127
|
+
console.log(
|
|
128
|
+
`AILI skills: scanned=${report.scanned} updated=${report.updated.length} unchanged=${report.skippedMissing.length + report.skippedUnsafe.length}`,
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
133
|
+
await main();
|
|
134
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { ExtensionAPI, ToolCallEventResult } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { isProtectedCredentialPath } from "./path-boundaries.js";
|
|
4
|
+
|
|
5
|
+
const FILE_TOOLS = new Set(["read", "write", "edit", "ls", "grep", "find"]);
|
|
6
|
+
|
|
7
|
+
function normalizeShellCandidate(value: string): string {
|
|
8
|
+
return value
|
|
9
|
+
.trim()
|
|
10
|
+
.replace(/^['"`]+|['"`]+$/g, "")
|
|
11
|
+
.replaceAll("$HOME", homedir())
|
|
12
|
+
.replace(/^~(?=\/|$)/, homedir());
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Conservatively finds path-shaped shell words, including arguments embedded
|
|
17
|
+
* in `bash -c` strings. A false positive only blocks a credential-like path;
|
|
18
|
+
* a false negative must never be treated as an allow decision elsewhere.
|
|
19
|
+
*/
|
|
20
|
+
export function bashMentionsCredentialPath(command: string): boolean {
|
|
21
|
+
const candidates = command
|
|
22
|
+
.replace(/\\([\s'"`])/g, "$1")
|
|
23
|
+
.split(/[\s;|&(){}<>]+/)
|
|
24
|
+
.flatMap((part) => part.split(/["'`]/))
|
|
25
|
+
.map(normalizeShellCandidate)
|
|
26
|
+
.filter(Boolean);
|
|
27
|
+
return candidates.some((candidate) => isProtectedCredentialPath(candidate));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function isProtectedChildPath(cwd: string, path: string): Promise<boolean> {
|
|
31
|
+
// The predicate deliberately checks lexical credential names. Resolving a
|
|
32
|
+
// caller-selected external root is unnecessary for this hard denial and
|
|
33
|
+
// avoids treating a missing path as permission to read it later.
|
|
34
|
+
void cwd;
|
|
35
|
+
return isProtectedCredentialPath(normalizeShellCandidate(path));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* This extension is injected by the AILI generic subagent wrapper for every
|
|
40
|
+
* child run. It is intentionally independent of caller-supplied role, cwd,
|
|
41
|
+
* sandbox, or extension settings and blocks only credential-path access.
|
|
42
|
+
*/
|
|
43
|
+
export default function registerCredentialGuard(pi: ExtensionAPI): void {
|
|
44
|
+
pi.on("tool_call", async (event): Promise<ToolCallEventResult | undefined> => {
|
|
45
|
+
try {
|
|
46
|
+
const input = event.input as Record<string, unknown>;
|
|
47
|
+
if (FILE_TOOLS.has(event.toolName) && typeof input.path === "string" && await isProtectedChildPath(process.cwd(), input.path)) {
|
|
48
|
+
return { block: true, reason: "AILI child denied credential/auth/private-key path access" };
|
|
49
|
+
}
|
|
50
|
+
if (event.toolName === "bash" && typeof input.command === "string" && bashMentionsCredentialPath(input.command)) {
|
|
51
|
+
return { block: true, reason: "AILI child denied credential/auth/private-key path access in bash" };
|
|
52
|
+
}
|
|
53
|
+
return undefined;
|
|
54
|
+
} catch {
|
|
55
|
+
return { block: true, reason: "AILI child denied credential-path classification failure" };
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
package/src/runtime/doctor.ts
CHANGED
|
@@ -41,7 +41,7 @@ export async function runDoctor(
|
|
|
41
41
|
});
|
|
42
42
|
results.push({ id: "package", status: dependencyState ? "PASS" : "ERROR", evidence: `version=${packageJson.version ?? "unverified"}; node=${packageJson.engines?.node ?? "unverified"}; native_dependencies=${dependencyState ? "exact" : "drift"}` });
|
|
43
43
|
const resources = [...(packageJson.pi?.extensions ?? []), ...(packageJson.pi?.prompts ?? []), ...(packageJson.pi?.skills ?? [])];
|
|
44
|
-
results.push({ id: "package.resources", status: resources.length ===
|
|
44
|
+
results.push({ id: "package.resources", status: resources.length === 11 ? "PASS" : "ERROR", evidence: `declared=${resources.length}` });
|
|
45
45
|
} catch (error) {
|
|
46
46
|
results.push({ id: "package", status: "ERROR", evidence: boundedError(error) });
|
|
47
47
|
}
|
package/src/runtime/index.ts
CHANGED
|
@@ -2,14 +2,14 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
|
2
2
|
import type { RuntimeComponent } from "./contracts.js";
|
|
3
3
|
import { registerRoseContext } from "./rose-context.js";
|
|
4
4
|
import { registerDoctor } from "./doctor.js";
|
|
5
|
-
import {
|
|
5
|
+
import { registerSubagent } from "./subagents.js";
|
|
6
6
|
import { registerNativeIntegrations } from "./native-integrations.js";
|
|
7
7
|
import { registerGlobalResourceCommand } from "./global-resources.js";
|
|
8
8
|
|
|
9
9
|
export const runtimeComponents: readonly RuntimeComponent[] = [
|
|
10
10
|
{ id: "rose-context", availability: "available", register: registerRoseContext },
|
|
11
11
|
{ id: "lifecycle-routing", availability: "available" },
|
|
12
|
-
{ id: "task-runtime", availability: "available", register:
|
|
12
|
+
{ id: "task-runtime", availability: "available", register: registerSubagent },
|
|
13
13
|
{ id: "native-integrations", availability: "available", register: registerNativeIntegrations },
|
|
14
14
|
{ id: "global-resources", availability: "available", register: registerGlobalResourceCommand },
|
|
15
15
|
{ id: "capability-registry", availability: "available" },
|
|
@@ -53,7 +53,7 @@ function isMissingPathError(error: unknown): boolean {
|
|
|
53
53
|
return error instanceof Error && "code" in error && (error as NodeJS.ErrnoException).code === "ENOENT";
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
function isProtectedCredentialPath(path: string): boolean {
|
|
56
|
+
export function isProtectedCredentialPath(path: string): boolean {
|
|
57
57
|
const normalized = path.replaceAll("\\", "/").toLowerCase();
|
|
58
58
|
const basename = normalized.split("/").at(-1) ?? "";
|
|
59
59
|
if (/^\.env(?:\..+)?$/.test(basename)) return true;
|
package/src/runtime/registry.ts
CHANGED
|
@@ -148,7 +148,11 @@ export async function validateLiveVerification(): Promise<string[]> {
|
|
|
148
148
|
implementation?: Record<string, string>;
|
|
149
149
|
}>("manifests/live-verification.json");
|
|
150
150
|
if (evidence.schemaVersion !== 1 || evidence.platform !== "linux" || evidence.piVersion !== "0.81.1" || evidence.status !== "passed") errors.push("live verification: identity is incomplete or non-pass");
|
|
151
|
-
|
|
151
|
+
const genericProbe = evidence.probes?.find((probe) => probe.id === "generic-agentless-read-package");
|
|
152
|
+
const credentialProbe = evidence.probes?.find((probe) => probe.id === "generic-credential-guard");
|
|
153
|
+
if (evidence.probes?.length !== 3 || evidence.probes.some((probe) => probe.status !== "passed") || genericProbe?.changedFiles !== 0 || credentialProbe?.changedFiles !== 0) {
|
|
154
|
+
errors.push("live verification: required probes are missing, non-pass, or mutated files");
|
|
155
|
+
}
|
|
152
156
|
for (const [filePath, expected] of Object.entries(evidence.implementation ?? {})) {
|
|
153
157
|
const content = await readFile(new URL(filePath, ROOT), "utf8");
|
|
154
158
|
const actual = createHash("sha256").update(content).digest("hex");
|
|
@@ -167,7 +171,7 @@ export async function validateProvenance(): Promise<string[]> {
|
|
|
167
171
|
const provenance = await json<{ schemaVersion: number; sources: Array<{ name: string; revision: string; license: string; status: string; repository: string; sourceFiles: string[]; symbols: string[]; localChanges: string[]; verification: string[] }> }>("manifests/provenance.json");
|
|
168
172
|
const sbom = await json<{ spdxVersion?: string; packages?: Array<{ SPDXID?: string; name?: string; licenseDeclared?: string }> }>("manifests/sbom.json");
|
|
169
173
|
const notices = await readFile(new URL("THIRD_PARTY_NOTICES.md", ROOT), "utf8");
|
|
170
|
-
if (provenance.schemaVersion !== 1 || provenance.sources.length !==
|
|
174
|
+
if (provenance.schemaVersion !== 1 || provenance.sources.length !== 6) errors.push("provenance: expected six schema-v1 source records");
|
|
171
175
|
const names = new Set<string>();
|
|
172
176
|
for (const source of provenance.sources) {
|
|
173
177
|
if (!source.name || names.has(source.name)) errors.push(`provenance: duplicate or missing source ${source.name}`);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡜⢠⢾⢿⣸⠃⡆⢸⠀⠀⠀⡁⠀⠀⠀⠀⠀⢀⢦⢸⡑⢮⣑⢺⣽⣮⢍⣛⣧⢯⡹⣍⢯⣙⢏⠿⣎⡭⢻⣌⣷⢩⠯⡽⣙⡻⢦⢹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣾⣿⣿⣿⢻⣿⢧⣈⣻⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
2
|
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡸⢠⡟⡿⡆⡇⠸⠀⠸⠀⠀⠀⡁⠀⠀⣠⠆⡆⡜⣸⢼⢩⠲⡜⢢⣷⢿⣮⡱⢎⡷⣧⡙⢮⠜⣮⡙⠽⣾⡡⢿⡜⣏⠳⣍⠣⣝⢫⢧⢻⣿⣮⠱⡩⢿⣏⣿⣿⣿⡿⢿⣿⣿⢿⣿⢿⣿⣿⠚⠛⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
3
|
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣰⠃⡾⢹⢇⣹⠀⢂⡄⣘⠀⠀⠀⡅⢀⢠⢃⠞⣸⠰⣹⡼⣧⢛⡜⢣⢽⣾⣿⣧⣛⠼⣹⢮⡙⡞⡴⣩⠳⣍⢿⣚⣷⢭⡛⣬⠳⣌⢻⡞⣯⣿⣿⡱⣙⢬⣻⣿⢻⣿⣿⣷⣮⢽⣻⢿⡜⣿⣿⣧⠀⠘⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
4
|
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⡇⣸⠣⣸⢸⡏⠀⡞⠀⣇⠀⢀⡞⡇⡜⢢⢍⠺⣐⠳⡘⣗⣿⡇⢎⡱⢊⣿⣿⣿⣿⣜⡱⢎⢿⣜⡱⢣⡛⡜⣎⢿⣽⣎⠷⣡⠟⣬⢣⢿⣻⢿⣿⣷⡩⣶⣿⣿⣧⢻⣿⣿⣿⣿⣿⣞⣿⣿⣯⢿⣇⠀⣘⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
5
|
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⢡⡏⢰⡟⢸⡇⠀⠃⡄⣷⠀⡏⡔⣯⠘⡥⢎⢣⢃⠯⡔⣻⢼⢿⢢⡱⣉⢾⣳⠘⢿⣿⡷⣍⠞⡼⢷⡫⣜⠵⣊⡎⢿⣿⡳⢥⡛⡴⣋⠾⣟⡧⢿⣽⢷⣿⣿⡞⣷⣫⢿⣿⣿⣿⣟⢿⣿⣷⣯⣿⣿⡄⢋⣸⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
6
|
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡏⣼⠁⢸⡇⢸⡇⡀⢸⠡⢼⡒⡱⠌⣷⣉⠖⡭⢲⡉⠖⣱⢸⡞⣿⣇⠲⢥⣊⢿⡆⡈⠙⢿⣝⡻⣜⡣⢟⣮⡝⣲⣙⢎⢿⣏⢧⡝⡲⣍⠞⣧⣿⣩⣿⣿⠿⣿⣿⣿⢧⣯⣿⣿⣞⣿⣿⣿⣿⣿⡿⣿⣷⣤⣀⣻⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
7
|
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣇⡏⠀⢸⡇⢸⡇⡜⢢⡙⢼⣣⠱⡉⢾⡤⢋⡔⢣⠜⡱⢌⡒⣻⢻⣷⡝⣢⠞⡸⣷⢡⠀⠀⠉⠳⣍⡟⣾⣔⡻⢧⣚⠼⣪⣿⡗⢮⠵⣎⢏⢿⣹⡦⢽⣻⣯⡽⣷⡿⣿⡿⡿⢿⣯⣞⡿⣝⣿⣞⣿⣿⣿⡌⠉⠻⣧⡀⠀⠀⠀⠀⠀⠀⠀⣀⣀⣀⠀⠀⠀⠀
|
|
8
|
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠊⠉⠉⠑⠀⠀⠤⢄⡀⠀⠀⢸⣻⠁⢀⣼⡇⢼⡗⣌⠣⡜⣸⣧⢣⠑⡘⣷⡡⢚⠤⣋⠴⢣⠱⣚⣿⣟⣷⡡⢞⣥⣿⣶⡤⠖⠒⠒⠚⠓⠧⣫⢟⣷⣽⣣⢇⣿⣿⡩⢞⡜⢮⣹⢿⣏⠾⣿⣿⣿⣞⣿⣿⣷⣏⢯⣿⣯⣿⠽⣾⣧⢟⣿⣿⣇⠀⠀⣿⠹⡆⠀⠀⠀⠠⣶⣻⡹⣼⡽⠃⠀⠀⠀
|
|
9
|
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⡋⠀⠀⠀⢀⠠⠀⠂⢀⢠⠃⠀⠀⣿⣹⢀⡼⡰⣯⢘⣿⠤⡓⢬⠐⣿⡆⡍⢒⣻⣧⢩⠒⡥⢎⡣⡝⡤⢻⡽⣿⣿⠽⣋⠜⣳⡄⠀⠀⠀⠀⣀⣀⣈⠙⠲⢭⡻⢿⣾⣿⣱⢫⡜⢧⢺⡟⣧⡟⣿⣿⠟⠋⠀⣀⣀⡈⠙⢾⣿⣝⢿⣽⡷⣯⣿⣿⣿⠀⠀⣹⠆⢻⡄⠀⠀⠀⠀⠈⠉⠉⠀⠀⠀⠀⠀
|
|
10
|
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠙⠒⠦⣤⣀⡐⣀⠖⠁⠀⠀⠀⣿⣼⠚⡥⡑⢿⣂⢿⡧⡙⢆⠩⢼⣷⡌⢣⠼⣿⣧⢹⠰⢣⡱⢎⡱⢣⢻⣿⣯⢷⡈⢞⡰⢳⡀⠴⢊⣉⣤⣴⣶⣶⣦⣤⣽⣛⡻⢿⣧⠳⡜⣭⢺⣏⣷⡹⣿⡇⠀⡴⠋⠀⠀⠈⠢⡀⢹⣿⣷⣝⣿⣵⣿⣿⣿⡄⠀⣼⠁⠸⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
11
|
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠁⠀⠀⠀⠀⢰⣿⡏⡱⢢⣹⡞⣳⢺⣷⢉⡜⢌⡚⣿⣯⠒⣌⢻⣯⣧⡙⢦⡑⢎⡱⢍⡖⠦⢿⣧⢻⡔⣌⠣⢳⣴⣿⣿⣿⣿⣿⣿⠿⢿⣿⣿⣿⣿⣿⡱⣉⠦⣹⢶⣏⢷⣿⡇⢰⢥⣀⠀⠀⠀⠀⠱⡈⣿⣞⣿⣿⣛⣿⣿⣿⡇⠀⡾⠀⠀⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
12
|
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣟⡧⣑⣳⠃⠀⢸⡆⣿⠆⡜⢢⠜⡹⡿⣷⣸⣮⢿⣯⣷⡂⣍⠲⡘⢢⠜⢣⡍⣿⣧⠙⢦⠩⣽⣿⡿⢛⣿⣿⣿⣿⣷⣶⣿⡄⠙⣿⣿⠶⢥⠚⣼⢻⣏⢾⡻⡇⡞⠳⣌⠱⣄⠐⠀⠀⡇⢹⣾⢿⣿⣿⣯⣿⣿⡇⢠⠃⠀⠀⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
13
|
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠟⢳⠀⠀⠀⠀⢸⣿⣷⢠⢿⠀⠀⠈⡧⣹⡗⣌⢣⢎⡱⢻⣿⣯⠰⡩⢧⡙⢿⣤⠣⣙⢢⠙⢦⡘⡜⢯⣷⡀⠛⠭⠖⠋⢸⣿⢿⣋⡿⠯⡟⣹⡇⢠⠋⣿⡘⠦⡙⣼⣿⡹⣾⣇⠃⣧⠤⠼⠃⠈⠢⣌⣠⠇⣼⣿⣿⣻⣿⣿⢿⣿⡷⠎⠀⠀⢰⡿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
14
|
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢿⠀⠈⠇⠀⠀⠀⢸⣽⢻⡌⣿⠀⠀⠀⢳⡔⢿⣌⠲⡌⣵⢏⢻⡽⣷⡠⢍⢳⡌⠻⣷⣈⠦⣙⠢⢜⣰⡭⠟⠙⠀⠀⠀⠀⠀⠻⣮⠉⠂⢀⣀⡿⠗⠙⠀⣽⢌⢣⡑⣾⣷⣽⡿⠁⠀⢿⣀⡤⣖⠒⢄⠀⠀⢠⣿⣾⣿⣿⣿⡿⠀⣿⣿⣄⠀⠀⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
15
|
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⡀⠀⠘⡄⠀⠀⠘⣾⠘⣷⢹⡄⠀⠀⠘⣇⠎⣿⡰⡽⢃⠎⣌⠿⣟⣷⣎⡴⡙⣦⡌⠻⢷⡦⠗⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⠋⠉⠀⠀⠀⠀⠀⣿⢈⢦⡵⡿⠑⠋⠀⠀⠀⠈⣏⡧⠀⠙⠘⣀⠤⠞⠷⣿⣿⣿⣿⡇⠠⢿⠘⣟⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
16
|
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢣⠀⠀⠰⡄⠀⠀⣿⡇⢻⣞⣧⠀⠀⠀⢹⡎⡜⢿⡡⣉⠖⣩⣾⣿⣿⣿⢿⣿⣷⡿⠷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢿⡾⠝⠋⠀⠀⠀⠀⠀⣠⠏⢉⠠⢄⡶⠋⠁⠀⠀⢀⣼⣿⢟⡿⠀⠀⡜⠀⠸⡞⣆⠀⠀⠀⠠⠷⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
17
|
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢆⠀⠀⠘⡄⠀⢸⡇⠜⣿⣻⡄⠀⠀⠀⢻⡰⣉⢷⣌⢲⣿⡟⣸⡏⣿⡿⣿⣿⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣧⠴⠃⡠⠊⠀⠀⠀⠀⣰⣿⣿⠏⣼⠃⠀⢠⠇⠀⠀⢻⠟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
18
|
+
⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢆⠀⠀⠘⢆⠀⣷⠐⢿⣿⣷⠀⠀⠀⠈⣿⣔⣊⢻⣿⣿⣴⠞⢻⣟⢻⠜⠀⡹⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠞⠁⠀⠀⠀⢀⣾⣿⣿⠋⢠⠃⠀⠀⠊⠀⠀⣀⠬⠤⠤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
19
|
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢆⠀⠀⠀⠣⡈⣇⠀⢻⣿⡄⠀⠀⠀⢸⣿⣷⣆⣻⣿⠟⠢⢀⠙⢻⠶⠊⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⣿⣿⠋⠀⠀⠀⠀⢠⣾⣿⡿⠁⡰⠁⠀⠀⠀⡠⠔⠉⠀⠀⠀⠀⣸⠆⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⠀
|
|
20
|
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠣⡀⠀⠀⠑⢜⢧⡀⠻⣿⠀⠀⠀⠀⢿⣺⣻⢿⡀⠀⠀⠈⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⣿⣿⠃⠀⠀⠀⠀⢠⣿⣿⠏⠀⠀⠀⠀⢀⠴⠊⠀⠀⠀⠀⢀⡠⠚⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠁⠀
|
|
21
|
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⡄⠀⠀⠈⠫⣷⣤⡹⣇⠀⠀⠀⠘⣷⢿⣾⡽⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠤⡴⠖⠚⠓⠓⢦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⠏⠀⠀⠀⠀⢠⣿⡿⠁⠀⠀⠀⢀⠔⠁⠀⠀⠀⠀⢀⠔⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
22
|
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠤⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⢄⠀⠀⠀⠈⠙⢛⡋⠀⠀⠀⠀⠺⣿⣞⣿⣯⣿⣦⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⡭⠒⠉⠂⠉⠀⠁⠈⠘⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⣿⡟⠀⠀⠀⠀⢠⠟⣹⣁⠀⠀⢀⠔⠁⠀⠀⠀⠀⢀⡔⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
23
|
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣆⠀⡑⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠤⠔⠒⠈⢇⠀⠀⠀⠀⠁⠀⠀⠀⠀⠀⠀⠙⣿⣾⣽⣻⣿⠯⠗⠢⠀⠀⠀⠀⠀⠀⢰⣿⡟⠀⠀⠀⠀⠀⠀⠀⠀⢰⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠏⣸⣿⡿⠀⠀⠀⠀⢀⠏⠀⠋⠉⠓⡴⠃⠀⠀⠀⠀⠀⡠⠋⠀⠀⠀⠀⠀⠀⠀⠀⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
24
|
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢧⡀⢸⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡖⠉⠀⠀⠤⡔⠒⢺⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢻⣿⣿⣻⣽⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠦⡀⠀⠀⠀⠀⠀⠀⣠⠏⠀⠀⠀⠀⠀⠀⠀⠀⢠⠞⡡⠎⣼⣿⠁⠀⠀⠀⠀⡎⠀⠀⠀⣠⠊⠀⠀⠀⠀⠀⣠⠊⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
25
|
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢳⡀⠀⠀⢀⠎⠙⠛⠒⠚⠓⠒⠉⠉⠑⢤⡀⠀⠀⠀⠈⣿⣿⣿⡾⣿⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠑⠢⠤⠤⠒⠋⠀⠀⠀⠀⠀⠀⠀⠀⢠⡼⢋⠴⡁⠎⢼⠇⠀⠀⠀⠐⣸⠁⠀⢀⠔⠁⠀⠀⠀⠀⢠⠞⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
26
|
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣦⣠⠋⠘⢄⠀⠀⠳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠢⣀⡀⠀⢹⣻⣿⣽⣻⣿⣿⣦⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠞⢣⠐⠥⢂⠅⣨⠏⠀⠀⠀⠀⠀⢿⠀⢠⠊⠀⠀⠀⠀⢀⣴⠁⠀⠀⠀⠀⣀⣤⡶⣞⡿⣏⣯⣝⡻⣷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
27
|
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠙⢦⡀⠈⠳⡀⠀⠀⠑⠒⠰⢢⢶⡄⠀⠀⠀⠀⠀⠀⠉⠛⠉⣷⢺⡽⣿⣧⣽⣿⣿⣷⣦⣤⣄⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⢚⠡⡘⠤⢉⢂⣡⡸⠃⠀⠀⠀⠀⠀⠐⠚⡷⠃⠀⠀⠀⠀⢠⠞⠀⠀⢀⣠⣶⠿⣏⣷⡻⣵⣻⢞⡵⢮⡱⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
28
|
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠢⡀⠈⠢⢤⣤⡴⣴⣯⣍⡿⡦⣀⠀⠀⠀⠀⠀⠀⠀⢹⣻⢼⡳⣿⢯⣟⣯⢟⣿⡛⠛⠉⣡⠮⣍⣹⠓⢶⡦⠤⣀⣀⡤⠖⠋⠰⠘⠒⠋⠉⠉⠉⡰⠁⠀⠀⠀⠀⠀⠀⠀⠈⢁⡤⠒⠲⡄⣰⠃⠀⡠⠒⣽⢯⣏⡿⡽⣶⢻⣳⡝⣮⡝⣮⢵⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
29
|
+
⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⠀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⣀⠈⠱⡤⠤⠤⠴⠒⠲⠚⢹⡑⡌⠳⡆⠀⠀⠀⠀⠀⢸⣽⢣⠿⣽⡿⡾⣽⢺⡜⣇⠀⢷⢧⡐⢠⠃⡍⡐⢧⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡠⣄⡜⠁⠀⠀⠀⠀⠀⠀⠀⡠⠖⠁⠀⠀⣴⠘⢧⠔⠉⠀⣸⣟⢮⣗⣻⡽⣞⢯⣓⢾⡱⣞⣥⡿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
30
|
+
⠀⠀⠀⠀⠀⠀⠀⢮⣄⠀⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠤⠖⠚⠉⠙⠇⠀⠈⠋⠹⣇⠀⠀⠀⠀⠀⠀⠈⡇⠀⠀⠙⠀⠀⠀⠀⠀⣿⡜⣫⣟⣵⠟⠛⢳⠿⠾⣟⡷⡌⠳⣻⡏⠲⣤⠑⢺⡄⠀⠀⢀⡤⢦⠀⣠⠞⢁⣴⡫⠔⠒⠒⠢⢄⠀⢀⡤⠊⠀⠀⠀⢀⣴⠃⠀⠈⢧⡀⠀⢻⣞⢯⡞⣧⢻⡜⣮⢝⣮⢳⢧⣾⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
31
|
+
⠀⠀⠀⠀⠀⠀⠀⠈⠙⠆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⠥⠒⠒⠋⣉⣽⡆⠀⠀⠀⠀⠹⣆⠀⠀⠀⠀⠀⠀⠹⡄⠀⠀⠀⠀⠀⠀⣸⡧⢫⡟⠉⠓⡦⢞⣫⡿⠟⠛⠓⠚⢒⡿⣅⠀⠈⢳⡈⡇⢀⠜⠁⠀⢸⡞⡡⠊⠁⠀⠀⠀⠀⠀⠀⠀⠉⠚⠤⣀⠀⠀⢀⡼⣏⢀⡤⠶⢌⣷⡀⢸⣿⢫⡞⣵⢣⣟⣼⠯⣞⡭⣾⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
32
|
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠀⠠⠄⠀⠀⠠⠀⣤⠀⢀⣸⠤⠔⠂⠙⠋⠀⣇⠀⠀⠀⢨⢐⡽⠙⠢⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠂⣿⣷⠛⠶⣦⠟⣱⡿⠉⠀⠀⠀⣀⣀⣀⣬⠾⢆⠀⠀⢳⡿⠃⠀⠀⠀⢀⡼⠀⠀⠀⠀⠀⢀⣀⠀⠀⠀⠀⣀⠀⠀⠉⣷⣩⠖⠋⠉⠀⠀⠈⢧⣿⢻⡜⣧⣻⠼⣛⠭⣎⢻⡜⣼⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
33
|
+
⠀⠀⠀⠀⠀⠀⠀⠠⠀⠀⠀⠀⢀⠤⠒⠉⠀⠀⠀⠀⠀⠀⠀⠀⢓⠊⠁⠀⠀⠀⠀⠀⠠⠁⡏⠀⠀⢀⣊⡞⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⣿⣏⣀⣴⢣⣾⠏⠀⠡⢉⡬⠛⠉⠀⠀⠈⠑⠺⣧⣤⠈⠁⠀⠀⢀⠴⢊⡇⠀⠀⠀⠀⠀⣀⠼⡏⠓⣲⠲⠤⣵⣲⢾⠋⡀⠀⠀⠀⣠⠆⠀⠈⢿⡷⡛⢩⠐⡍⢎⡳⡜⣣⢞⣹⣇⣀⠤⠾⢛⣿⡿⣟⣷⢲⣄⡀⠀⠀
|
|
34
|
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠊⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⢀⠂⡇⠀⢐⣿⡏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣾⣿⠃⠀⣹⢡⢯⠇⠀⣬⠖⠉⠀⠀⠀⠀⠀⢀⣠⣴⣿⣿⣿⠶⠀⣾⣿⠶⣾⠀⠀⠀⠀⠐⠞⠁⡐⢻⡀⠳⠖⠚⢩⡴⠃⡔⠸⣰⠖⠛⠳⣄⠠⠀⢨⣏⠹⣉⠟⠺⠶⣥⢳⣑⢮⡜⣻⣤⢦⡴⣾⢯⣗⢯⡞⣧⣛⢿⡀⠀
|
|
35
|
+
⠀⠀⠀⠀⠀⠀⠀⠀⢠⠎⠀⠀⠀⠀⠀⠀⠀⠀⢀⡠⠔⣲⠀⠀⢸⡄⠀⠀⠀⠀⠀⡤⠀⢸⣇⡴⠋⠸⢀⣀⠠⠤⠤⠢⢤⡤⡀⣀⣤⣶⠏⠉⣩⣿⣿⣟⢻⢡⢏⣎⠴⠋⠀⠀⠀⠀⣀⣤⣴⣾⣿⣿⣿⣿⣿⣿⡆⠀⠰⡀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠁⢼⠆⠀⠀⠀⢯⣠⣼⠥⠞⢁⠀⠀⠀⠉⠃⣴⢋⠆⣓⠰⢊⡑⠦⡈⠝⣯⣾⡽⣽⢿⣶⣽⡿⡹⢎⡿⡜⢧⡹⡚⡇⠀
|
|
36
|
+
⠀⠀⠀⠀⠀⠀⠀⠰⡅⠀⠀⠀⠀⢀⡀⠤⠔⠂⠁⣠⠞⠉⡇⠀⠈⡷⠀⠀⠀⠀⡼⠁⣰⡟⠋⠀⣴⠟⠁⠀⠀⠀⢀⡀⡼⣲⠿⣛⠵⠎⢀⣴⡿⡟⠁⣈⡏⡼⡛⠁⠀⢀⣠⣴⣶⡿⠟⣫⣽⣿⣿⡏⠉⣿⡟⣿⣷⠀⠀⠉⠙⡇⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⠟⡤⢋⠜⡠⢍⢢⠑⢢⠑⡌⡐⢻⡴⣩⢖⡎⢶⣱⣙⢧⡚⣭⠲⣡⢃⢷⡠
|
|
37
|
+
⠀⠀⠀⠀⠀⣀⠤⠤⠽⢭⠉⠉⠉⠀⠀⠀⢀⣴⠞⠋⠀⠀⣇⠀⠀⢿⠀⡀⣀⣨⣵⠞⠁⡀⠠⢼⣧⡦⠶⠖⠚⣻⠉⣼⢳⢋⡞⣱⣮⣶⡿⠋⢀⣧⡵⢻⠀⣧⣧⣶⣿⠿⠛⠋⠀⣰⣾⣿⢿⣿⡟⠀⢰⣿⠇⢻⣿⡄⠀⠀⠀⢣⠀⠀⠀⠀⠀⠀⠀⠀⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠞⣡⠚⡄⢃⠎⡑⠈⠆⡉⢆⠱⢠⠑⠄⢻⡵⢎⣞⢣⡗⡺⣖⡹⢆⡻⢄⠣⢾⡱
|
|
38
|
+
⣀⠀⠠⢊⠉⠀⣀⣠⠖⢹⠆⠀⠀⢀⠠⢔⠛⠁⠀⠀⠀⠀⢻⠀⡀⢾⡈⣭⡵⢾⢥⠀⠐⠀⠀⢸⣆⣠⢴⣴⣫⣇⣼⣏⣴⡿⠿⠟⠛⠉⠀⠀⡏⠉⠉⢹⠀⠻⣿⡍⠤⠒⢀⣠⣾⣿⠏⢡⣾⠟⠀⢀⣿⡿⠀⢸⣿⣧⠀⠀⠀⢸⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣴⠞⡡⢩⠄⠣⠘⠠⠈⠄⠁⠂⠈⠄⠊⠤⢉⡐⠂⣿⡹⣬⠳⡞⡵⣭⣹⠹⡜⡭⢊⡽⠀
|
|
@@ -14,7 +14,7 @@ export function buildRoseAppendix(event: BeforeAgentStartEvent, pi: ExtensionAPI
|
|
|
14
14
|
const conflictState = conflicts.length === 0
|
|
15
15
|
? "lifecycle_conflicts=none-observed"
|
|
16
16
|
: `lifecycle_conflicts=non-pass (${conflicts.map((conflict) => conflict.name).join(", ")})`;
|
|
17
|
-
return `## AILI runtime summary\n- ${projectRuleState(event)}\n- ${conflictState}\n- rose_static_rules=global APPEND_SYSTEM marker resource (not injected by this Extension)\n- task_runtime=pi-subagent
|
|
17
|
+
return `## AILI runtime summary\n- ${projectRuleState(event)}\n- ${conflictState}\n- rose_static_rules=global APPEND_SYSTEM marker resource (not injected by this Extension)\n- task_runtime=generic pinned pi-subagent surface (optional global aili.<role> profiles; async/actions, bounded upstream fan-out, worktree/external cwd, and sandbox configuration available; credential paths remain denied)\n- permission_runtime=pi-permission-modes (Default/Plan/Build/YOLO; /perm; Alt+M; sandbox availability is vendor-reported)\n- native_web=pi-web-access complete upstream surface; provider/network/filesystem side effects remain visible to the active permission policy\n- quota_status=pi-quota-status default enabled; its global state is maintained by the upstream extension\n- capability_registry=available; optional or unavailable capability decisions must report SKIP/WARN and must not claim work ran\n- doctor=available (/aili-doctor; current core remains non-pass until required provenance and release evidence pass)`;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export function registerRoseContext(pi: ExtensionAPI): void {
|