harnery 0.8.0 → 0.10.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 (234) hide show
  1. package/README.md +2 -0
  2. package/dist/commander.d.ts.map +1 -1
  3. package/dist/commander.js +8 -0
  4. package/dist/commands/agents.d.ts +24 -0
  5. package/dist/commands/agents.d.ts.map +1 -1
  6. package/dist/commands/agents.js +244 -13
  7. package/dist/commands/backup.d.ts +5 -4
  8. package/dist/commands/backup.d.ts.map +1 -1
  9. package/dist/commands/backup.js +15 -14
  10. package/dist/commands/browse.d.ts.map +1 -1
  11. package/dist/commands/browse.js +27 -1
  12. package/dist/commands/claude-desktop.d.ts +19 -0
  13. package/dist/commands/claude-desktop.d.ts.map +1 -0
  14. package/dist/commands/claude-desktop.js +168 -0
  15. package/dist/commands/context.d.ts.map +1 -1
  16. package/dist/commands/context.js +149 -1
  17. package/dist/commands/doctor.d.ts.map +1 -1
  18. package/dist/commands/doctor.js +37 -0
  19. package/dist/commands/eml.d.ts +32 -0
  20. package/dist/commands/eml.d.ts.map +1 -1
  21. package/dist/commands/eml.js +16 -3
  22. package/dist/commands/grep.d.ts +35 -2
  23. package/dist/commands/grep.d.ts.map +1 -1
  24. package/dist/commands/grep.js +427 -139
  25. package/dist/commands/harness.d.ts +11 -0
  26. package/dist/commands/harness.d.ts.map +1 -0
  27. package/dist/commands/harness.js +118 -0
  28. package/dist/commands/init.d.ts +15 -5
  29. package/dist/commands/init.d.ts.map +1 -1
  30. package/dist/commands/init.js +125 -14
  31. package/dist/commands/presence.d.ts +9 -4
  32. package/dist/commands/presence.d.ts.map +1 -1
  33. package/dist/commands/presence.js +88 -5
  34. package/dist/commands/relay.d.ts +9 -0
  35. package/dist/commands/relay.d.ts.map +1 -0
  36. package/dist/commands/relay.js +143 -0
  37. package/dist/commands/sync.d.ts.map +1 -1
  38. package/dist/commands/sync.js +5 -0
  39. package/dist/commands/workflow.d.ts +4 -0
  40. package/dist/commands/workflow.d.ts.map +1 -0
  41. package/dist/commands/workflow.js +82 -0
  42. package/dist/core/agents/canonical-emit.d.ts +15 -0
  43. package/dist/core/agents/canonical-emit.d.ts.map +1 -1
  44. package/dist/core/agents/canonical-emit.js +41 -3
  45. package/dist/core/agents/cli.js +14 -5
  46. package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
  47. package/dist/core/agents/render/prompt-context.js +44 -3
  48. package/dist/core/agents/render/session-context.d.ts.map +1 -1
  49. package/dist/core/agents/render/session-context.js +26 -1
  50. package/dist/core/agents/rules/claim-conflict.d.ts.map +1 -1
  51. package/dist/core/agents/rules/claim-conflict.js +26 -1
  52. package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -1
  53. package/dist/core/agents/rules/commit-conflict.js +4 -37
  54. package/dist/core/agents/rules/stop-hook.d.ts +8 -0
  55. package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
  56. package/dist/core/agents/rules/stop-hook.js +8 -0
  57. package/dist/core/agents/session-events.d.ts.map +1 -1
  58. package/dist/core/agents/session-events.js +16 -43
  59. package/dist/core/agents/state/heartbeat-projector.d.ts +2 -0
  60. package/dist/core/agents/state/heartbeat-projector.d.ts.map +1 -1
  61. package/dist/core/agents/state/heartbeat-projector.js +24 -4
  62. package/dist/core/agents/state/heartbeat-writer.d.ts +16 -2
  63. package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
  64. package/dist/core/agents/state/heartbeat-writer.js +24 -7
  65. package/dist/core/agents/state/names.d.ts +38 -3
  66. package/dist/core/agents/state/names.d.ts.map +1 -1
  67. package/dist/core/agents/state/names.js +46 -7
  68. package/dist/core/agents/state/pidmap.d.ts +5 -1
  69. package/dist/core/agents/state/pidmap.d.ts.map +1 -1
  70. package/dist/core/agents/state/pidmap.js +58 -2
  71. package/dist/core/agents/state/stale-sweep.d.ts +3 -2
  72. package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
  73. package/dist/core/agents/state/stale-sweep.js +6 -6
  74. package/dist/core/config.d.ts +77 -11
  75. package/dist/core/config.d.ts.map +1 -1
  76. package/dist/core/config.js +210 -25
  77. package/dist/core/context/index.d.ts +144 -0
  78. package/dist/core/context/index.d.ts.map +1 -0
  79. package/dist/core/context/index.js +380 -0
  80. package/dist/core/harnesses/bench.d.ts +29 -0
  81. package/dist/core/harnesses/bench.d.ts.map +1 -0
  82. package/dist/core/harnesses/bench.js +151 -0
  83. package/dist/core/harnesses/index.d.ts +9 -0
  84. package/dist/core/harnesses/index.d.ts.map +1 -0
  85. package/dist/core/harnesses/index.js +4 -0
  86. package/dist/core/harnesses/profiles.d.ts +62 -0
  87. package/dist/core/harnesses/profiles.d.ts.map +1 -0
  88. package/dist/core/harnesses/profiles.js +115 -0
  89. package/dist/core/harnesses/registry.d.ts +15 -0
  90. package/dist/core/harnesses/registry.d.ts.map +1 -0
  91. package/dist/core/harnesses/registry.js +113 -0
  92. package/dist/core/harnesses/types.d.ts +64 -0
  93. package/dist/core/harnesses/types.d.ts.map +1 -0
  94. package/dist/core/harnesses/types.js +20 -0
  95. package/dist/core/hooks/cli.js +309 -66
  96. package/dist/core/hooks/events/schema.d.ts +42 -1
  97. package/dist/core/hooks/events/schema.d.ts.map +1 -1
  98. package/dist/core/hooks/harness/events.d.ts +7 -0
  99. package/dist/core/hooks/harness/events.d.ts.map +1 -1
  100. package/dist/core/hooks/harness/events.js +4 -0
  101. package/dist/core/hooks/harness/parse.d.ts +1 -1
  102. package/dist/core/hooks/harness/parse.d.ts.map +1 -1
  103. package/dist/core/hooks/harness/parse.js +4 -0
  104. package/dist/core/hooks/resolve/coord-root.d.ts +11 -0
  105. package/dist/core/hooks/resolve/coord-root.d.ts.map +1 -1
  106. package/dist/core/hooks/resolve/coord-root.js +28 -6
  107. package/dist/core/presence/blob.d.ts +40 -0
  108. package/dist/core/presence/blob.d.ts.map +1 -0
  109. package/dist/core/presence/blob.js +91 -0
  110. package/dist/core/presence/git.d.ts +68 -0
  111. package/dist/core/presence/git.d.ts.map +1 -0
  112. package/dist/core/presence/git.js +173 -0
  113. package/dist/core/presence/index.d.ts +74 -0
  114. package/dist/core/presence/index.d.ts.map +1 -0
  115. package/dist/core/presence/index.js +224 -0
  116. package/dist/core/presence/relay-client.d.ts +39 -0
  117. package/dist/core/presence/relay-client.d.ts.map +1 -0
  118. package/dist/core/presence/relay-client.js +295 -0
  119. package/dist/core/presence/relay-protocol.d.ts +94 -0
  120. package/dist/core/presence/relay-protocol.d.ts.map +1 -0
  121. package/dist/core/presence/relay-protocol.js +169 -0
  122. package/dist/core/workflow/billing.d.ts +48 -0
  123. package/dist/core/workflow/billing.d.ts.map +1 -0
  124. package/dist/core/workflow/billing.js +108 -0
  125. package/dist/core/workflow/child-env.d.ts +30 -0
  126. package/dist/core/workflow/child-env.d.ts.map +1 -0
  127. package/dist/core/workflow/child-env.js +43 -0
  128. package/dist/core/workflow/engine.d.ts +21 -0
  129. package/dist/core/workflow/engine.d.ts.map +1 -0
  130. package/dist/core/workflow/engine.js +349 -0
  131. package/dist/core/workflow/harnesses.d.ts +17 -0
  132. package/dist/core/workflow/harnesses.d.ts.map +1 -0
  133. package/dist/core/workflow/harnesses.js +21 -0
  134. package/dist/core/workflow/spawn-claude.d.ts +25 -0
  135. package/dist/core/workflow/spawn-claude.d.ts.map +1 -0
  136. package/dist/core/workflow/spawn-claude.js +103 -0
  137. package/dist/core/workflow/spawn-codex.d.ts +22 -0
  138. package/dist/core/workflow/spawn-codex.d.ts.map +1 -0
  139. package/dist/core/workflow/spawn-codex.js +88 -0
  140. package/dist/core/workflow/spawn-cursor.d.ts +29 -0
  141. package/dist/core/workflow/spawn-cursor.d.ts.map +1 -0
  142. package/dist/core/workflow/spawn-cursor.js +91 -0
  143. package/dist/core/workflow/types.d.ts +152 -0
  144. package/dist/core/workflow/types.d.ts.map +1 -0
  145. package/dist/core/workflow/types.js +9 -0
  146. package/dist/core/workflow/validate.d.ts +15 -0
  147. package/dist/core/workflow/validate.d.ts.map +1 -0
  148. package/dist/core/workflow/validate.js +70 -0
  149. package/dist/lib/browser/client.d.ts +16 -0
  150. package/dist/lib/browser/client.d.ts.map +1 -1
  151. package/dist/lib/browser/client.js +22 -0
  152. package/dist/lib/browser/index.d.ts +1 -0
  153. package/dist/lib/browser/index.d.ts.map +1 -1
  154. package/dist/lib/browser/index.js +1 -0
  155. package/dist/lib/browser/launch-args.d.ts +21 -0
  156. package/dist/lib/browser/launch-args.d.ts.map +1 -0
  157. package/dist/lib/browser/launch-args.js +32 -0
  158. package/dist/lib/claude-desktop.d.ts +105 -0
  159. package/dist/lib/claude-desktop.d.ts.map +1 -0
  160. package/dist/lib/claude-desktop.js +217 -0
  161. package/dist/lib/docs-lint.d.ts.map +1 -1
  162. package/dist/lib/docs-lint.js +5 -2
  163. package/dist/lib/identities/assume.d.ts +51 -0
  164. package/dist/lib/identities/assume.d.ts.map +1 -0
  165. package/dist/lib/identities/assume.js +274 -0
  166. package/dist/lib/identities/index.d.ts +7 -7
  167. package/dist/lib/identities/index.d.ts.map +1 -1
  168. package/dist/lib/identities/index.js +24 -24
  169. package/dist/lib/instructions/templates.d.ts.map +1 -1
  170. package/dist/lib/instructions/templates.js +15 -2
  171. package/package.json +12 -1
  172. package/schemas/config.schema.json +88 -19
  173. package/src/commander.ts +8 -0
  174. package/src/commands/agents.ts +280 -14
  175. package/src/commands/backup.ts +17 -16
  176. package/src/commands/browse.ts +32 -0
  177. package/src/commands/claude-desktop.ts +215 -0
  178. package/src/commands/context.ts +174 -1
  179. package/src/commands/doctor.ts +44 -0
  180. package/src/commands/eml.ts +22 -4
  181. package/src/commands/grep.ts +535 -142
  182. package/src/commands/harness.ts +147 -0
  183. package/src/commands/init.ts +138 -17
  184. package/src/commands/presence.ts +111 -5
  185. package/src/commands/relay.ts +160 -0
  186. package/src/commands/sync.ts +4 -0
  187. package/src/commands/workflow.ts +123 -0
  188. package/src/core/agents/canonical-emit.ts +41 -3
  189. package/src/core/agents/cli.ts +15 -6
  190. package/src/core/agents/render/prompt-context.ts +48 -3
  191. package/src/core/agents/render/session-context.ts +27 -1
  192. package/src/core/agents/rules/claim-conflict.ts +26 -1
  193. package/src/core/agents/rules/commit-conflict.ts +4 -33
  194. package/src/core/agents/rules/stop-hook.ts +17 -0
  195. package/src/core/agents/session-events.ts +15 -39
  196. package/src/core/agents/state/heartbeat-projector.ts +23 -2
  197. package/src/core/agents/state/heartbeat-writer.ts +33 -7
  198. package/src/core/agents/state/names.ts +68 -9
  199. package/src/core/agents/state/pidmap.ts +59 -2
  200. package/src/core/agents/state/stale-sweep.ts +6 -11
  201. package/src/core/config.ts +271 -24
  202. package/src/core/context/index.ts +575 -0
  203. package/src/core/harnesses/bench.ts +214 -0
  204. package/src/core/harnesses/index.ts +31 -0
  205. package/src/core/harnesses/profiles.ts +141 -0
  206. package/src/core/harnesses/registry.ts +140 -0
  207. package/src/core/harnesses/types.ts +90 -0
  208. package/src/core/hooks/cli.ts +361 -65
  209. package/src/core/hooks/events/schema.ts +72 -0
  210. package/src/core/hooks/harness/events.ts +11 -0
  211. package/src/core/hooks/harness/parse.ts +7 -1
  212. package/src/core/hooks/resolve/coord-root.ts +28 -6
  213. package/src/core/presence/blob.ts +125 -0
  214. package/src/core/presence/git.ts +191 -0
  215. package/src/core/presence/index.ts +274 -0
  216. package/src/core/presence/relay-client.ts +322 -0
  217. package/src/core/presence/relay-protocol.ts +246 -0
  218. package/src/core/workflow/billing.ts +152 -0
  219. package/src/core/workflow/child-env.ts +47 -0
  220. package/src/core/workflow/engine.ts +403 -0
  221. package/src/core/workflow/harnesses.ts +33 -0
  222. package/src/core/workflow/spawn-claude.ts +118 -0
  223. package/src/core/workflow/spawn-codex.ts +93 -0
  224. package/src/core/workflow/spawn-cursor.ts +108 -0
  225. package/src/core/workflow/types.ts +160 -0
  226. package/src/core/workflow/validate.ts +75 -0
  227. package/src/lib/browser/client.ts +37 -0
  228. package/src/lib/browser/index.ts +1 -0
  229. package/src/lib/browser/launch-args.ts +33 -0
  230. package/src/lib/claude-desktop.ts +293 -0
  231. package/src/lib/docs-lint.ts +5 -2
  232. package/src/lib/identities/assume.ts +363 -0
  233. package/src/lib/identities/index.ts +28 -24
  234. package/src/lib/instructions/templates.ts +16 -2
@@ -0,0 +1,11 @@
1
+ import type { Command } from "commander";
2
+ import type { EmitContext } from "../commander.js";
3
+ import { type BenchResult, type HarnessBenchReport } from "../core/harnesses/index.js";
4
+ import type { HarnessProfile } from "../core/harnesses/types.js";
5
+ export declare function registerHarnessCommand(program: Command, emit: EmitContext): void;
6
+ export declare function renderProfileTable(profiles: readonly HarnessProfile[]): string;
7
+ export declare function renderProfile(profile: HarnessProfile): string;
8
+ export declare function renderBenchReport(report: HarnessBenchReport): string;
9
+ /** Kept exported so command tests can assert row semantics without model calls. */
10
+ export type { BenchResult };
11
+ //# sourceMappingURL=harness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harness.d.ts","sourceRoot":"","sources":["../../src/commands/harness.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EACL,KAAK,WAAW,EAEhB,KAAK,kBAAkB,EAExB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAYjE,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CA6DhF;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,GAAG,MAAM,CAU9E;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM,CAkB7D;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAcpE;AAgBD,mFAAmF;AACnF,YAAY,EAAE,WAAW,EAAE,CAAC"}
@@ -0,0 +1,118 @@
1
+ import { createBuiltinHarnessRegistry, runHarnessBench, } from "../core/harnesses/index.js";
2
+ const registry = createBuiltinHarnessRegistry();
3
+ export function registerHarnessCommand(program, emit) {
4
+ const command = program
5
+ .command("harness")
6
+ .description("Inspect registered harness capabilities and run their conformance bench.");
7
+ command
8
+ .command("list")
9
+ .description("List registered harness adapters and their high-signal capability claims.")
10
+ .option("--json", "Machine-readable profile catalog")
11
+ .action((opts) => {
12
+ const profiles = registry.list().map((adapter) => adapter.profile);
13
+ if (opts.json) {
14
+ emit.config({ format: "json" });
15
+ emit.data({ harnesses: profiles });
16
+ }
17
+ else {
18
+ emit.text(renderProfileTable(profiles));
19
+ }
20
+ });
21
+ command
22
+ .command("show <id>")
23
+ .description("Show one harness's complete capability declaration.")
24
+ .option("--json", "Machine-readable profile")
25
+ .action((id, opts) => {
26
+ const adapter = registry.get(id);
27
+ if (!adapter) {
28
+ emit.error({
29
+ code: "unknown_harness",
30
+ message: `unknown harness ${JSON.stringify(id)} (registered: ${registry.ids().join(", ")})`,
31
+ });
32
+ emit.setExitCode(1);
33
+ return;
34
+ }
35
+ if (opts.json) {
36
+ emit.config({ format: "json" });
37
+ emit.data(adapter.profile);
38
+ }
39
+ else {
40
+ emit.text(renderProfile(adapter.profile));
41
+ }
42
+ });
43
+ command
44
+ .command("bench [harnesses...]")
45
+ .description("Run the offline adapter-contract bench (no model calls); drift exits non-zero.")
46
+ .option("--require-installed", "Also fail when a registered vendor CLI is missing")
47
+ .option("--json", "Machine-readable conformance report")
48
+ .action((harnesses, opts) => {
49
+ try {
50
+ const report = runHarnessBench(registry, { harnesses });
51
+ if (opts.json) {
52
+ emit.config({ format: "json" });
53
+ emit.data(report);
54
+ }
55
+ else {
56
+ emit.text(renderBenchReport(report));
57
+ }
58
+ emit.setExitCode(report.drift || (opts.requireInstalled && report.skipped) ? 1 : 0);
59
+ }
60
+ catch (error) {
61
+ emit.error({ code: "harness_bench_failed", message: error.message });
62
+ emit.setExitCode(1);
63
+ }
64
+ });
65
+ }
66
+ export function renderProfileTable(profiles) {
67
+ const rows = profiles.map((profile) => [
68
+ profile.id,
69
+ profile.binary,
70
+ profile.capabilities.modelSelection.support,
71
+ profile.capabilities.effortSelection.support,
72
+ profile.capabilities.sessionId.support,
73
+ profile.capabilities.cost.support,
74
+ ]);
75
+ return renderTable(["HARNESS", "BINARY", "MODEL", "EFFORT", "SESSION", "COST"], rows);
76
+ }
77
+ export function renderProfile(profile) {
78
+ const lines = [
79
+ `${profile.displayName} (${profile.id})`,
80
+ `binary: ${profile.binary}`,
81
+ `integration: ${profile.integrationMode}`,
82
+ `auth: ${profile.authModel}`,
83
+ `model family: ${profile.modelFamily}`,
84
+ `effort values: ${profile.effortValues.join(", ") || "none"}`,
85
+ profile.verified
86
+ ? `verified: ${profile.verified.date} (${profile.verified.version})`
87
+ : "verified: not recorded",
88
+ "",
89
+ "capabilities:",
90
+ ];
91
+ for (const [dimension, claim] of Object.entries(profile.capabilities)) {
92
+ lines.push(` ${dimension.padEnd(18)} ${claim.support}${claim.note ? ` — ${claim.note}` : ""}`);
93
+ }
94
+ return lines.join("\n");
95
+ }
96
+ export function renderBenchReport(report) {
97
+ const rows = report.results.map((result) => [
98
+ result.harness,
99
+ result.dimension,
100
+ result.declared,
101
+ result.observed,
102
+ result.verdict,
103
+ ]);
104
+ const table = renderTable(["HARNESS", "DIMENSION", "DECLARED", "OBSERVED", "VERDICT"], rows);
105
+ const summary = Object.entries(report.summary)
106
+ .filter(([, count]) => count > 0)
107
+ .map(([verdict, count]) => `${verdict}=${count}`)
108
+ .join(", ");
109
+ return `${table}\n\nmode: ${report.mode} (no model calls)\n${summary}`;
110
+ }
111
+ function renderTable(headers, rows) {
112
+ const widths = headers.map((header, index) => Math.max(header.length, ...rows.map((row) => row[index]?.length ?? 0)));
113
+ const line = (cells) => cells
114
+ .map((cell, index) => cell.padEnd(widths[index]))
115
+ .join(" ")
116
+ .trimEnd();
117
+ return [line(headers), line(widths.map((width) => "-".repeat(width))), ...rows.map(line)].join("\n");
118
+ }
@@ -23,11 +23,12 @@ export type { HookGroup, SettingsFile };
23
23
  export declare function registerInitCommand(program: Command, emit: EmitContext, binName?: string): void;
24
24
  /**
25
25
  * Merge agent-hook entries into a harness settings object in place, idempotently.
26
- * Preserves every existing hook; skips events already wired to
27
- * `agent-hook <subcommand>`. Honors the harness's entry shape (Claude/Codex nest
28
- * under an inner `hooks` array; Cursor uses a flat `{ command }`) and ensures the
29
- * root `version` key when the harness requires one. Pure (no fs/git) so it's
30
- * unit-testable.
26
+ * Preserves every existing hook; rewrites events already wired to
27
+ * `agent-hook <subcommand>` when their command string is stale (e.g. an older
28
+ * harnery wired a bare relative path). Honors the harness's entry shape
29
+ * (Claude/Codex nest under an inner `hooks` array; Cursor uses a flat
30
+ * `{ command }`) and ensures the root `version` key when the harness requires
31
+ * one. Pure (no fs/git) so it's unit-testable.
31
32
  *
32
33
  * The trailing space in the match (`agent-hook ${subcommand} `) is load-bearing:
33
34
  * it keeps `stop` from matching `stop-failure`.
@@ -36,6 +37,7 @@ export declare function wireHooks(settings: SettingsFile, spec: HarnessSpec, age
36
37
  wired: number;
37
38
  already: number;
38
39
  removed: number;
40
+ upgraded: number;
39
41
  };
40
42
  /**
41
43
  * Inverse of {@link wireHooks}: strip every harnery-owned hook entry from a
@@ -61,4 +63,12 @@ export declare function unwireHooks(settings: SettingsFile): {
61
63
  * - key absent → splice it in as the first key (comment-safe).
62
64
  */
63
65
  export declare function stampBinName(configPath: string, binName: string, dryRun: boolean): string | null;
66
+ /**
67
+ * Idempotently pin `workflow.subscriptionOnly: true` in `.harnery/config.jsonc`
68
+ * (see `workflowSubscriptionOnly()` in core/config.ts and the `workflow run`
69
+ * billing safeguards). Same comment-preserving discipline as `stampBinName`.
70
+ * A `workflow` key of ANY shape (including `subscriptionOnly: false`) is a
71
+ * deliberate project choice and is left alone.
72
+ */
73
+ export declare function stampWorkflowDefaults(configPath: string, dryRun: boolean): string | null;
64
74
  //# sourceMappingURL=init.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAEnD,OAAO,EAAiB,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAClG,OAAO,EAGL,KAAK,SAAS,EAEd,KAAK,YAAY,EAClB,MAAM,iCAAiC,CAAC;AAKzC,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC;AAYxC,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAsH/F;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CACvB,QAAQ,EAAE,YAAY,EACtB,IAAI,EAAE,WAAW,EACjB,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,SAAS,GACjB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAiCrD;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,YAAY,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAmB1F;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAmDhG"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAEnD,OAAO,EAAiB,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAClG,OAAO,EAGL,KAAK,SAAS,EAEd,KAAK,YAAY,EAClB,MAAM,iCAAiC,CAAC;AAKzC,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC;AAYxC,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAiI/F;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CACvB,QAAQ,EAAE,YAAY,EACtB,IAAI,EAAE,WAAW,EACjB,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,SAAS,GACjB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAqCvE;AAyDD;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,YAAY,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAmB1F;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAmDhG;AASD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAkCxF"}
@@ -92,6 +92,17 @@ export function registerInitCommand(program, emit, binName) {
92
92
  if (stamp)
93
93
  actions.push(stamp);
94
94
  }
95
+ // ── 1c. workflow billing default ───────────────────────────────────────
96
+ // Every init'd project gets `workflow.subscriptionOnly: true`: workflow
97
+ // children ride the logged-in (subscription) harness auth, and the pin
98
+ // makes per-token API billing structurally impossible unless the project
99
+ // deliberately flips it. A committed `workflow` key of any shape is a
100
+ // deliberate choice and is never touched.
101
+ {
102
+ const stamp = stampWorkflowDefaults(resolve(coordDir, "config.jsonc"), dryRun);
103
+ if (stamp)
104
+ actions.push(stamp);
105
+ }
95
106
  // ── 2. harness hooks ───────────────────────────────────────────────────
96
107
  const settingsPath = resolve(projectRoot, spec.settingsFile);
97
108
  const agentHook = relative(projectRoot, resolve(HARNERY_ROOT, "bin", "agent-hook"));
@@ -110,19 +121,19 @@ export function registerInitCommand(program, emit, binName) {
110
121
  else {
111
122
  settings = {};
112
123
  }
113
- const { wired, already, removed } = wireHooks(settings, spec, agentHook, harness);
114
- if (wired === 0 && removed === 0) {
124
+ const { wired, already, removed, upgraded } = wireHooks(settings, spec, agentHook, harness);
125
+ if (wired === 0 && removed === 0 && upgraded === 0) {
115
126
  actions.push(`· all ${spec.events.length} ${harness} hooks already wired in ${rel(projectRoot, settingsPath)}`);
116
127
  }
117
128
  else if (dryRun) {
118
- actions.push(`+ would wire ${wired} hook(s) and remove ${removed} legacy hook(s) in ` +
119
- `${rel(projectRoot, settingsPath)} (${already} already present)`);
129
+ actions.push(`+ would wire ${wired} hook(s), upgrade ${upgraded} stale command(s), and remove ` +
130
+ `${removed} legacy hook(s) in ${rel(projectRoot, settingsPath)} (${already} already present)`);
120
131
  }
121
132
  else {
122
133
  mkdirSync(dirname(settingsPath), { recursive: true });
123
134
  writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`);
124
- actions.push(`+ wired ${wired} hook(s) and removed ${removed} legacy hook(s) in ` +
125
- `${rel(projectRoot, settingsPath)} (${already} already present)`);
135
+ actions.push(`+ wired ${wired} hook(s), upgraded ${upgraded} stale command(s), and removed ` +
136
+ `${removed} legacy hook(s) in ${rel(projectRoot, settingsPath)} (${already} already present)`);
126
137
  }
127
138
  // ── 3. agent-facing instructions block + skills ────────────────────────
128
139
  const applied = applyInstructions(projectRoot, { binName: bin, harness, dryRun });
@@ -132,11 +143,12 @@ export function registerInitCommand(program, emit, binName) {
132
143
  }
133
144
  /**
134
145
  * Merge agent-hook entries into a harness settings object in place, idempotently.
135
- * Preserves every existing hook; skips events already wired to
136
- * `agent-hook <subcommand>`. Honors the harness's entry shape (Claude/Codex nest
137
- * under an inner `hooks` array; Cursor uses a flat `{ command }`) and ensures the
138
- * root `version` key when the harness requires one. Pure (no fs/git) so it's
139
- * unit-testable.
146
+ * Preserves every existing hook; rewrites events already wired to
147
+ * `agent-hook <subcommand>` when their command string is stale (e.g. an older
148
+ * harnery wired a bare relative path). Honors the harness's entry shape
149
+ * (Claude/Codex nest under an inner `hooks` array; Cursor uses a flat
150
+ * `{ command }`) and ensures the root `version` key when the harness requires
151
+ * one. Pure (no fs/git) so it's unit-testable.
140
152
  *
141
153
  * The trailing space in the match (`agent-hook ${subcommand} `) is load-bearing:
142
154
  * it keeps `stop` from matching `stop-failure`.
@@ -150,6 +162,7 @@ export function wireHooks(settings, spec, agentHookPath, harness) {
150
162
  let wired = 0;
151
163
  let already = 0;
152
164
  let removed = 0;
165
+ let upgraded = 0;
153
166
  for (const { settingsKey, subcommand } of spec.legacyEvents ?? []) {
154
167
  const groups = settings.hooks[settingsKey] ?? [];
155
168
  const kept = groups.filter((group) => !groupCommands(group).some((command) => commandWiresSubcommand(command, subcommand)));
@@ -160,9 +173,14 @@ export function wireHooks(settings, spec, agentHookPath, harness) {
160
173
  settings.hooks[settingsKey] = kept;
161
174
  }
162
175
  for (const { settingsKey, subcommand } of spec.events) {
163
- const command = `bash ${agentHookPath} ${subcommand} --harness ${harness}`;
176
+ const command = hookCommand(spec, agentHookPath, subcommand, harness);
164
177
  const groups = settings.hooks[settingsKey] ?? [];
165
- const present = groups.some((g) => groupCommands(g).some((c) => commandWiresSubcommand(c, subcommand)));
178
+ let present = false;
179
+ for (const group of groups) {
180
+ upgraded += rewriteStaleCommands(group, subcommand, command, () => {
181
+ present = true;
182
+ });
183
+ }
166
184
  if (present) {
167
185
  already++;
168
186
  continue;
@@ -171,7 +189,52 @@ export function wireHooks(settings, spec, agentHookPath, harness) {
171
189
  settings.hooks[settingsKey] = groups;
172
190
  wired++;
173
191
  }
174
- return { wired, already, removed };
192
+ return { wired, already, removed, upgraded };
193
+ }
194
+ /**
195
+ * The canonical hook command for one event. When the harness exports a
196
+ * project-dir env var to hook processes (Claude Code's CLAUDE_PROJECT_DIR),
197
+ * anchor the agent-hook path on it: hook processes inherit the session
198
+ * shell's cwd, which follows `cd` away from the project root — a bare
199
+ * relative path silently fails to spawn from there (no events, no image
200
+ * capture, no guards). `:-.` keeps the command working on harness versions
201
+ * that don't set the var. Only the env expansion is quoted so the
202
+ * `agent-hook <subcommand> ` wiring match stays byte-identical.
203
+ */
204
+ function hookCommand(spec, agentHookPath, subcommand, harness) {
205
+ const anchor = spec.projectDirEnv ? `"\${${spec.projectDirEnv}:-.}"/` : "";
206
+ return `bash ${anchor}${agentHookPath} ${subcommand} --harness ${harness}`;
207
+ }
208
+ /**
209
+ * Rewrite any harnery-owned command for `subcommand` inside one hook group to
210
+ * the canonical form, in place. Returns the number of commands rewritten and
211
+ * calls `onPresent` when the subcommand is wired in this group at all.
212
+ */
213
+ function rewriteStaleCommands(group, subcommand, canonical, onPresent) {
214
+ let rewritten = 0;
215
+ if ("command" in group && typeof group.command === "string") {
216
+ if (commandWiresSubcommand(group.command, subcommand)) {
217
+ onPresent();
218
+ if (group.command !== canonical) {
219
+ group.command = canonical;
220
+ rewritten++;
221
+ }
222
+ }
223
+ }
224
+ if ("hooks" in group && Array.isArray(group.hooks)) {
225
+ for (const hook of group.hooks) {
226
+ if (typeof hook.command !== "string")
227
+ continue;
228
+ if (!commandWiresSubcommand(hook.command, subcommand))
229
+ continue;
230
+ onPresent();
231
+ if (hook.command !== canonical) {
232
+ hook.command = canonical;
233
+ rewritten++;
234
+ }
235
+ }
236
+ }
237
+ return rewritten;
175
238
  }
176
239
  /**
177
240
  * Inverse of {@link wireHooks}: strip every harnery-owned hook entry from a
@@ -270,6 +333,54 @@ export function stampBinName(configPath, binName, dryRun) {
270
333
  writeFileSync(configPath, next);
271
334
  return `+ added binName "${binName}" to ${rel(configPath)}`;
272
335
  }
336
+ /** The commented workflow-billing default block init stamps into config.jsonc. */
337
+ const WORKFLOW_DEFAULT_BLOCK = ` // Workflow children ride the logged-in (subscription) harness auth; this pin\n` +
338
+ ` // scrubs API-key vars from child envs so runs can never bill per-token rates.\n` +
339
+ ` // Key-only hosts (CI): set false, or HARNERY_WORKFLOW_SUBSCRIPTION_ONLY=0.\n` +
340
+ ` "workflow": { "subscriptionOnly": true }`;
341
+ /**
342
+ * Idempotently pin `workflow.subscriptionOnly: true` in `.harnery/config.jsonc`
343
+ * (see `workflowSubscriptionOnly()` in core/config.ts and the `workflow run`
344
+ * billing safeguards). Same comment-preserving discipline as `stampBinName`.
345
+ * A `workflow` key of ANY shape (including `subscriptionOnly: false`) is a
346
+ * deliberate project choice and is left alone.
347
+ */
348
+ export function stampWorkflowDefaults(configPath, dryRun) {
349
+ const rel = (p) => relative(dirname(dirname(configPath)), p) || p;
350
+ if (!existsSync(configPath)) {
351
+ if (dryRun)
352
+ return `+ would pin workflow.subscriptionOnly in ${rel(configPath)}`;
353
+ writeFileSync(configPath, `{\n${WORKFLOW_DEFAULT_BLOCK}\n}\n`);
354
+ return `+ pinned workflow.subscriptionOnly: true in ${rel(configPath)}`;
355
+ }
356
+ let raw;
357
+ try {
358
+ raw = readFileSync(configPath, "utf8");
359
+ }
360
+ catch {
361
+ return null;
362
+ }
363
+ let parsed;
364
+ try {
365
+ parsed = JSON.parse(stripJsonComments(raw)) ?? {};
366
+ }
367
+ catch {
368
+ return `· ${rel(configPath)} isn't valid JSONC; skipped workflow.subscriptionOnly pin`;
369
+ }
370
+ if ("workflow" in parsed)
371
+ return null; // deliberate config; never touch
372
+ const at = firstBraceIndex(raw);
373
+ if (at < 0)
374
+ return `· ${rel(configPath)} has no object literal; skipped workflow pin`;
375
+ const keys = Object.keys(parsed);
376
+ const next = keys.length === 0
377
+ ? `{\n${WORKFLOW_DEFAULT_BLOCK}\n}\n`
378
+ : `${raw.slice(0, at + 1)}\n${WORKFLOW_DEFAULT_BLOCK},${raw.slice(at + 1)}`;
379
+ if (dryRun)
380
+ return `+ would pin workflow.subscriptionOnly in ${rel(configPath)}`;
381
+ writeFileSync(configPath, next);
382
+ return `+ pinned workflow.subscriptionOnly: true in ${rel(configPath)}`;
383
+ }
273
384
  /** Index of the first structural `{`, skipping leading whitespace + comments. */
274
385
  function firstBraceIndex(raw) {
275
386
  let i = 0;
@@ -1,12 +1,17 @@
1
1
  import type { Command } from "commander";
2
2
  import type { EmitContext } from "../commander.js";
3
3
  /**
4
- * `presence`: mobile/office state for agents (~/.claude/presence).
4
+ * `presence`: two related surfaces under one name.
5
+ *
6
+ * 1. Mobile/office state for agents (~/.claude/presence): get / set /
7
+ * clear / detect. The state file path is Claude-Code-specific but the
8
+ * logic is generic.
9
+ * 2. Cross-machine session presence (ADR 0016): publish / fetch / peers —
10
+ * the git-refs transport that lets sessions on other machines show up
11
+ * in the peer table via `refs/harnery/presence/<machine>` on origin.
5
12
  *
6
13
  * Uses the injected EmitContext so composed and standalone consumers share
7
- * one code path. The state file path is Claude-Code-specific (lives under
8
- * ~/.claude/) but the detect/set/clear logic is generic: any agent harness
9
- * can adopt the same convention.
14
+ * one code path.
10
15
  */
11
16
  export declare function registerPresenceCommand(program: Command, emit: EmitContext): void;
12
17
  //# sourceMappingURL=presence.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"presence.d.ts","sourceRoot":"","sources":["../../src/commands/presence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAkBnD;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAoFjF"}
1
+ {"version":3,"file":"presence.d.ts","sourceRoot":"","sources":["../../src/commands/presence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AA0BnD;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAwKjF"}
@@ -1,17 +1,24 @@
1
+ import { monorepoRoot } from "../core/agents/coord-client.js";
1
2
  import { emitCanonical, normalizeHarness, readHeartbeat, resolveOwner, } from "../core/agents/index.js";
3
+ import { fetchPresence, publishPresence, readRemoteMachines, relayDaemonStatus, runRelayDaemon, } from "../core/presence/index.js";
2
4
  import { ageSeconds, applyDetection, clearPresence, formatAge, presenceFilePath, readPresence, writePresence, } from "../lib/presence.js";
3
5
  /**
4
- * `presence`: mobile/office state for agents (~/.claude/presence).
6
+ * `presence`: two related surfaces under one name.
7
+ *
8
+ * 1. Mobile/office state for agents (~/.claude/presence): get / set /
9
+ * clear / detect. The state file path is Claude-Code-specific but the
10
+ * logic is generic.
11
+ * 2. Cross-machine session presence (ADR 0016): publish / fetch / peers —
12
+ * the git-refs transport that lets sessions on other machines show up
13
+ * in the peer table via `refs/harnery/presence/<machine>` on origin.
5
14
  *
6
15
  * Uses the injected EmitContext so composed and standalone consumers share
7
- * one code path. The state file path is Claude-Code-specific (lives under
8
- * ~/.claude/) but the detect/set/clear logic is generic: any agent harness
9
- * can adopt the same convention.
16
+ * one code path.
10
17
  */
11
18
  export function registerPresenceCommand(program, emit) {
12
19
  const cmd = program
13
20
  .command("presence")
14
- .description("Mobile/office state for agents (~/.claude/presence)")
21
+ .description("Presence: mobile/office state (get/set/clear/detect) + cross-machine session presence (publish/fetch/peers)")
15
22
  .action(() => {
16
23
  printStatus(emit);
17
24
  });
@@ -62,6 +69,74 @@ export function registerPresenceCommand(program, emit) {
62
69
  path: presenceFilePath(),
63
70
  });
64
71
  });
72
+ // ---- cross-machine session presence (ADR 0016) ----
73
+ cmd
74
+ .command("publish")
75
+ .description("Publish this machine's live sessions to refs/harnery/presence/<machine> on origin. " +
76
+ "Change-batched with a keepalive; --force pushes regardless. Synchronous (errors report).")
77
+ .option("--force", "Push even if unchanged / within throttle windows")
78
+ .action((opts) => {
79
+ const root = requireRoot(emit);
80
+ const r = publishPresence(root, { force: opts.force, sync: true });
81
+ if (r.status === "error") {
82
+ emit.error({ code: "publish_failed", message: r.error });
83
+ process.exit(1);
84
+ }
85
+ emit.data(r);
86
+ });
87
+ cmd
88
+ .command("fetch")
89
+ .description("Fetch peer machines' presence refs from origin (throttled; --force bypasses). Synchronous.")
90
+ .option("--force", "Fetch even within the throttle interval")
91
+ .action((opts) => {
92
+ const root = requireRoot(emit);
93
+ const r = fetchPresence(root, { force: opts.force, sync: true });
94
+ if (r.status === "error") {
95
+ emit.error({ code: "fetch_failed", message: r.error });
96
+ process.exit(1);
97
+ }
98
+ emit.data(r);
99
+ });
100
+ cmd
101
+ .command("relay-daemon")
102
+ .description("Run the relay client daemon in the foreground (normally spawned detached by the hooks " +
103
+ "when presence.relay is configured; holds the WebSocket, publishes on heartbeat change, " +
104
+ "caches peers' blobs for the renderers, exits when the machine goes idle).")
105
+ .action(async () => {
106
+ const root = requireRoot(emit);
107
+ process.exit(await runRelayDaemon(root));
108
+ });
109
+ cmd
110
+ .command("peers")
111
+ .description("Show sessions on other machines (merged from presence refs + the relay daemon's cache)")
112
+ .option("--json", "JSON output")
113
+ .option("--stale", "Include machines past the stale window")
114
+ .action((opts) => {
115
+ const root = requireRoot(emit);
116
+ const machines = readRemoteMachines(root, { includeStale: opts.stale });
117
+ const daemon = relayDaemonStatus(root);
118
+ if (opts.json)
119
+ emit.config({ format: "json" });
120
+ if (opts.json || !process.stdout.isTTY) {
121
+ emit.data({ machines, relay_daemon: daemon });
122
+ return;
123
+ }
124
+ emit.text(daemon.running
125
+ ? `relay daemon: running (pid ${daemon.pid}, ${daemon.url})\n`
126
+ : "relay daemon: not running (git-refs transport only)\n");
127
+ if (machines.length === 0) {
128
+ emit.text("no remote sessions known (try `presence fetch --force`)\n");
129
+ return;
130
+ }
131
+ for (const m of machines) {
132
+ emit.text(`${m.machine} (published ${formatAge(m.age_secs)} ago)\n`);
133
+ for (const a of m.agents) {
134
+ const task = a.task ? ` "${a.task.slice(0, 70)}"` : "";
135
+ const files = a.files_touched?.length ? ` [${a.files_touched.length} files]` : "";
136
+ emit.text(` agent-${a.name ?? a.instance_id.slice(0, 8)}${task}${files}\n`);
137
+ }
138
+ }
139
+ });
65
140
  cmd
66
141
  .command("detect")
67
142
  .description("Internal: read prompt text from stdin, apply detection rules, update state if signal is clear")
@@ -85,6 +160,14 @@ export function registerPresenceCommand(program, emit) {
85
160
  }
86
161
  });
87
162
  }
163
+ function requireRoot(emit) {
164
+ const root = monorepoRoot();
165
+ if (!root) {
166
+ emit.error({ code: "not_in_repo", message: "coord root not found (no .harnery/ upward)" });
167
+ process.exit(1);
168
+ }
169
+ return root;
170
+ }
88
171
  function printStatus(emit) {
89
172
  const r = readPresence();
90
173
  emit.data({
@@ -0,0 +1,9 @@
1
+ import type { Command } from "commander";
2
+ import type { EmitContext } from "../commander.js";
3
+ /** Start the relay server. Exported for in-process integration tests. */
4
+ export declare function startRelayServer(port: number): {
5
+ port: number;
6
+ stop: () => void;
7
+ };
8
+ export declare function registerRelayCommand(program: Command, emit: EmitContext): void;
9
+ //# sourceMappingURL=relay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relay.d.ts","sourceRoot":"","sources":["../../src/commands/relay.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAgCnD,yEAAyE;AACzE,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,IAAI,CAAA;CAAE,CAkGjF;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAyB9E"}