@oharness/engine 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.
Files changed (256) hide show
  1. package/dist/agent/agent.d.ts +196 -0
  2. package/dist/agent/agent.js +758 -0
  3. package/dist/agent/agent.js.map +1 -0
  4. package/dist/agent/compaction.d.ts +68 -0
  5. package/dist/agent/compaction.js +179 -0
  6. package/dist/agent/compaction.js.map +1 -0
  7. package/dist/agent/hooks.d.ts +77 -0
  8. package/dist/agent/hooks.js +152 -0
  9. package/dist/agent/hooks.js.map +1 -0
  10. package/dist/agent/memory.d.ts +45 -0
  11. package/dist/agent/memory.js +147 -0
  12. package/dist/agent/memory.js.map +1 -0
  13. package/dist/agent/overflow.d.ts +35 -0
  14. package/dist/agent/overflow.js +69 -0
  15. package/dist/agent/overflow.js.map +1 -0
  16. package/dist/agent/permissions.d.ts +181 -0
  17. package/dist/agent/permissions.js +289 -0
  18. package/dist/agent/permissions.js.map +1 -0
  19. package/dist/agent/prompt.d.ts +25 -0
  20. package/dist/agent/prompt.js +63 -0
  21. package/dist/agent/prompt.js.map +1 -0
  22. package/dist/agent/session.d.ts +108 -0
  23. package/dist/agent/session.js +231 -0
  24. package/dist/agent/session.js.map +1 -0
  25. package/dist/agent/settings.d.ts +37 -0
  26. package/dist/agent/settings.js +113 -0
  27. package/dist/agent/settings.js.map +1 -0
  28. package/dist/agent/skills.d.ts +58 -0
  29. package/dist/agent/skills.js +225 -0
  30. package/dist/agent/skills.js.map +1 -0
  31. package/dist/agent/store.d.ts +308 -0
  32. package/dist/agent/store.js +596 -0
  33. package/dist/agent/store.js.map +1 -0
  34. package/dist/agent/subagent.d.ts +44 -0
  35. package/dist/agent/subagent.js +138 -0
  36. package/dist/agent/subagent.js.map +1 -0
  37. package/dist/agent/titles.d.ts +8 -0
  38. package/dist/agent/titles.js +23 -0
  39. package/dist/agent/titles.js.map +1 -0
  40. package/dist/agent/workspace.d.ts +60 -0
  41. package/dist/agent/workspace.js +156 -0
  42. package/dist/agent/workspace.js.map +1 -0
  43. package/dist/artifacts/publish.d.ts +94 -0
  44. package/dist/artifacts/publish.js +104 -0
  45. package/dist/artifacts/publish.js.map +1 -0
  46. package/dist/artifacts/store.d.ts +324 -0
  47. package/dist/artifacts/store.js +500 -0
  48. package/dist/artifacts/store.js.map +1 -0
  49. package/dist/auth/index.d.ts +13 -0
  50. package/dist/auth/index.js +10 -0
  51. package/dist/auth/index.js.map +1 -0
  52. package/dist/auth/manager.d.ts +83 -0
  53. package/dist/auth/manager.js +266 -0
  54. package/dist/auth/manager.js.map +1 -0
  55. package/dist/auth/oauth.d.ts +83 -0
  56. package/dist/auth/oauth.js +327 -0
  57. package/dist/auth/oauth.js.map +1 -0
  58. package/dist/auth/providers/account.d.ts +59 -0
  59. package/dist/auth/providers/account.js +282 -0
  60. package/dist/auth/providers/account.js.map +1 -0
  61. package/dist/auth/providers/anthropic.d.ts +24 -0
  62. package/dist/auth/providers/anthropic.js +209 -0
  63. package/dist/auth/providers/anthropic.js.map +1 -0
  64. package/dist/auth/providers/generic.d.ts +57 -0
  65. package/dist/auth/providers/generic.js +155 -0
  66. package/dist/auth/providers/generic.js.map +1 -0
  67. package/dist/auth/providers/official.d.ts +3 -0
  68. package/dist/auth/providers/official.js +90 -0
  69. package/dist/auth/providers/official.js.map +1 -0
  70. package/dist/auth/providers/ottoport.d.ts +3 -0
  71. package/dist/auth/providers/ottoport.js +60 -0
  72. package/dist/auth/providers/ottoport.js.map +1 -0
  73. package/dist/auth/store.d.ts +22 -0
  74. package/dist/auth/store.js +138 -0
  75. package/dist/auth/store.js.map +1 -0
  76. package/dist/auth/types.d.ts +167 -0
  77. package/dist/auth/types.js +16 -0
  78. package/dist/auth/types.js.map +1 -0
  79. package/dist/config.d.ts +197 -0
  80. package/dist/config.js +195 -0
  81. package/dist/config.js.map +1 -0
  82. package/dist/core/errors.d.ts +46 -0
  83. package/dist/core/errors.js +83 -0
  84. package/dist/core/errors.js.map +1 -0
  85. package/dist/core/tokens.d.ts +12 -0
  86. package/dist/core/tokens.js +56 -0
  87. package/dist/core/tokens.js.map +1 -0
  88. package/dist/core/types.d.ts +91 -0
  89. package/dist/core/types.js +49 -0
  90. package/dist/core/types.js.map +1 -0
  91. package/dist/harness.d.ts +302 -0
  92. package/dist/harness.js +695 -0
  93. package/dist/harness.js.map +1 -0
  94. package/dist/index.d.ts +32 -0
  95. package/dist/index.js +21 -0
  96. package/dist/index.js.map +1 -0
  97. package/dist/mcp/client.d.ts +55 -0
  98. package/dist/mcp/client.js +199 -0
  99. package/dist/mcp/client.js.map +1 -0
  100. package/dist/mcp/index.d.ts +13 -0
  101. package/dist/mcp/index.js +8 -0
  102. package/dist/mcp/index.js.map +1 -0
  103. package/dist/mcp/manager.d.ts +77 -0
  104. package/dist/mcp/manager.js +325 -0
  105. package/dist/mcp/manager.js.map +1 -0
  106. package/dist/mcp/oauth.d.ts +63 -0
  107. package/dist/mcp/oauth.js +302 -0
  108. package/dist/mcp/oauth.js.map +1 -0
  109. package/dist/mcp/protocol.d.ts +130 -0
  110. package/dist/mcp/protocol.js +14 -0
  111. package/dist/mcp/protocol.js.map +1 -0
  112. package/dist/mcp/registry.d.ts +52 -0
  113. package/dist/mcp/registry.js +100 -0
  114. package/dist/mcp/registry.js.map +1 -0
  115. package/dist/mcp/registry.json +293 -0
  116. package/dist/mcp/secrets.d.ts +20 -0
  117. package/dist/mcp/secrets.js +75 -0
  118. package/dist/mcp/secrets.js.map +1 -0
  119. package/dist/mcp/transport.d.ts +51 -0
  120. package/dist/mcp/transport.js +158 -0
  121. package/dist/mcp/transport.js.map +1 -0
  122. package/dist/paths.d.ts +37 -0
  123. package/dist/paths.js +63 -0
  124. package/dist/paths.js.map +1 -0
  125. package/dist/plugins.d.ts +55 -0
  126. package/dist/plugins.js +201 -0
  127. package/dist/plugins.js.map +1 -0
  128. package/dist/providers/builtin.d.ts +23 -0
  129. package/dist/providers/builtin.js +141 -0
  130. package/dist/providers/builtin.js.map +1 -0
  131. package/dist/providers/catalog.d.ts +129 -0
  132. package/dist/providers/catalog.js +254 -0
  133. package/dist/providers/catalog.js.map +1 -0
  134. package/dist/providers/catalog.json +472 -0
  135. package/dist/providers/discovery.d.ts +16 -0
  136. package/dist/providers/discovery.js +161 -0
  137. package/dist/providers/discovery.js.map +1 -0
  138. package/dist/providers/http.d.ts +24 -0
  139. package/dist/providers/http.js +139 -0
  140. package/dist/providers/http.js.map +1 -0
  141. package/dist/providers/index.d.ts +10 -0
  142. package/dist/providers/index.js +8 -0
  143. package/dist/providers/index.js.map +1 -0
  144. package/dist/providers/piai.d.ts +124 -0
  145. package/dist/providers/piai.js +551 -0
  146. package/dist/providers/piai.js.map +1 -0
  147. package/dist/providers/registry.d.ts +65 -0
  148. package/dist/providers/registry.js +195 -0
  149. package/dist/providers/registry.js.map +1 -0
  150. package/dist/providers/resilient.d.ts +46 -0
  151. package/dist/providers/resilient.js +117 -0
  152. package/dist/providers/resilient.js.map +1 -0
  153. package/dist/providers/types.d.ts +182 -0
  154. package/dist/providers/types.js +50 -0
  155. package/dist/providers/types.js.map +1 -0
  156. package/dist/schedule/cron.d.ts +35 -0
  157. package/dist/schedule/cron.js +125 -0
  158. package/dist/schedule/cron.js.map +1 -0
  159. package/dist/schedule/scheduler.d.ts +87 -0
  160. package/dist/schedule/scheduler.js +237 -0
  161. package/dist/schedule/scheduler.js.map +1 -0
  162. package/dist/storage/kv.d.ts +56 -0
  163. package/dist/storage/kv.js +173 -0
  164. package/dist/storage/kv.js.map +1 -0
  165. package/dist/storage/migrate.d.ts +11 -0
  166. package/dist/storage/migrate.js +93 -0
  167. package/dist/storage/migrate.js.map +1 -0
  168. package/dist/storage/project.d.ts +9 -0
  169. package/dist/storage/project.js +57 -0
  170. package/dist/storage/project.js.map +1 -0
  171. package/dist/storage/records.d.ts +32 -0
  172. package/dist/storage/records.js +48 -0
  173. package/dist/storage/records.js.map +1 -0
  174. package/dist/sync/client.d.ts +79 -0
  175. package/dist/sync/client.js +118 -0
  176. package/dist/sync/client.js.map +1 -0
  177. package/dist/sync/mirror.d.ts +85 -0
  178. package/dist/sync/mirror.js +389 -0
  179. package/dist/sync/mirror.js.map +1 -0
  180. package/dist/sync/protocol.d.ts +67 -0
  181. package/dist/sync/protocol.js +16 -0
  182. package/dist/sync/protocol.js.map +1 -0
  183. package/dist/sync/settings.d.ts +56 -0
  184. package/dist/sync/settings.js +60 -0
  185. package/dist/sync/settings.js.map +1 -0
  186. package/dist/tools/builtin/artifact.d.ts +16 -0
  187. package/dist/tools/builtin/artifact.js +134 -0
  188. package/dist/tools/builtin/artifact.js.map +1 -0
  189. package/dist/tools/builtin/bash.d.ts +3 -0
  190. package/dist/tools/builtin/bash.js +142 -0
  191. package/dist/tools/builtin/bash.js.map +1 -0
  192. package/dist/tools/builtin/fs.d.ts +19 -0
  193. package/dist/tools/builtin/fs.js +280 -0
  194. package/dist/tools/builtin/fs.js.map +1 -0
  195. package/dist/tools/builtin/index.d.ts +22 -0
  196. package/dist/tools/builtin/index.js +42 -0
  197. package/dist/tools/builtin/index.js.map +1 -0
  198. package/dist/tools/builtin/mcp-resource.d.ts +3 -0
  199. package/dist/tools/builtin/mcp-resource.js +220 -0
  200. package/dist/tools/builtin/mcp-resource.js.map +1 -0
  201. package/dist/tools/builtin/memory.d.ts +10 -0
  202. package/dist/tools/builtin/memory.js +76 -0
  203. package/dist/tools/builtin/memory.js.map +1 -0
  204. package/dist/tools/builtin/recall.d.ts +13 -0
  205. package/dist/tools/builtin/recall.js +163 -0
  206. package/dist/tools/builtin/recall.js.map +1 -0
  207. package/dist/tools/builtin/search.d.ts +3 -0
  208. package/dist/tools/builtin/search.js +240 -0
  209. package/dist/tools/builtin/search.js.map +1 -0
  210. package/dist/tools/builtin/skill.d.ts +10 -0
  211. package/dist/tools/builtin/skill.js +46 -0
  212. package/dist/tools/builtin/skill.js.map +1 -0
  213. package/dist/tools/builtin/task.d.ts +9 -0
  214. package/dist/tools/builtin/task.js +68 -0
  215. package/dist/tools/builtin/task.js.map +1 -0
  216. package/dist/tools/builtin/todo.d.ts +20 -0
  217. package/dist/tools/builtin/todo.js +63 -0
  218. package/dist/tools/builtin/todo.js.map +1 -0
  219. package/dist/tools/define.d.ts +19 -0
  220. package/dist/tools/define.js +39 -0
  221. package/dist/tools/define.js.map +1 -0
  222. package/dist/tools/index.d.ts +7 -0
  223. package/dist/tools/index.js +5 -0
  224. package/dist/tools/index.js.map +1 -0
  225. package/dist/tools/registry.d.ts +39 -0
  226. package/dist/tools/registry.js +101 -0
  227. package/dist/tools/registry.js.map +1 -0
  228. package/dist/tools/types.d.ts +112 -0
  229. package/dist/tools/types.js +22 -0
  230. package/dist/tools/types.js.map +1 -0
  231. package/dist/util/id.d.ts +9 -0
  232. package/dist/util/id.js +19 -0
  233. package/dist/util/id.js.map +1 -0
  234. package/dist/util/json.d.ts +25 -0
  235. package/dist/util/json.js +100 -0
  236. package/dist/util/json.js.map +1 -0
  237. package/dist/util/jwt.d.ts +74 -0
  238. package/dist/util/jwt.js +177 -0
  239. package/dist/util/jwt.js.map +1 -0
  240. package/dist/util/logger.d.ts +13 -0
  241. package/dist/util/logger.js +59 -0
  242. package/dist/util/logger.js.map +1 -0
  243. package/dist/util/queue.d.ts +26 -0
  244. package/dist/util/queue.js +106 -0
  245. package/dist/util/queue.js.map +1 -0
  246. package/dist/util/schema.d.ts +17 -0
  247. package/dist/util/schema.js +98 -0
  248. package/dist/util/schema.js.map +1 -0
  249. package/dist/util/shell.d.ts +20 -0
  250. package/dist/util/shell.js +24 -0
  251. package/dist/util/shell.js.map +1 -0
  252. package/official.json +4 -0
  253. package/package.json +38 -0
  254. package/skills/artifact-authoring/SKILL.md +81 -0
  255. package/skills/canvas/SKILL.md +85 -0
  256. package/skills/skill-authoring/SKILL.md +85 -0
@@ -0,0 +1,138 @@
1
+ import { userPath } from "../paths.js";
2
+ import { readFile, readdir } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { splitFrontmatter } from "./skills.js";
5
+ export const EXPLORE_AGENT = {
6
+ name: "explore",
7
+ description: "Read-only search across the codebase. Use when answering means sweeping many files and you only want the conclusion, not the file dumps.",
8
+ systemPrompt: `You are a read-only exploration agent. Search the codebase to answer the question you were given.
9
+
10
+ You cannot modify anything, and the person who spawned you cannot ask follow-up questions — so answer completely in one message.
11
+
12
+ Report concrete findings: file paths with line numbers, the actual code or configuration that matters, and a direct answer to what was asked. If you could not find something, say so plainly rather than guessing. Do not describe your search process.`,
13
+ tools: { allow: ["read", "list", "glob", "grep", "bash"], deny: ["write", "edit", "task"] },
14
+ maxTurns: 30,
15
+ };
16
+ export const GENERAL_AGENT = {
17
+ name: "general",
18
+ description: "General-purpose agent with the full tool set, for multi-step work that can be delegated wholesale.",
19
+ systemPrompt: `You are a subagent handling a delegated task. Complete it fully using the tools available.
20
+
21
+ The person who spawned you cannot answer questions, so make reasonable decisions and state the assumptions you made. Your final message is the only thing they will see — put the outcome first, then whatever detail they need to act on it.`,
22
+ tools: { deny: ["task"] }, // one level of delegation only
23
+ maxTurns: 50,
24
+ };
25
+ export const BUILTIN_SUBAGENTS = [EXPLORE_AGENT, GENERAL_AGENT];
26
+ export class SubagentRegistry {
27
+ agents = new Map();
28
+ constructor(definitions = BUILTIN_SUBAGENTS) {
29
+ for (const def of definitions)
30
+ this.register(def);
31
+ }
32
+ register(def) {
33
+ this.agents.set(def.name, def);
34
+ return this;
35
+ }
36
+ get(name) {
37
+ return this.agents.get(name);
38
+ }
39
+ list() {
40
+ return [...this.agents.values()];
41
+ }
42
+ summaries() {
43
+ return this.list().map(({ name, description }) => ({ name, description }));
44
+ }
45
+ }
46
+ // ---------------------------------------------------------------------------
47
+ // Markdown-defined subagents
48
+ // ---------------------------------------------------------------------------
49
+ /**
50
+ * Load subagents from `agents/**‍/*.md`, the convention opencode uses:
51
+ * YAML frontmatter is the configuration, the body is the system prompt.
52
+ *
53
+ * A markdown file is a better home for a subagent than a JSON config field —
54
+ * the prompt is the bulk of the definition, and prompts do not survive being
55
+ * escaped into JSON strings with any readability.
56
+ */
57
+ export async function loadMarkdownSubagents(cwd, extraDirs = []) {
58
+ const roots = [
59
+ userPath("agents"),
60
+ path.join(cwd, ".oharness", "agents"),
61
+ ...extraDirs.map((dir) => path.resolve(cwd, dir)),
62
+ ];
63
+ // Keyed by name so a project definition shadows a user one.
64
+ const byName = new Map();
65
+ for (const root of roots) {
66
+ for (const definition of await scanAgentDir(root)) {
67
+ byName.set(definition.name, definition);
68
+ }
69
+ }
70
+ return [...byName.values()];
71
+ }
72
+ async function scanAgentDir(root, prefix = "") {
73
+ let entries;
74
+ try {
75
+ entries = await readdir(root, { withFileTypes: true });
76
+ }
77
+ catch {
78
+ return [];
79
+ }
80
+ const out = [];
81
+ for (const entry of entries) {
82
+ const full = path.join(root, entry.name);
83
+ if (entry.isDirectory()) {
84
+ out.push(...(await scanAgentDir(full, prefix ? `${prefix}/${entry.name}` : entry.name)));
85
+ continue;
86
+ }
87
+ if (!entry.name.endsWith(".md"))
88
+ continue;
89
+ let raw;
90
+ try {
91
+ raw = await readFile(full, "utf8");
92
+ }
93
+ catch {
94
+ continue;
95
+ }
96
+ const parsed = parseAgentMarkdown(raw, entry.name.replace(/\.md$/, ""), prefix);
97
+ if (parsed)
98
+ out.push(parsed);
99
+ }
100
+ return out;
101
+ }
102
+ /** Parse one agent markdown file. Returns undefined if it cannot be selected. */
103
+ export function parseAgentMarkdown(raw, fallbackName, prefix = "") {
104
+ const { frontmatter, body } = splitFrontmatter(raw);
105
+ const name = frontmatter["name"]?.trim() || (prefix ? `${prefix}/${fallbackName}` : fallbackName);
106
+ const description = frontmatter["description"]?.trim();
107
+ const systemPrompt = body.trim();
108
+ // Both are load-bearing: the description is how the model picks this agent,
109
+ // and without a prompt it is just the default agent under another name.
110
+ if (!description || !systemPrompt)
111
+ return undefined;
112
+ const definition = { name, description, systemPrompt };
113
+ if (frontmatter["model"])
114
+ definition.model = frontmatter["model"].trim();
115
+ const maxTurns = Number(frontmatter["maxTurns"] ?? frontmatter["max_turns"]);
116
+ if (Number.isInteger(maxTurns) && maxTurns > 0)
117
+ definition.maxTurns = maxTurns;
118
+ const allow = splitList(frontmatter["tools"] ?? frontmatter["allowedTools"]);
119
+ const deny = splitList(frontmatter["denyTools"] ?? frontmatter["disallowedTools"]);
120
+ if (allow.length || deny.length) {
121
+ definition.tools = {
122
+ ...(allow.length ? { allow } : {}),
123
+ ...(deny.length ? { deny } : {}),
124
+ };
125
+ }
126
+ return definition;
127
+ }
128
+ /** Accept both `tools: read, grep` and `tools: [read, grep]`. */
129
+ function splitList(value) {
130
+ if (!value)
131
+ return [];
132
+ return value
133
+ .replace(/^\[|\]$/g, "")
134
+ .split(",")
135
+ .map((item) => item.trim().replace(/^["']|["']$/g, ""))
136
+ .filter((item) => item !== "");
137
+ }
138
+ //# sourceMappingURL=subagent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subagent.js","sourceRoot":"","sources":["../../src/agent/subagent.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { Message } from "../core/types.ts";
2
+ /**
3
+ * A short name taken from the first prompt.
4
+ *
5
+ * The first line only, and cut on a word boundary: a wrapped sentence fragment
6
+ * in a narrow sidebar is harder to scan than a clean truncation.
7
+ */
8
+ export declare function titleFromMessage(message: Message): string | undefined;
@@ -0,0 +1,23 @@
1
+ import { messageText } from "../core/types.js";
2
+ /**
3
+ * A short name taken from the first prompt.
4
+ *
5
+ * The first line only, and cut on a word boundary: a wrapped sentence fragment
6
+ * in a narrow sidebar is harder to scan than a clean truncation.
7
+ */
8
+ export function titleFromMessage(message) {
9
+ const text = messageText(message).trim();
10
+ if (!text)
11
+ return undefined;
12
+ const line = text.split("\n").find((candidate) => candidate.trim() !== "")?.trim();
13
+ if (!line)
14
+ return undefined;
15
+ if (line.length <= 60)
16
+ return line;
17
+ const cut = line.slice(0, 60);
18
+ const lastSpace = cut.lastIndexOf(" ");
19
+ // CJK has no spaces, so a space-based cut would return the whole 60 chars —
20
+ // which is the right answer there, not a bug.
21
+ return (lastSpace > 30 ? cut.slice(0, lastSpace) : cut) + "…";
22
+ }
23
+ //# sourceMappingURL=titles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"titles.js","sourceRoot":"","sources":["../../src/agent/titles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAG/C;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAgB;IAC/C,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;IACzC,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAC;IAE5B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,SAAiB,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC;IAC3F,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAC;IAC5B,IAAI,IAAI,CAAC,MAAM,IAAI,EAAE;QAAE,OAAO,IAAI,CAAC;IAEnC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC9B,MAAM,SAAS,GAAG,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACvC,4EAA4E;IAC5E,8CAA8C;IAC9C,OAAO,CAAC,SAAS,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;AAChE,CAAC"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Where the agent is allowed to write.
3
+ *
4
+ * **This is a policy gate, not a sandbox.** It inspects the arguments a tool
5
+ * was called with and refuses paths outside the allowed roots. It does not
6
+ * confine the process: a shell command can reach any path the user can, and
7
+ * `bash -c 'python -c "..."'` defeats any amount of argument inspection.
8
+ *
9
+ * Real confinement needs the OS — seatbelt on macOS, Landlock or a container
10
+ * on Linux — which this harness does not set up. Calling this a sandbox would
11
+ * promise a guarantee it cannot keep, so it is deliberately named a policy.
12
+ * Treat it as a guard against mistakes, not against a determined command.
13
+ */
14
+ export type WorkspaceMode = "read-only" | "workspace-write" | "full-access";
15
+ export interface WorkspacePolicy {
16
+ mode: WorkspaceMode;
17
+ /** Absolute roots that may be written to, beyond the working directory. */
18
+ writableRoots: string[];
19
+ /**
20
+ * Absolute roots that may be read. Always a superset of `writableRoots` —
21
+ * a file the agent is allowed to rewrite it is plainly allowed to open — so
22
+ * the two are separate only to let a directory be readable without also
23
+ * being writable.
24
+ */
25
+ readableRoots: string[];
26
+ }
27
+ export interface WriteCheck {
28
+ allowed: boolean;
29
+ reason?: string;
30
+ /** The path that failed, for the message shown to the model. */
31
+ offending?: string;
32
+ }
33
+ export declare function createPolicy(cwd: string, opts?: {
34
+ mode?: WorkspaceMode;
35
+ writableRoots?: string[];
36
+ readableRoots?: string[];
37
+ }): WorkspacePolicy;
38
+ /** Whether a resolved path falls inside one of the writable roots. */
39
+ export declare function isWritable(policy: WorkspacePolicy, target: string): boolean;
40
+ /** Whether a resolved path falls inside one of the readable roots. */
41
+ export declare function isReadable(policy: WorkspacePolicy, target: string): boolean;
42
+ /**
43
+ * Resolve a model-supplied path and confine it to the roots for that access.
44
+ *
45
+ * Throws instead of returning a flag because every caller turns a refusal into
46
+ * the same tool error, and a path that fell outside is never usable anyway.
47
+ *
48
+ * A missing policy means the embedder never asked for one, so this keeps the
49
+ * old behaviour of confining everything to the working directory rather than
50
+ * silently widening to whatever the default policy would have allowed.
51
+ */
52
+ export declare function resolveWithin(policy: WorkspacePolicy | undefined, cwd: string, input: string, access: "read" | "write"): string;
53
+ /**
54
+ * Check a tool call against the policy.
55
+ *
56
+ * Only the tools whose arguments name a path are checked. A `bash` command is
57
+ * scanned for obvious out-of-root writes, which catches a typo'd path but is
58
+ * explicitly not a security boundary — see the note at the top of this file.
59
+ */
60
+ export declare function checkWrite(policy: WorkspacePolicy, toolName: string, input: unknown, cwd: string): WriteCheck;
@@ -0,0 +1,156 @@
1
+ import path from "node:path";
2
+ import { expandHome, userPath, within } from "../paths.js";
3
+ export function createPolicy(cwd, opts = {}) {
4
+ const resolve = (roots) => dedupeRoots(roots.map((root) => path.resolve(cwd, expandHome(root))));
5
+ const writableRoots = resolve([cwd, ...userExtensionRoots(), ...(opts.writableRoots ?? [])]);
6
+ return {
7
+ mode: opts.mode ?? "workspace-write",
8
+ writableRoots,
9
+ readableRoots: resolve([...writableRoots, ...(opts.readableRoots ?? [])]),
10
+ };
11
+ }
12
+ /**
13
+ * The user-level directories the agent is expected to write to.
14
+ *
15
+ * The skill, subagent and plugin registries all *read* from these, so leaving
16
+ * them out made installing a user-level skill impossible while the harness
17
+ * happily loaded one that had arrived there by some other means. A directory
18
+ * that is read but never writable is a split with no rationale behind it.
19
+ *
20
+ * Deliberately these three rather than `userDir()` wholesale: the same
21
+ * directory holds `auth.json`, and handing the agent the credential file by
22
+ * default is a different decision than letting it drop a `SKILL.md`. Anyone
23
+ * who does want the whole tree can name it in `writableRoots`.
24
+ */
25
+ function userExtensionRoots() {
26
+ return [userPath("skills"), userPath("agents"), userPath("plugins")];
27
+ }
28
+ /** Whether a resolved path falls inside one of the writable roots. */
29
+ export function isWritable(policy, target) {
30
+ if (policy.mode === "full-access")
31
+ return true;
32
+ if (policy.mode === "read-only")
33
+ return false;
34
+ return within(policy.writableRoots, path.resolve(target));
35
+ }
36
+ /** Whether a resolved path falls inside one of the readable roots. */
37
+ export function isReadable(policy, target) {
38
+ if (policy.mode === "full-access")
39
+ return true;
40
+ return within(policy.readableRoots, path.resolve(target));
41
+ }
42
+ /**
43
+ * Resolve a model-supplied path and confine it to the roots for that access.
44
+ *
45
+ * Throws instead of returning a flag because every caller turns a refusal into
46
+ * the same tool error, and a path that fell outside is never usable anyway.
47
+ *
48
+ * A missing policy means the embedder never asked for one, so this keeps the
49
+ * old behaviour of confining everything to the working directory rather than
50
+ * silently widening to whatever the default policy would have allowed.
51
+ */
52
+ export function resolveWithin(policy, cwd, input, access) {
53
+ const resolved = path.resolve(cwd, expandHome(input));
54
+ if (!policy) {
55
+ if (within([path.resolve(cwd)], resolved))
56
+ return resolved;
57
+ throw new Error(`path escapes the working directory: ${input} (resolved to ${resolved})`);
58
+ }
59
+ if (access === "read") {
60
+ if (isReadable(policy, resolved))
61
+ return resolved;
62
+ throw new Error(`path is outside the readable roots: ${input} (resolved to ${resolved}; readable: ${policy.readableRoots.join(", ")})`);
63
+ }
64
+ if (policy.mode === "read-only") {
65
+ throw new Error(`the workspace is read-only; ${input} may not be modified`);
66
+ }
67
+ if (isWritable(policy, resolved))
68
+ return resolved;
69
+ throw new Error(`path is outside the writable roots: ${input} (resolved to ${resolved}; writable: ${policy.writableRoots.join(", ")})`);
70
+ }
71
+ /**
72
+ * Check a tool call against the policy.
73
+ *
74
+ * Only the tools whose arguments name a path are checked. A `bash` command is
75
+ * scanned for obvious out-of-root writes, which catches a typo'd path but is
76
+ * explicitly not a security boundary — see the note at the top of this file.
77
+ */
78
+ export function checkWrite(policy, toolName, input, cwd) {
79
+ if (policy.mode === "full-access")
80
+ return { allowed: true };
81
+ const record = (input ?? {});
82
+ if (toolName === "write" || toolName === "edit") {
83
+ const target = record["path"];
84
+ if (typeof target !== "string")
85
+ return { allowed: true };
86
+ const resolved = path.resolve(cwd, expandHome(target));
87
+ if (policy.mode === "read-only") {
88
+ return {
89
+ allowed: false,
90
+ offending: target,
91
+ reason: "the workspace is read-only; no file may be modified",
92
+ };
93
+ }
94
+ return isWritable(policy, resolved)
95
+ ? { allowed: true }
96
+ : {
97
+ allowed: false,
98
+ offending: target,
99
+ reason: `${resolved} is outside the writable roots (${policy.writableRoots.join(", ")})`,
100
+ };
101
+ }
102
+ if (toolName === "bash") {
103
+ const command = record["command"];
104
+ if (typeof command !== "string")
105
+ return { allowed: true };
106
+ if (policy.mode === "read-only" && looksMutating(command)) {
107
+ return {
108
+ allowed: false,
109
+ offending: command,
110
+ reason: "the workspace is read-only; this command appears to modify state",
111
+ };
112
+ }
113
+ const escaping = absolutePathsIn(command).find((candidate) => !isWritable(policy, candidate));
114
+ if (escaping && looksMutating(command)) {
115
+ return {
116
+ allowed: false,
117
+ offending: escaping,
118
+ reason: `${escaping} is outside the writable roots (${policy.writableRoots.join(", ")})`,
119
+ };
120
+ }
121
+ }
122
+ return { allowed: true };
123
+ }
124
+ /**
125
+ * A rough test for whether a shell command changes anything.
126
+ *
127
+ * Kept deliberately coarse: false positives cost an approval prompt, false
128
+ * negatives cost nothing extra because this was never a security boundary.
129
+ */
130
+ function looksMutating(command) {
131
+ return /\b(rm|mv|cp|touch|mkdir|rmdir|dd|chmod|chown|ln|truncate|tee|install)\b|>>?[^&|]/.test(command);
132
+ }
133
+ /**
134
+ * Absolute paths appearing as bare words or after a redirect.
135
+ *
136
+ * `~` is expanded the way the shell expands it before the command ever runs.
137
+ * Without that the leading `~` simply went unmatched and `~/.oharness/skills`
138
+ * was reported back as `/.oharness/skills` — a path that exists nowhere, in a
139
+ * message whose entire job is to tell the reader what was refused.
140
+ */
141
+ function absolutePathsIn(command) {
142
+ const matches = command.match(/(?<![\w-])~?\/[^\s'";|&)]+/g) ?? [];
143
+ return matches.filter((candidate) => candidate.length > 1).map(expandHome);
144
+ }
145
+ /** Drop roots already covered by an ancestor, so messages stay readable. */
146
+ function dedupeRoots(roots) {
147
+ const sorted = [...new Set(roots)].sort((a, b) => a.length - b.length);
148
+ const kept = [];
149
+ for (const root of sorted) {
150
+ if (kept.some((parent) => root === parent || root.startsWith(parent + path.sep)))
151
+ continue;
152
+ kept.push(root);
153
+ }
154
+ return kept;
155
+ }
156
+ //# sourceMappingURL=workspace.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace.js","sourceRoot":"","sources":["../../src/agent/workspace.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,94 @@
1
+ import type { ArtifactKind } from "./store.ts";
2
+ /**
3
+ * Publishing an artifact to the website, so it has a URL someone else can open.
4
+ *
5
+ * The harness runs on loopback with a token that changes every start, and its
6
+ * artifact store is memory. None of that can be shared, so publishing is a
7
+ * copy — the content leaves this machine and lands in the site's database,
8
+ * where an account and a row-level security policy decide who may read it.
9
+ *
10
+ * It goes through the site's own API rather than straight at Supabase, for the
11
+ * same reason signing in does (see auth/providers/account.ts, which posts to
12
+ * `/api/desktop/exchange` rather than talking to the auth server directly):
13
+ * the schema stays private, validation and limits live in one place, and a
14
+ * three-versions-old CLI cannot be writing rows in a shape the site has since
15
+ * changed.
16
+ *
17
+ * Identity follows the artifact, not the act of publishing. Publishing
18
+ * `sales-chart` twice updates one row and keeps one URL, because that is what
19
+ * the store already means by an id — see store.ts.
20
+ *
21
+ * What that does NOT mean is that the link tracks the artifact. The site holds
22
+ * a copy, and only another publish overwrites it: revise an artifact ten times
23
+ * without republishing and the link still shows the revision that was sent.
24
+ * That is the safer default — a link should not change under the person you
25
+ * gave it to — but it has to be visible, which is why the revision number
26
+ * travels back into the store and the panel says which one the link is on.
27
+ */
28
+ export type ArtifactVisibility = "unlisted" | "team";
29
+ export interface PublishRequest {
30
+ /** The session-scoped id. Stable across revisions, so the URL is too. */
31
+ sourceId: string;
32
+ title: string;
33
+ mime: string;
34
+ kind: ArtifactKind;
35
+ language?: string;
36
+ content: string;
37
+ version: number;
38
+ /**
39
+ * `unlisted` means the link is the credential; `team` additionally requires
40
+ * the reader to be signed in and in the team. There is deliberately no
41
+ * `public` — a listable, indexable surface is a different product decision
42
+ * from handing someone a link.
43
+ */
44
+ visibility: ArtifactVisibility;
45
+ /** Required when `visibility` is `team`; ignored otherwise. */
46
+ teamId?: string;
47
+ }
48
+ export interface PublishResult {
49
+ url: string;
50
+ /** The site's own id for the row. Unguessable; not the artifact's id. */
51
+ publicId: string;
52
+ visibility: ArtifactVisibility;
53
+ updated: boolean;
54
+ }
55
+ /**
56
+ * Kinds that may leave this machine.
57
+ *
58
+ * Everything the store can hold, and the reason this list exists anyway: it is
59
+ * the seam where a future decision to refuse a kind for publishing lands. The
60
+ * executable kind is the one that would go first, and it is allowed here only
61
+ * because the site serves it under a `sandbox` CSP directive — a header, which
62
+ * applies to the document however it is loaded, rather than an iframe
63
+ * attribute, which does not apply when someone opens the URL directly. See
64
+ * website/app/a/[id]/raw/route.ts.
65
+ */
66
+ export declare const PUBLISHABLE_KINDS: ArtifactKind[];
67
+ /** Refused before the request, so an oversized artifact costs no round trip. */
68
+ export declare const MAX_PUBLISHED_BYTES: number;
69
+ export declare function publishArtifact(request: PublishRequest, opts: {
70
+ accountToken: string | undefined;
71
+ fetchImpl?: typeof fetch;
72
+ }): Promise<PublishResult>;
73
+ export interface AccountTeam {
74
+ id: string;
75
+ name: string;
76
+ role: string;
77
+ }
78
+ /**
79
+ * The teams this account belongs to, for a frontend offering "publish to…".
80
+ *
81
+ * Answers with an empty list rather than throwing when the account is signed
82
+ * out or the site is unreachable: the caller's next move either way is to
83
+ * offer only an unlisted link, and a thrown error would make a Publish menu
84
+ * fail to open because a feature nobody asked for could not be enumerated.
85
+ */
86
+ export declare function accountTeams(opts: {
87
+ accountToken: string | undefined;
88
+ fetchImpl?: typeof fetch;
89
+ }): Promise<AccountTeam[]>;
90
+ /** Withdraw a published artifact. The URL stops resolving for everyone. */
91
+ export declare function unpublishArtifact(sourceId: string, opts: {
92
+ accountToken: string | undefined;
93
+ fetchImpl?: typeof fetch;
94
+ }): Promise<void>;
@@ -0,0 +1,104 @@
1
+ import { errorMessage } from "../core/errors.js";
2
+ import { parseJson } from "../util/json.js";
3
+ import { siteUrl } from "../auth/providers/account.js";
4
+ /**
5
+ * Kinds that may leave this machine.
6
+ *
7
+ * Everything the store can hold, and the reason this list exists anyway: it is
8
+ * the seam where a future decision to refuse a kind for publishing lands. The
9
+ * executable kind is the one that would go first, and it is allowed here only
10
+ * because the site serves it under a `sandbox` CSP directive — a header, which
11
+ * applies to the document however it is loaded, rather than an iframe
12
+ * attribute, which does not apply when someone opens the URL directly. See
13
+ * website/app/a/[id]/raw/route.ts.
14
+ */
15
+ export const PUBLISHABLE_KINDS = [
16
+ "text",
17
+ "markdown",
18
+ "code",
19
+ "image",
20
+ "vector",
21
+ "page",
22
+ ];
23
+ /** Refused before the request, so an oversized artifact costs no round trip. */
24
+ export const MAX_PUBLISHED_BYTES = 1024 * 1024;
25
+ export async function publishArtifact(request, opts) {
26
+ if (!opts.accountToken) {
27
+ throw new Error("Publishing needs an OHarness account. Sign in first, then publish again.");
28
+ }
29
+ if (!PUBLISHABLE_KINDS.includes(request.kind)) {
30
+ throw new Error(`Artifacts of kind ${request.kind} cannot be published.`);
31
+ }
32
+ const bytes = Buffer.byteLength(request.content, "utf8");
33
+ if (bytes > MAX_PUBLISHED_BYTES) {
34
+ throw new Error(`Artifact is ${bytes} bytes, over the ${MAX_PUBLISHED_BYTES} byte publishing limit.`);
35
+ }
36
+ const send = opts.fetchImpl ?? fetch;
37
+ let response;
38
+ try {
39
+ response = await send(`${siteUrl()}/api/artifacts`, {
40
+ method: "POST",
41
+ headers: {
42
+ "content-type": "application/json",
43
+ accept: "application/json",
44
+ // The account's Supabase access token, the same one sync uses. The
45
+ // site validates it with the auth server rather than trusting it.
46
+ authorization: `Bearer ${opts.accountToken}`,
47
+ },
48
+ body: JSON.stringify(request),
49
+ });
50
+ }
51
+ catch (err) {
52
+ throw new Error(`could not reach ${siteUrl()}: ${errorMessage(err)}`);
53
+ }
54
+ const body = parseJson(await response.text());
55
+ if (!response.ok) {
56
+ throw new Error((body.ok && body.value.error) || `publishing failed with HTTP ${response.status}`);
57
+ }
58
+ if (!body.ok || !body.value.url || !body.value.id) {
59
+ throw new Error("the site returned no link for the artifact");
60
+ }
61
+ return {
62
+ url: body.value.url,
63
+ publicId: body.value.id,
64
+ visibility: request.visibility,
65
+ updated: body.value.updated === true,
66
+ };
67
+ }
68
+ /**
69
+ * The teams this account belongs to, for a frontend offering "publish to…".
70
+ *
71
+ * Answers with an empty list rather than throwing when the account is signed
72
+ * out or the site is unreachable: the caller's next move either way is to
73
+ * offer only an unlisted link, and a thrown error would make a Publish menu
74
+ * fail to open because a feature nobody asked for could not be enumerated.
75
+ */
76
+ export async function accountTeams(opts) {
77
+ if (!opts.accountToken)
78
+ return [];
79
+ const send = opts.fetchImpl ?? fetch;
80
+ try {
81
+ const response = await send(`${siteUrl()}/api/teams`, {
82
+ headers: { authorization: `Bearer ${opts.accountToken}`, accept: "application/json" },
83
+ });
84
+ if (!response.ok)
85
+ return [];
86
+ const body = parseJson(await response.text());
87
+ return body.ok && Array.isArray(body.value.teams) ? body.value.teams : [];
88
+ }
89
+ catch {
90
+ return [];
91
+ }
92
+ }
93
+ /** Withdraw a published artifact. The URL stops resolving for everyone. */
94
+ export async function unpublishArtifact(sourceId, opts) {
95
+ if (!opts.accountToken)
96
+ throw new Error("Not signed in.");
97
+ const send = opts.fetchImpl ?? fetch;
98
+ const response = await send(`${siteUrl()}/api/artifacts?sourceId=${encodeURIComponent(sourceId)}`, { method: "DELETE", headers: { authorization: `Bearer ${opts.accountToken}` } });
99
+ if (!response.ok) {
100
+ const body = parseJson(await response.text());
101
+ throw new Error((body.ok && body.value.error) || `withdrawing failed with HTTP ${response.status}`);
102
+ }
103
+ }
104
+ //# sourceMappingURL=publish.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publish.js","sourceRoot":"","sources":["../../src/artifacts/publish.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,8BAA8B,CAAC;AA4DvD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAmB;IAC/C,MAAM;IACN,UAAU;IACV,MAAM;IACN,OAAO;IACP,QAAQ;IACR,MAAM;CACP,CAAC;AAEF,gFAAgF;AAChF,MAAM,CAAC,MAAM,mBAAmB,GAAG,IAAI,GAAG,IAAI,CAAC;AAE/C,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,OAAuB,EACvB,IAAoE;IAEpE,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CACb,0EAA0E,CAC3E,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9C,MAAM,IAAI,KAAK,CAAC,qBAAqB,OAAO,CAAC,IAAI,uBAAuB,CAAC,CAAC;IAC5E,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACzD,IAAI,KAAK,GAAG,mBAAmB,EAAE,CAAC;QAChC,MAAM,IAAI,KAAK,CACb,eAAe,KAAK,oBAAoB,mBAAmB,yBAAyB,CACrF,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC;IACrC,IAAI,QAAkB,CAAC;IACvB,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,IAAI,CAAC,GAAG,OAAO,EAAE,gBAAgB,EAAE;YAClD,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,MAAM,EAAE,kBAAkB;gBAC1B,mEAAmE;gBACnE,kEAAkE;gBAClE,aAAa,EAAE,UAAU,IAAI,CAAC,YAAY,EAAE;aAC7C;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;SAC9B,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,mBAAmB,OAAO,EAAE,KAAK,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACxE,CAAC;IAED,MAAM,IAAI,GAAG,SAAS,CACpB,MAAM,QAAQ,CAAC,IAAI,EAAE,CACtB,CAAC;IACF,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CACb,CAAC,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,+BAA+B,QAAQ,CAAC,MAAM,EAAE,CAClF,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC;QAClD,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;IAChE,CAAC;IAED,OAAO;QACL,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG;QACnB,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE;QACvB,UAAU,EAAE,OAAO,CAAC,UAAU;QAC9B,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,KAAK,IAAI;KACrC,CAAC;AACJ,CAAC;AAQD;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAGlC;IACC,IAAI,CAAC,IAAI,CAAC,YAAY;QAAE,OAAO,EAAE,CAAC;IAClC,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC;IACrC,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,GAAG,OAAO,EAAE,YAAY,EAAE;YACpD,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,IAAI,CAAC,YAAY,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE;SACtF,CAAC,CAAC;QACH,IAAI,CAAC,QAAQ,CAAC,EAAE;YAAE,OAAO,EAAE,CAAC;QAC5B,MAAM,IAAI,GAAG,SAAS,CAA4B,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;QACzE,OAAO,IAAI,CAAC,EAAE,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IAC5E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,2EAA2E;AAC3E,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,QAAgB,EAChB,IAAoE;IAEpE,IAAI,CAAC,IAAI,CAAC,YAAY;QAAE,MAAM,IAAI,KAAK,CAAC,gBAAgB,CAAC,CAAC;IAC1D,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC;IACrC,MAAM,QAAQ,GAAG,MAAM,IAAI,CACzB,GAAG,OAAO,EAAE,2BAA2B,kBAAkB,CAAC,QAAQ,CAAC,EAAE,EACrE,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,IAAI,CAAC,YAAY,EAAE,EAAE,EAAE,CAChF,CAAC;IACF,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,GAAG,SAAS,CAAqB,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;QAClE,MAAM,IAAI,KAAK,CACb,CAAC,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,gCAAgC,QAAQ,CAAC,MAAM,EAAE,CACnF,CAAC;IACJ,CAAC;AACH,CAAC"}