harnery 0.32.0 → 0.33.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 (91) hide show
  1. package/README.md +2 -0
  2. package/dist/commander.d.ts.map +1 -1
  3. package/dist/commander.js +3 -1
  4. package/dist/commands/agents.d.ts.map +1 -1
  5. package/dist/commands/agents.js +212 -1
  6. package/dist/commands/claude-desktop.d.ts +2 -2
  7. package/dist/commands/claude-desktop.d.ts.map +1 -1
  8. package/dist/commands/claude-desktop.js +74 -3
  9. package/dist/commands/doctor.d.ts +4 -2
  10. package/dist/commands/doctor.d.ts.map +1 -1
  11. package/dist/commands/doctor.js +45 -6
  12. package/dist/commands/init.d.ts +1 -0
  13. package/dist/commands/init.d.ts.map +1 -1
  14. package/dist/commands/init.js +10 -0
  15. package/dist/commands/instructions.d.ts +4 -0
  16. package/dist/commands/instructions.d.ts.map +1 -0
  17. package/dist/commands/instructions.js +100 -0
  18. package/dist/commands/presence.d.ts.map +1 -1
  19. package/dist/commands/presence.js +4 -1
  20. package/dist/core/agents/canonical-emit.d.ts +1 -1
  21. package/dist/core/agents/canonical-emit.d.ts.map +1 -1
  22. package/dist/core/agents/canonical-emit.js +5 -3
  23. package/dist/core/agents/cli.js +5 -1
  24. package/dist/core/agents/coord-client.d.ts +11 -0
  25. package/dist/core/agents/coord-client.d.ts.map +1 -1
  26. package/dist/core/agents/index.d.ts +1 -0
  27. package/dist/core/agents/index.d.ts.map +1 -1
  28. package/dist/core/agents/index.js +1 -0
  29. package/dist/core/agents/render/prompt-context.d.ts +6 -0
  30. package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
  31. package/dist/core/agents/render/prompt-context.js +46 -5
  32. package/dist/core/agents/render/session-context.d.ts.map +1 -1
  33. package/dist/core/agents/render/session-context.js +8 -2
  34. package/dist/core/agents/rules/stop-hook.js +4 -4
  35. package/dist/core/agents/state/heartbeat-projector.d.ts +11 -0
  36. package/dist/core/agents/state/heartbeat-projector.d.ts.map +1 -1
  37. package/dist/core/agents/state/heartbeat-projector.js +54 -0
  38. package/dist/core/agents/state/heartbeat-writer.d.ts +15 -0
  39. package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
  40. package/dist/core/agents/state/heartbeat-writer.js +32 -0
  41. package/dist/core/agents/state/session-state.d.ts +44 -0
  42. package/dist/core/agents/state/session-state.d.ts.map +1 -0
  43. package/dist/core/agents/state/session-state.js +81 -0
  44. package/dist/core/hooks/adapter/events.d.ts.map +1 -1
  45. package/dist/core/hooks/adapter/events.js +1 -0
  46. package/dist/core/hooks/adapter/parse.d.ts +1 -1
  47. package/dist/core/hooks/adapter/parse.d.ts.map +1 -1
  48. package/dist/core/hooks/adapter/parse.js +2 -0
  49. package/dist/core/hooks/cli.js +90 -6
  50. package/dist/core/hooks/codex-authorization.d.ts +30 -0
  51. package/dist/core/hooks/codex-authorization.d.ts.map +1 -0
  52. package/dist/core/hooks/codex-authorization.js +199 -0
  53. package/dist/core/hooks/events/schema.d.ts +30 -1
  54. package/dist/core/hooks/events/schema.d.ts.map +1 -1
  55. package/dist/core/presence/blob.d.ts +4 -0
  56. package/dist/core/presence/blob.d.ts.map +1 -1
  57. package/dist/core/presence/blob.js +12 -0
  58. package/dist/core/presence/index.js +15 -1
  59. package/dist/lib/claude-desktop.d.ts +47 -0
  60. package/dist/lib/claude-desktop.d.ts.map +1 -1
  61. package/dist/lib/claude-desktop.js +169 -1
  62. package/dist/lib/instructions/bundle.d.ts +33 -0
  63. package/dist/lib/instructions/bundle.d.ts.map +1 -0
  64. package/dist/lib/instructions/bundle.js +149 -0
  65. package/package.json +2 -2
  66. package/src/commander.ts +3 -1
  67. package/src/commands/agents.ts +248 -1
  68. package/src/commands/claude-desktop.ts +106 -5
  69. package/src/commands/doctor.ts +59 -7
  70. package/src/commands/init.ts +11 -0
  71. package/src/commands/instructions.ts +129 -0
  72. package/src/commands/presence.ts +7 -1
  73. package/src/core/agents/canonical-emit.ts +6 -4
  74. package/src/core/agents/cli.ts +6 -1
  75. package/src/core/agents/coord-client.ts +11 -0
  76. package/src/core/agents/index.ts +1 -0
  77. package/src/core/agents/render/prompt-context.ts +71 -6
  78. package/src/core/agents/render/session-context.ts +14 -2
  79. package/src/core/agents/rules/stop-hook.ts +5 -5
  80. package/src/core/agents/state/heartbeat-projector.ts +63 -0
  81. package/src/core/agents/state/heartbeat-writer.ts +51 -0
  82. package/src/core/agents/state/session-state.ts +131 -0
  83. package/src/core/hooks/adapter/events.ts +1 -0
  84. package/src/core/hooks/adapter/parse.ts +3 -0
  85. package/src/core/hooks/cli.ts +98 -7
  86. package/src/core/hooks/codex-authorization.ts +253 -0
  87. package/src/core/hooks/events/schema.ts +44 -0
  88. package/src/core/presence/blob.ts +18 -0
  89. package/src/core/presence/index.ts +18 -1
  90. package/src/lib/claude-desktop.ts +232 -1
  91. package/src/lib/instructions/bundle.ts +199 -0
package/README.md CHANGED
@@ -100,6 +100,8 @@ The boundary is enforced, not aspirational: CI verifies that no `harnery/lib/*`
100
100
  Full docs at **[harnery.com](https://harnery.com)**:
101
101
 
102
102
  - [Getting started](https://harnery.com/getting-started/install)
103
+ - [Features](https://harnery.com/features/session-state/)
104
+ - [Guides](https://harnery.com/guides/manage-claude-desktop-sessions/)
103
105
  - [CLI reference](https://harnery.com/cli/)
104
106
  - [Concepts](https://harnery.com/concepts/coord-layer)
105
107
  - [Configuration schema](https://harnery.com/reference/config-schema)
@@ -1 +1 @@
1
- {"version":3,"file":"commander.d.ts","sourceRoot":"","sources":["../src/commander.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"commander.d.ts","sourceRoot":"","sources":["../src/commander.ts"],"names":[],"mappings":"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"}
package/dist/commander.js CHANGED
@@ -43,6 +43,7 @@ import { registerFileHistoryCommand } from "./commands/file-history.js";
43
43
  import { registerGovernorCommand } from "./commands/governor.js";
44
44
  import { registerGrepCommand } from "./commands/grep.js";
45
45
  import { registerInitCommand } from "./commands/init.js";
46
+ import { registerInstructionsCommand } from "./commands/instructions.js";
46
47
  import { registerJournalCommand } from "./commands/journal.js";
47
48
  import { registerOutlineCommand } from "./commands/outline.js";
48
49
  import { registerPolicyCommand } from "./commands/policy.js";
@@ -131,10 +132,11 @@ export function createHarneryProgram(opts = {}) {
131
132
  registerAgentsCommand(program, emit, opts.context);
132
133
  registerCouncilCommands(program);
133
134
  registerDoctorCommand(program, emit);
135
+ registerInstructionsCommand(program, emit);
134
136
  registerInitCommand(program, emit, opts.binName);
135
137
  registerDeinitCommand(program, emit, opts.binName);
136
138
  registerBackupCommand(program, emit);
137
- registerClaudeDesktopCommand(program, emit);
139
+ registerClaudeDesktopCommand(program, emit, opts.context);
138
140
  registerSyncCommand(program, emit);
139
141
  registerWorkflowCommand(program, emit);
140
142
  registerWorkCommand(program, emit);
@@ -1 +1 @@
1
- {"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../src/commands/agents.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAeH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAkF1E;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAA;CAAE,CAErF;AAsED,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,WAAW,EACtB,cAAc,CAAC,EAAE,qBAAqB,GACrC,IAAI,CAmQN;AAsKD,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAyO7D"}
1
+ {"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../src/commands/agents.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAeH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AA0F1E;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAA;CAAE,CAErF;AAyFD,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,WAAW,EACtB,cAAc,CAAC,EAAE,qBAAqB,GACrC,IAAI,CA+QN;AAsKD,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAyO7D"}
@@ -20,7 +20,8 @@ import { coordBinPath } from "../core/agents/coord-bin.js";
20
20
  import { readStreamTailBounded } from "../core/agents/events/consume.js";
21
21
  import { checkGitFinalization, formatGitFinalizationFailure, readSessionWriteClaims, } from "../core/agents/finalization.js";
22
22
  import { emitCanonical, monorepoRoot, normalizeAdapter, readHeartbeat, resolveOwner, resolveOwnerBySessionEnv, resolveOwnerWithSource, } from "../core/agents/index.js";
23
- import { buildSuggestedName } from "../core/agents/state/heartbeat-writer.js";
23
+ import { buildLifecycleSuggestedName, buildSuggestedName, } from "../core/agents/state/heartbeat-writer.js";
24
+ import { foldSessionState, } from "../core/agents/state/session-state.js";
24
25
  import { coordFreshnessSeconds, resolveBinName } from "../core/config.js";
25
26
  import { readRemoteMachines } from "../core/presence/index.js";
26
27
  import { registerContextCommand } from "./context.js";
@@ -101,6 +102,16 @@ function formatPlatformLabel(platform) {
101
102
  return "Codex";
102
103
  return "CC";
103
104
  }
105
+ function activityOf(hb) {
106
+ return hb.activity ?? "unknown";
107
+ }
108
+ function taskStateOf(hb) {
109
+ return hb.task_state ?? "active";
110
+ }
111
+ function lifecycleLabel(hb) {
112
+ const state = taskStateOf(hb);
113
+ return state === "blocked" && hb.task_state_reason ? `${state}: ${hb.task_state_reason}` : state;
114
+ }
104
115
  let emit;
105
116
  export function registerAgentsCommand(program, emitParam, programContext) {
106
117
  emit = emitParam;
@@ -184,6 +195,14 @@ export function registerAgentsCommand(program, emitParam, programContext) {
184
195
  .action((text, opts) => {
185
196
  runSetTask(text.join(" "), opts);
186
197
  });
198
+ cmd
199
+ .command("lifecycle <state>")
200
+ .description("Declare this session's task lifecycle: active, blocked, or done")
201
+ .option("--reason <text>", "Explain the lifecycle declaration")
202
+ .option("--session-id <id>", "Target the heartbeat with this session_id directly, bypassing the ppid walk.")
203
+ .action((state, opts) => {
204
+ runLifecycle(state, opts);
205
+ });
187
206
  cmd
188
207
  .command("release-claim <path>")
189
208
  .description("Drop a file claim from your heartbeat. Operator escape hatch when a " +
@@ -598,6 +617,12 @@ function runWhoami(opts) {
598
617
  last_tool: hb.last_tool ?? null,
599
618
  last_tool_target: hb.last_tool_target ?? null,
600
619
  task: hb.task ?? null,
620
+ activity: activityOf(hb),
621
+ activity_updated_at: hb.activity_updated_at ?? null,
622
+ activity_source: hb.activity_source ?? null,
623
+ task_state: taskStateOf(hb),
624
+ task_state_updated_at: hb.task_state_updated_at ?? null,
625
+ task_state_reason: hb.task_state_reason ?? null,
601
626
  turn_summary: hb.turn_summary ?? null,
602
627
  turn_summary_updated_at: hb.turn_summary_updated_at ?? null,
603
628
  platform: hb.platform ?? "claude-code",
@@ -687,6 +712,12 @@ function runList(opts) {
687
712
  last_tool: h.last_tool ?? null,
688
713
  last_tool_target: h.last_tool_target ?? null,
689
714
  task: h.task ?? null,
715
+ activity: activityOf(h),
716
+ activity_updated_at: h.activity_updated_at ?? null,
717
+ activity_source: h.activity_source ?? null,
718
+ task_state: taskStateOf(h),
719
+ task_state_updated_at: h.task_state_updated_at ?? null,
720
+ task_state_reason: h.task_state_reason ?? null,
690
721
  turn_summary: h.turn_summary ?? null,
691
722
  turn_summary_updated_at: h.turn_summary_updated_at ?? null,
692
723
  platform: h.platform ?? "claude-code",
@@ -712,6 +743,12 @@ function runList(opts) {
712
743
  last_tool: h.last_tool ?? null,
713
744
  last_tool_target: h.last_tool_target ?? null,
714
745
  task: h.task ?? null,
746
+ activity: activityOf(h),
747
+ activity_updated_at: h.activity_updated_at ?? null,
748
+ activity_source: h.activity_source ?? null,
749
+ task_state: taskStateOf(h),
750
+ task_state_updated_at: h.task_state_updated_at ?? null,
751
+ task_state_reason: h.task_state_reason ?? null,
715
752
  turn_summary: h.turn_summary ?? null,
716
753
  turn_summary_updated_at: h.turn_summary_updated_at ?? null,
717
754
  platform: h.platform ?? "claude-code",
@@ -738,6 +775,12 @@ function runList(opts) {
738
775
  last_tool: a.last_tool ?? null,
739
776
  last_tool_target: null,
740
777
  task: a.task ?? null,
778
+ activity: a.activity ?? "unknown",
779
+ activity_updated_at: null,
780
+ activity_source: null,
781
+ task_state: a.task_state ?? "active",
782
+ task_state_updated_at: null,
783
+ task_state_reason: a.task_state_reason ?? null,
741
784
  turn_summary: a.turn_summary ?? null,
742
785
  turn_summary_updated_at: null,
743
786
  platform: a.platform ?? "claude-code",
@@ -983,6 +1026,12 @@ async function runShow(name, opts) {
983
1026
  age_secs: ageSecs,
984
1027
  last_heartbeat_secs_ago: heartbeatAgeSecs,
985
1028
  task: hb.task ?? null,
1029
+ activity: activityOf(hb),
1030
+ activity_updated_at: hb.activity_updated_at ?? null,
1031
+ activity_source: hb.activity_source ?? null,
1032
+ task_state: taskStateOf(hb),
1033
+ task_state_updated_at: hb.task_state_updated_at ?? null,
1034
+ task_state_reason: hb.task_state_reason ?? null,
986
1035
  turn_summary: hb.turn_summary ?? null,
987
1036
  turn_summary_updated_at: hb.turn_summary_updated_at ?? null,
988
1037
  title: report?.title ?? null,
@@ -1010,6 +1059,8 @@ async function runShow(name, opts) {
1010
1059
  lines.push(`agent-${data.name} ${subtitle}`);
1011
1060
  lines.push(` session ${formatAge(ageSecs)} old · kind=${data.kind} · session_id=${data.session_id.slice(0, 8)}…`);
1012
1061
  lines.push(` last heartbeat: ${formatAge(heartbeatAgeSecs)} ago`);
1062
+ lines.push(` activity: ${data.activity}`);
1063
+ lines.push(` lifecycle: ${data.task_state}${data.task_state === "blocked" && data.task_state_reason ? `: ${data.task_state_reason}` : ""}`);
1013
1064
  if (data.last_tool) {
1014
1065
  const target = data.last_tool_target ? ` ${truncate(data.last_tool_target, 80)}` : "";
1015
1066
  lines.push(` last activity: ${data.last_tool}${target}`);
@@ -1183,6 +1234,10 @@ function runSetTask(task, opts) {
1183
1234
  // heartbeat-writer.setTask. This call is the naming call exactly when the
1184
1235
  // stamp appears across the mutation.
1185
1236
  const priorHb = readHeartbeat(myOwner);
1237
+ const normalizedTask = task.length > 0 ? task : undefined;
1238
+ const lifecycleWarning = priorHb && (priorHb.task_state ?? "active") !== "active" && priorHb.task !== normalizedTask
1239
+ ? `Task text changed while lifecycle is ${priorHb.task_state}; run \`${resolveBinName(root)} agents lifecycle active\` to reopen it explicitly.`
1240
+ : null;
1186
1241
  // Heartbeat mutation goes through agent-coord (atomic temp+rename).
1187
1242
  const helper = agentCoordOrExit(root);
1188
1243
  const result = spawnSync(helper, ["set-task", myOwner, task], {
@@ -1224,6 +1279,7 @@ function runSetTask(task, opts) {
1224
1279
  cleared: !task || task.length === 0,
1225
1280
  first_of_session: firstOfSession,
1226
1281
  suggested_session_name: suggestedName,
1282
+ ...(lifecycleWarning ? { warning: lifecycleWarning } : {}),
1227
1283
  // Right-time instruction: the UserPromptSubmit nudge fires before the name
1228
1284
  // exists; this result is the moment the model holds the string.
1229
1285
  ...(suggestedName
@@ -1233,6 +1289,136 @@ function runSetTask(task, opts) {
1233
1289
  : {}),
1234
1290
  });
1235
1291
  }
1292
+ function runLifecycle(rawState, opts) {
1293
+ const state = rawState.trim().toLowerCase();
1294
+ if (state !== "active" && state !== "blocked" && state !== "done") {
1295
+ emit.error({
1296
+ code: "invalid_lifecycle_state",
1297
+ message: "lifecycle state must be one of: active, blocked, done",
1298
+ });
1299
+ process.exitCode = 1;
1300
+ return;
1301
+ }
1302
+ const root = monorepoRoot();
1303
+ if (!root) {
1304
+ emit.error({
1305
+ code: "not_in_repo",
1306
+ message: "not in an agent session; coord_root() returned null",
1307
+ });
1308
+ process.exitCode = 1;
1309
+ return;
1310
+ }
1311
+ if (!opts.sessionId)
1312
+ ensureCursorSession(root);
1313
+ const myOwner = opts.sessionId ?? resolveOwner();
1314
+ if (!myOwner) {
1315
+ emit.error({
1316
+ code: "no_pidmap_entry",
1317
+ message: "not in an agent session; ppid walk found no pid-map entry (pass --session-id to bypass)",
1318
+ });
1319
+ process.exitCode = 1;
1320
+ return;
1321
+ }
1322
+ const hb = readHeartbeat(myOwner);
1323
+ if (!hb) {
1324
+ emit.error({ code: "no_heartbeat", message: noHeartbeatMessage(myOwner) });
1325
+ process.exitCode = 1;
1326
+ return;
1327
+ }
1328
+ if (hb.kind === "subagent" || hb.kind === "transient" || hb.workflow_run_id) {
1329
+ emit.error({
1330
+ code: "lifecycle_not_human_facing",
1331
+ message: "task lifecycle declarations are limited to human-facing sessions",
1332
+ });
1333
+ process.exitCode = 1;
1334
+ return;
1335
+ }
1336
+ const reason = opts.reason?.trim() || undefined;
1337
+ if (state === "blocked" && !reason) {
1338
+ emit.error({
1339
+ code: "blocked_reason_required",
1340
+ message: "lifecycle blocked requires --reason <text>",
1341
+ });
1342
+ process.exitCode = 1;
1343
+ return;
1344
+ }
1345
+ if (state === "done" && !hb.task) {
1346
+ emit.error({
1347
+ code: "task_required_for_done",
1348
+ message: "declare a current task with agents set-task before marking it done",
1349
+ });
1350
+ process.exitCode = 1;
1351
+ return;
1352
+ }
1353
+ const priorState = hb.task_state ?? "active";
1354
+ const priorReason = hb.task_state_reason || undefined;
1355
+ if (priorState === state && priorReason === reason) {
1356
+ emit.data({
1357
+ instance_id: myOwner,
1358
+ task_state: state,
1359
+ reason: reason ?? null,
1360
+ changed: false,
1361
+ name_reminted: false,
1362
+ git_finalization_checked: false,
1363
+ });
1364
+ return;
1365
+ }
1366
+ let finalization = null;
1367
+ if (state === "done") {
1368
+ const history = readSessionWriteClaims(root, hb.instance_id, hb.session_id ?? myOwner);
1369
+ const touchedPaths = [...new Set([...(hb.files_touched ?? []), ...history.paths])];
1370
+ finalization = checkGitFinalization(root, touchedPaths, {
1371
+ claimHistoryComplete: history.complete,
1372
+ });
1373
+ if (!finalization.ok) {
1374
+ emit.error({
1375
+ code: "git_not_finalized",
1376
+ message: formatGitFinalizationFailure(finalization, resolveBinName(root)).replace("the status box was not issued", "lifecycle was not changed"),
1377
+ });
1378
+ process.exitCode = 1;
1379
+ return;
1380
+ }
1381
+ }
1382
+ const suggestedName = buildLifecycleSuggestedName(hb.name ?? "unknown", hb.task, state);
1383
+ const nameReminted = suggestedName !== null && suggestedName !== hb.suggested_session_name;
1384
+ const emitted = emitCanonical({
1385
+ type: "state.task_state",
1386
+ owner: myOwner,
1387
+ session: hb.session_id ?? myOwner,
1388
+ adapter: normalizeAdapter(hb.platform),
1389
+ data: {
1390
+ state,
1391
+ reason: reason ?? null,
1392
+ prior_state: priorState,
1393
+ name_reminted: nameReminted,
1394
+ git_finalization_checked: finalization !== null,
1395
+ ...(nameReminted ? { suggested_session_name: suggestedName } : {}),
1396
+ },
1397
+ });
1398
+ if (!emitted) {
1399
+ emit.error({
1400
+ code: "lifecycle_event_failed",
1401
+ message: "the lifecycle event could not be recorded; heartbeat state was left unchanged",
1402
+ });
1403
+ process.exitCode = 1;
1404
+ return;
1405
+ }
1406
+ emit.data({
1407
+ instance_id: myOwner,
1408
+ task_state: state,
1409
+ prior_state: priorState,
1410
+ reason: reason ?? null,
1411
+ changed: true,
1412
+ name_reminted: nameReminted,
1413
+ suggested_session_name: nameReminted ? suggestedName : null,
1414
+ git_finalization_checked: finalization !== null,
1415
+ ...(nameReminted
1416
+ ? {
1417
+ note: "Reproduce suggested_session_name verbatim, by itself, in a fenced code block at the top of your reply — the operator copies it as the session/tab title.",
1418
+ }
1419
+ : {}),
1420
+ });
1421
+ }
1236
1422
  function runSuggestName(descriptionParts, opts) {
1237
1423
  const root = monorepoRoot();
1238
1424
  if (!root) {
@@ -1422,6 +1608,12 @@ function runStatus(opts) {
1422
1608
  instance_id: hb.instance_id,
1423
1609
  kind: normalizeKind(hb.kind),
1424
1610
  session_age_secs: ageSecs,
1611
+ activity: activityOf(hb),
1612
+ activity_updated_at: hb.activity_updated_at ?? null,
1613
+ activity_source: hb.activity_source ?? null,
1614
+ task_state: taskStateOf(hb),
1615
+ task_state_updated_at: hb.task_state_updated_at ?? null,
1616
+ task_state_reason: hb.task_state_reason ?? null,
1425
1617
  files_held: filesHeld,
1426
1618
  peers_live: livePeers.length,
1427
1619
  peers_stale: peersStale,
@@ -1435,6 +1627,9 @@ function runStatus(opts) {
1435
1627
  agents: m.agents.map((a) => ({
1436
1628
  name: a.name || "unnamed",
1437
1629
  task: a.task ?? null,
1630
+ activity: a.activity ?? "unknown",
1631
+ task_state: a.task_state ?? "active",
1632
+ task_state_reason: a.task_state_reason ?? null,
1438
1633
  files: a.files_touched?.length ?? 0,
1439
1634
  })),
1440
1635
  })),
@@ -1452,6 +1647,8 @@ function runStatus(opts) {
1452
1647
  }
1453
1648
  const rows = [
1454
1649
  ["session", formatAge(ageSecs)],
1650
+ ["activity", activityOf(hb)],
1651
+ ["lifecycle", lifecycleLabel(hb)],
1455
1652
  ["context", ctxStr],
1456
1653
  ["files", filesStr],
1457
1654
  ["peers", peersStr],
@@ -2134,6 +2331,12 @@ function traceLine(ev, allTools) {
2134
2331
  case "state.task_set":
2135
2332
  detail = d.cleared ? "(cleared)" : clip(s("task"));
2136
2333
  break;
2334
+ case "state.task_state":
2335
+ detail = `${s("prior_state") || "active"} → ${s("state") || "active"}${s("reason") ? ` · ${clip(s("reason"), 55)}` : ""}`;
2336
+ break;
2337
+ case "interaction.input_requested":
2338
+ detail = s("request_kind") || s("tool_name") || "operator input requested";
2339
+ break;
2137
2340
  case "state.status_checked":
2138
2341
  detail = "status box rendered";
2139
2342
  break;
@@ -2247,6 +2450,7 @@ function runTrace(name, opts) {
2247
2450
  }
2248
2451
  }
2249
2452
  const events = byId.get(resolvedId) ?? [];
2453
+ const state = foldSessionState(events, { instance_id: resolvedId });
2250
2454
  const lines = events
2251
2455
  .map((ev) => traceLine(ev, !!opts.allTools))
2252
2456
  .filter((l) => l !== null)
@@ -2260,6 +2464,12 @@ function runTrace(name, opts) {
2260
2464
  instance_id: resolvedId,
2261
2465
  other_instances: candidateIds.filter((id) => id !== resolvedId),
2262
2466
  total_events: events.length,
2467
+ activity: state.activity,
2468
+ activity_updated_at: state.activity_updated_at ?? null,
2469
+ activity_source: state.activity_source ?? null,
2470
+ task_state: state.task_state,
2471
+ task_state_updated_at: state.task_state_updated_at ?? null,
2472
+ task_state_reason: state.task_state_reason ?? null,
2263
2473
  shown: shown.length,
2264
2474
  entries: shown,
2265
2475
  };
@@ -2270,6 +2480,7 @@ function runTrace(name, opts) {
2270
2480
  emit.data(result);
2271
2481
  const header = `Trace: agent-${displayName} (${resolvedId.slice(0, 8)}…) ${events.length} events${result.other_instances.length ? ` · ${result.other_instances.length} older instance(s) of this name` : ""}`;
2272
2482
  process.stdout.write(`${header}\n`); // lint-ok-emission: human trace view
2483
+ process.stdout.write(` activity=${state.activity} · lifecycle=${state.task_state}${state.task_state === "blocked" && state.task_state_reason ? `: ${state.task_state_reason}` : ""}\n`); // lint-ok-emission: human trace view
2273
2484
  if (shown.length === 0) {
2274
2485
  process.stdout.write(" (no events)\n"); // lint-ok-emission: human trace view
2275
2486
  return;
@@ -1,5 +1,5 @@
1
1
  import type { Command } from "commander";
2
- import type { EmitContext } from "../commander.js";
2
+ import type { EmitContext, HarneryProgramContext } from "../commander.js";
3
3
  /**
4
4
  * `claude-desktop`: make Claude desktop-app sessions survive account
5
5
  * switches.
@@ -15,5 +15,5 @@ import type { EmitContext } from "../commander.js";
15
15
  * desktop app picks the copies up, and opening one resumes with full
16
16
  * history.
17
17
  */
18
- export declare function registerClaudeDesktopCommand(program: Command, emit: EmitContext): void;
18
+ export declare function registerClaudeDesktopCommand(program: Command, emit: EmitContext, context?: HarneryProgramContext): void;
19
19
  //# sourceMappingURL=claude-desktop.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"claude-desktop.d.ts","sourceRoot":"","sources":["../../src/commands/claude-desktop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAUnD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAgKtF"}
1
+ {"version":3,"file":"claude-desktop.d.ts","sourceRoot":"","sources":["../../src/commands/claude-desktop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAc1E;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,WAAW,EACjB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,IAAI,CA6PN"}
@@ -1,4 +1,5 @@
1
- import { applyMirror, findDesktopDataDirs, listAccounts, planMirror, readCliAccount, } from "../lib/claude-desktop.js";
1
+ import { monorepoRoot } from "../core/agents/index.js";
2
+ import { applyMirror, applyTidy, findDesktopDataDirs, listAccounts, planMirror, planTidy, readCliAccount, readDesktopLifecycleIndex, } from "../lib/claude-desktop.js";
2
3
  /**
3
4
  * `claude-desktop`: make Claude desktop-app sessions survive account
4
5
  * switches.
@@ -14,10 +15,10 @@ import { applyMirror, findDesktopDataDirs, listAccounts, planMirror, readCliAcco
14
15
  * desktop app picks the copies up, and opening one resumes with full
15
16
  * history.
16
17
  */
17
- export function registerClaudeDesktopCommand(program, emit) {
18
+ export function registerClaudeDesktopCommand(program, emit, context) {
18
19
  const cmd = program
19
20
  .command("claude-desktop")
20
- .description("Claude desktop-app session index: list accounts/sessions, mirror sessions across accounts")
21
+ .description("Claude desktop-app session index: list, mirror, and archive completed sessions")
21
22
  .action(() => {
22
23
  const dirs = requireDataDirs(emit, undefined);
23
24
  for (const dir of dirs) {
@@ -141,6 +142,76 @@ export function registerClaudeDesktopCommand(program, emit) {
141
142
  });
142
143
  }
143
144
  });
145
+ cmd
146
+ .command("tidy")
147
+ .description("Archive desktop sidebar entries whose mapped Harnery lifecycle is done. " +
148
+ "Dry-run by default; --yes applies atomic entry updates.")
149
+ .option("--data-dir <path>", "Explicit desktop-app data directory")
150
+ .option("--account <uuid-prefix>", "Only entries under accounts matching this UUID prefix (repeatable)", collect, [])
151
+ .option("--include-legacy-prefix", "Also archive unmatched historical entries whose title starts with [DONE]")
152
+ .option("--yes", "Apply the archive plan (default is dry-run)")
153
+ .action(async (opts) => {
154
+ const dirs = requireDataDirs(emit, opts.dataDir);
155
+ const coordRoot = context?.resolveCoordRoot?.() ?? context?.repoRoot ?? monorepoRoot() ?? process.cwd();
156
+ const lifecycle = await readDesktopLifecycleIndex(coordRoot);
157
+ for (const dir of dirs) {
158
+ const plan = planTidy(listAccounts(dir), lifecycle, {
159
+ accounts: opts.account,
160
+ includeLegacyPrefix: opts.includeLegacyPrefix,
161
+ });
162
+ const planned = plan.actions.map((action) => ({
163
+ account_uuid: action.entry.accountUuid,
164
+ cli_session_id: action.entry.cliSessionId,
165
+ title: action.entry.title,
166
+ source: action.source,
167
+ lifecycle_updated_at: action.lifecycle?.updatedAt ?? null,
168
+ file: action.entry.file,
169
+ }));
170
+ const skipped = plan.skipped
171
+ .map((skip) => ({
172
+ account_uuid: skip.entry.accountUuid,
173
+ cli_session_id: skip.entry.cliSessionId,
174
+ title: skip.entry.title,
175
+ reason: skip.reason,
176
+ lifecycle: skip.lifecycle?.state ?? null,
177
+ }))
178
+ .filter((skip) => skip.reason !== "already_archived");
179
+ const skippedByReason = Object.fromEntries([
180
+ "already_archived",
181
+ "blocked_title",
182
+ "lifecycle_active",
183
+ "lifecycle_blocked",
184
+ "unmatched",
185
+ ].map((reason) => [
186
+ reason,
187
+ plan.skipped.filter((skip) => skip.reason === reason).length,
188
+ ]));
189
+ if (!opts.yes) {
190
+ emit.data({
191
+ data_dir: dir,
192
+ dry_run: true,
193
+ planned,
194
+ skipped,
195
+ skipped_by_reason: skippedByReason,
196
+ hint: plan.actions.length > 0 ? "re-run with --yes to archive" : "nothing to archive",
197
+ });
198
+ continue;
199
+ }
200
+ const result = applyTidy(plan);
201
+ emit.data({
202
+ data_dir: dir,
203
+ dry_run: false,
204
+ archived: result.archived,
205
+ already_archived: result.alreadyArchived,
206
+ planned,
207
+ skipped,
208
+ skipped_by_reason: skippedByReason,
209
+ hint: result.archived > 0
210
+ ? "fully quit the Claude desktop app (tray icon, not the X) and relaunch to refresh the sidebar"
211
+ : "nothing to archive",
212
+ });
213
+ }
214
+ });
144
215
  }
145
216
  function collect(value, previous) {
146
217
  return previous.concat([value]);
@@ -12,14 +12,16 @@
12
12
  */
13
13
  import type { Command } from "commander";
14
14
  import type { EmitContext } from "../commander.js";
15
+ import { type CodexHookAuthorizationResult } from "../core/hooks/codex-authorization.js";
15
16
  type Severity = "ok" | "warn" | "fail";
16
- interface Check {
17
+ export interface Check {
17
18
  name: string;
18
19
  severity: Severity;
19
20
  detail: string;
20
21
  hint?: string;
21
22
  }
22
23
  export declare function registerDoctorCommand(program: Command, emit: EmitContext): void;
23
- export declare function runChecks(): Check[];
24
+ export declare function runChecks(): Promise<Check[]>;
25
+ export declare function codexAuthorizationCheck(result: CodexHookAuthorizationResult): Check;
24
26
  export {};
25
27
  //# sourceMappingURL=doctor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAgBnD,KAAK,QAAQ,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;AAEvC,UAAU,KAAK;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAOD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAmD/E;AAED,wBAAgB,SAAS,IAAI,KAAK,EAAE,CAsBnC"}
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAKnD,OAAO,EACL,KAAK,4BAA4B,EAElC,MAAM,sCAAsC,CAAC;AAe9C,KAAK,QAAQ,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;AAEvC,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAOD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAmD/E;AAED,wBAAsB,SAAS,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC,CAyBlD;AA4CD,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,4BAA4B,GAAG,KAAK,CAoBnF"}
@@ -18,7 +18,8 @@ import { BUILTIN_ADAPTER_IDS } from "../core/adapters/index.js";
18
18
  import { resolveBinName, ripgrepAutoInstall } from "../core/config.js";
19
19
  import { ADAPTER_SPECS } from "../core/hooks/adapter/events.js";
20
20
  import { loadAdapterWiring, summarizeAdapterWiring } from "../core/hooks/adapter/wiring.js";
21
- import { inspectCodexWslBridge } from "../core/hooks/codex-wsl-bridge.js";
21
+ import { probeCodexHookAuthorization, } from "../core/hooks/codex-authorization.js";
22
+ import { inspectCodexWslBridge, } from "../core/hooks/codex-wsl-bridge.js";
22
23
  import { ADAPTER_BINARIES, ADAPTER_INSTALL_HINTS, ADAPTER_LOGIN_HINTS, } from "../core/workflow/adapters.js";
23
24
  import { probeBilling } from "../core/workflow/billing.js";
24
25
  import { checkGitHooks, gitHooksInstalled } from "../lib/instructions/git-hooks.js";
@@ -39,7 +40,7 @@ export function registerDoctorCommand(program, emit) {
39
40
  emit.text(`ripgrep install failed: ${err.message}\n`);
40
41
  }
41
42
  }
42
- const checks = runChecks();
43
+ const checks = await runChecks();
43
44
  const requiredFailed = checks.some((c) => c.severity === "fail");
44
45
  if (opts.json) {
45
46
  emit.data({
@@ -67,13 +68,15 @@ export function registerDoctorCommand(program, emit) {
67
68
  emit.setExitCode(requiredFailed ? 1 : 0);
68
69
  });
69
70
  }
70
- export function runChecks() {
71
- const codexWslBridge = checkCodexWslBridge();
71
+ export async function runChecks() {
72
+ const codexWslStatus = inspectCodexWslBridge();
73
+ const codexWslBridge = checkCodexWslBridge(codexWslStatus);
72
74
  // Probe the adapter CLIs before the hook check runs, not in output order:
73
75
  // an unwired adapter only matters if its CLI is actually installed, and
74
76
  // probing once here keeps that join to one spawn per CLI.
75
77
  const workflow = BUILTIN_ADAPTER_IDS.map((id) => ({ id, ...checkWorkflowAdapter(id) }));
76
78
  const installed = workflow.filter((w) => w.installed).map((w) => w.id);
79
+ const codexAuthorization = await checkCodexHookAuthorization(installed, codexWslStatus);
77
80
  return [
78
81
  checkNode(),
79
82
  checkGit(),
@@ -81,6 +84,7 @@ export function runChecks() {
81
84
  checkRipgrep(),
82
85
  checkHarneryDir(),
83
86
  checkAdapterHooks(installed),
87
+ ...(codexAuthorization ? [codexAuthorization] : []),
84
88
  checkGitHookRegions(),
85
89
  ...(codexWslBridge ? [codexWslBridge] : []),
86
90
  ...workflow.map((w) => w.check),
@@ -119,8 +123,7 @@ function checkGitHookRegions() {
119
123
  };
120
124
  }
121
125
  }
122
- function checkCodexWslBridge() {
123
- const status = inspectCodexWslBridge();
126
+ function checkCodexWslBridge(status) {
124
127
  if (!status)
125
128
  return null;
126
129
  return {
@@ -132,6 +135,42 @@ function checkCodexWslBridge() {
132
135
  : "forward CODEX_THREAD_ID through WSLENV in the Codex shell environment policy, then start a fresh task",
133
136
  };
134
137
  }
138
+ export function codexAuthorizationCheck(result) {
139
+ const reviewHint = "terminal UI: run `/hooks`; Codex Desktop: open Settings > Hooks; after approval, start a fresh task";
140
+ if (result.status === "runnable") {
141
+ return { name: "codex:hook authorization", severity: "ok", detail: result.detail };
142
+ }
143
+ if (result.status === "review_required" || result.status === "disabled") {
144
+ return {
145
+ name: "codex:hook authorization",
146
+ severity: "warn",
147
+ detail: result.detail,
148
+ hint: reviewHint,
149
+ };
150
+ }
151
+ return {
152
+ name: "codex:hook authorization",
153
+ severity: "warn",
154
+ detail: `authorization unverified: ${result.detail}`,
155
+ hint: reviewHint,
156
+ };
157
+ }
158
+ async function checkCodexHookAuthorization(installedAdapters, codexWslStatus) {
159
+ const root = findCoordProjectRoot();
160
+ if (!root || !summarizeAdapterWiring(root).wired.includes("codex"))
161
+ return null;
162
+ if (codexWslStatus) {
163
+ return {
164
+ name: "codex:hook authorization",
165
+ severity: "warn",
166
+ detail: "unverified from WSL; the Windows-native Codex runtime owns this workspace's trust state",
167
+ hint: "run the host's Windows bridge doctor, or open Codex Desktop Settings > Hooks; start a fresh task after approval",
168
+ };
169
+ }
170
+ if (!installedAdapters.includes("codex"))
171
+ return null;
172
+ return codexAuthorizationCheck(await probeCodexHookAuthorization({ cwd: root }));
173
+ }
135
174
  function checkRipgrep() {
136
175
  const found = findRg();
137
176
  if (found) {
@@ -20,6 +20,7 @@ import type { EmitContext } from "../commander.js";
20
20
  import { type AdapterId, type AdapterSpec } from "../core/hooks/adapter/events.js";
21
21
  import { type SettingsFile } from "../core/hooks/adapter/wiring.js";
22
22
  export declare function registerInitCommand(program: Command, emit: EmitContext, binName?: string): void;
23
+ export declare function codexHookReviewAction(adapter: AdapterId): string | null;
23
24
  /**
24
25
  * Merge agent-hook entries into a adapter settings object in place, idempotently.
25
26
  * Preserves every existing hook; rewrites events already wired to
@@ -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,EAKL,KAAK,YAAY,EAClB,MAAM,iCAAiC,CAAC;AAmBzC,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAuJ/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"}
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,EAKL,KAAK,YAAY,EAClB,MAAM,iCAAiC,CAAC;AAmBzC,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAyJ/F;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,SAAS,GAAG,MAAM,GAAG,IAAI,CAOvE;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"}