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
@@ -12,6 +12,7 @@
12
12
  * Owner identity invariant: every action operates on `.harnery/active/<instance_id>.json`
13
13
  * (the file IS the heartbeat). No mutations happen elsewhere.
14
14
  */
15
+ import { type AgentActivity, type SessionStateEvidenceEvent, type TaskState } from "./session-state.js";
15
16
  export interface Heartbeat {
16
17
  schema_version?: number;
17
18
  instance_id: string;
@@ -27,6 +28,12 @@ export interface Heartbeat {
27
28
  files_touched: string[];
28
29
  task?: string;
29
30
  task_updated_at?: string | null;
31
+ activity?: AgentActivity;
32
+ activity_updated_at?: string;
33
+ activity_source?: string;
34
+ task_state?: TaskState;
35
+ task_state_updated_at?: string;
36
+ task_state_reason?: string;
30
37
  /** Session name built on the first non-empty set-task (never rebuilt). Its
31
38
  * presence is the "this session has been named" signal the prompt-context
32
39
  * nudge and the Stop-hook naming rule key on. */
@@ -50,6 +57,12 @@ export interface Heartbeat {
50
57
  [extra: string]: unknown;
51
58
  }
52
59
  export declare function readHeartbeat(coordRoot: string, instanceId: string): Heartbeat | null;
60
+ /** Apply one evidence event to the live state fields without draining the
61
+ * global event stream. This keeps permission prompts current while avoiding
62
+ * unrelated projector side effects before a tool authorization verdict. */
63
+ export declare function stampSessionStateEvent(coordRoot: string, instanceId: string, event: Omit<SessionStateEvidenceEvent, "data"> & {
64
+ data: unknown;
65
+ }): Heartbeat | null;
53
66
  export declare function setTask(coordRoot: string, instanceId: string, task: string): Heartbeat | null;
54
67
  /**
55
68
  * Build the copy-pasteable session name from the coord identity + the agent's
@@ -60,6 +73,8 @@ export declare function buildSuggestedName(agentName: string, descriptionParts:
60
73
  suggestedName: string;
61
74
  description: string;
62
75
  } | null;
76
+ /** Build the operator-facing title projected from an explicit task lifecycle. */
77
+ export declare function buildLifecycleSuggestedName(agentName: string, task: string | undefined, state: TaskState): string | null;
63
78
  /** Stamp the sighting once the suggested name has been observed in assistant
64
79
  * reply text; later turns skip the transcript scan. Records WHICH name was seen,
65
80
  * because the skip is only valid for that one: if a later set-task mints a
@@ -1 +1 @@
1
- {"version":3,"file":"heartbeat-writer.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/heartbeat-writer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"heartbeat-writer.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/heartbeat-writer.ts"],"names":[],"mappings":"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"}
@@ -15,6 +15,7 @@
15
15
  import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from "node:fs";
16
16
  import { dirname, join } from "node:path";
17
17
  import { emit } from "../events/emit.js";
18
+ import { applySessionStateEvent, } from "./session-state.js";
18
19
  /** Inline adapter normalizer (mirrors canonical-emit.normalizeAdapter), kept
19
20
  * local so the writer's only cross-module dep is the in-process emit(). */
20
21
  function adapterOf(platform) {
@@ -80,6 +81,26 @@ export function readHeartbeat(coordRoot, instanceId) {
80
81
  return null;
81
82
  }
82
83
  }
84
+ /** Apply one evidence event to the live state fields without draining the
85
+ * global event stream. This keeps permission prompts current while avoiding
86
+ * unrelated projector side effects before a tool authorization verdict. */
87
+ export function stampSessionStateEvent(coordRoot, instanceId, event) {
88
+ return mutate(coordRoot, instanceId, (heartbeat) => {
89
+ const data = typeof event.data === "object" && event.data !== null && !Array.isArray(event.data)
90
+ ? event.data
91
+ : {};
92
+ const state = applySessionStateEvent(heartbeat, { ...event, data });
93
+ return {
94
+ ...heartbeat,
95
+ activity: state.activity,
96
+ activity_updated_at: state.activity_updated_at,
97
+ activity_source: state.activity_source,
98
+ task_state: state.task_state,
99
+ task_state_updated_at: state.task_state_updated_at,
100
+ task_state_reason: state.task_state_reason,
101
+ };
102
+ });
103
+ }
83
104
  function mutate(coordRoot, instanceId, fn) {
84
105
  const hb = readHeartbeat(coordRoot, instanceId);
85
106
  if (!hb)
@@ -120,6 +141,17 @@ export function buildSuggestedName(agentName, descriptionParts) {
120
141
  const name = agentName?.trim() || "unknown";
121
142
  return { suggestedName: `Agent ${name} - ${description}`, description };
122
143
  }
144
+ /** Build the operator-facing title projected from an explicit task lifecycle. */
145
+ export function buildLifecycleSuggestedName(agentName, task, state) {
146
+ const built = buildSuggestedName(agentName, task ? [task] : []);
147
+ if (!built)
148
+ return null;
149
+ if (state === "blocked")
150
+ return `[BLOCKED] - ${built.suggestedName}`;
151
+ if (state === "done")
152
+ return `[DONE] - ${built.suggestedName}`;
153
+ return built.suggestedName;
154
+ }
123
155
  /** Stamp the sighting once the suggested name has been observed in assistant
124
156
  * reply text; later turns skip the transcript scan. Records WHICH name was seen,
125
157
  * because the skip is only valid for that one: if a later set-task mints a
@@ -0,0 +1,44 @@
1
+ /** What the agent is doing now. `unknown` is evidence-safe, not an error. */
2
+ export type AgentActivity = "unknown" | "working" | "needs_input" | "idle";
3
+ /** Whether the declared task remains open. Activity never changes this axis. */
4
+ export type TaskState = "active" | "blocked" | "done";
5
+ /** Raw fields stored in a live heartbeat and rebuilt from canonical events. */
6
+ export interface SessionStateFields {
7
+ activity?: AgentActivity;
8
+ activity_updated_at?: string;
9
+ activity_source?: string;
10
+ task_state?: TaskState;
11
+ task_state_updated_at?: string;
12
+ task_state_reason?: string;
13
+ }
14
+ /** Reader-facing state with compatibility defaults applied. */
15
+ export interface SessionState extends SessionStateFields {
16
+ activity: AgentActivity;
17
+ task_state: TaskState;
18
+ }
19
+ export interface SessionStateSelector {
20
+ instance_id?: string;
21
+ session_id?: string;
22
+ }
23
+ export interface SessionStateEvidenceEvent {
24
+ event_type: string;
25
+ ts: string;
26
+ data?: Record<string, unknown>;
27
+ instance_id?: string;
28
+ session_id?: string;
29
+ }
30
+ /**
31
+ * Apply one canonical event to the two independent state axes.
32
+ *
33
+ * The table is deliberately evidence-only. Output chunks, post-tool events,
34
+ * narration, coordination commands, and elapsed time cannot clear an observed
35
+ * input wait. A command start only counts as progress when the session was
36
+ * already inside an open turn (`working` or `needs_input`).
37
+ */
38
+ export declare function applySessionStateEvent(current: Readonly<SessionStateFields>, event: SessionStateEvidenceEvent): SessionStateFields;
39
+ /**
40
+ * Rebuild both axes from canonical events after a heartbeat has disappeared.
41
+ * Events may be the whole ledger or an already-filtered session slice.
42
+ */
43
+ export declare function foldSessionState(events: readonly SessionStateEvidenceEvent[], selector?: SessionStateSelector): SessionState;
44
+ //# sourceMappingURL=session-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-state.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/session-state.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,SAAS,GAAG,aAAa,GAAG,MAAM,CAAC;AAE3E,gFAAgF;AAChF,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAC;AAEtD,+EAA+E;AAC/E,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,+DAA+D;AAC/D,MAAM,WAAW,YAAa,SAAQ,kBAAkB;IACtD,QAAQ,EAAE,aAAa,CAAC;IACxB,UAAU,EAAE,SAAS,CAAC;CACvB;AAED,MAAM,WAAW,oBAAoB;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,yBAAyB;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAID;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,QAAQ,CAAC,kBAAkB,CAAC,EACrC,KAAK,EAAE,yBAAyB,GAC/B,kBAAkB,CAkCpB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,SAAS,yBAAyB,EAAE,EAC5C,QAAQ,GAAE,oBAAyB,GAClC,YAAY,CAcd"}
@@ -0,0 +1,81 @@
1
+ const TERMINAL_ACTIVITY_EVENTS = new Set(["session.end", "subagent.stop", "turn.stop"]);
2
+ /**
3
+ * Apply one canonical event to the two independent state axes.
4
+ *
5
+ * The table is deliberately evidence-only. Output chunks, post-tool events,
6
+ * narration, coordination commands, and elapsed time cannot clear an observed
7
+ * input wait. A command start only counts as progress when the session was
8
+ * already inside an open turn (`working` or `needs_input`).
9
+ */
10
+ export function applySessionStateEvent(current, event) {
11
+ const next = { ...current };
12
+ if (event.event_type === "session.start" || event.event_type === "subagent.start") {
13
+ setActivity(next, "idle", event);
14
+ }
15
+ else if (event.event_type === "user_prompt.submit" || event.event_type === "tool.pre_use") {
16
+ setActivity(next, "working", event);
17
+ }
18
+ else if (event.event_type === "interaction.input_requested") {
19
+ setActivity(next, "needs_input", event);
20
+ }
21
+ else if (event.event_type === "command.start" &&
22
+ (current.activity === "working" || current.activity === "needs_input")) {
23
+ setActivity(next, "working", event);
24
+ }
25
+ else if (TERMINAL_ACTIVITY_EVENTS.has(event.event_type)) {
26
+ setActivity(next, "idle", event);
27
+ }
28
+ if (event.event_type === "state.task_state") {
29
+ const data = event.data ?? {};
30
+ const state = field(data, "state");
31
+ if (state === "active" || state === "blocked" || state === "done") {
32
+ next.task_state = state;
33
+ next.task_state_updated_at = event.ts;
34
+ const reason = field(data, "reason");
35
+ if (typeof reason === "string" && reason.length > 0) {
36
+ next.task_state_reason = reason;
37
+ }
38
+ else {
39
+ delete next.task_state_reason;
40
+ }
41
+ }
42
+ }
43
+ return next;
44
+ }
45
+ /**
46
+ * Rebuild both axes from canonical events after a heartbeat has disappeared.
47
+ * Events may be the whole ledger or an already-filtered session slice.
48
+ */
49
+ export function foldSessionState(events, selector = {}) {
50
+ const ordered = events
51
+ .map((event, index) => ({ event, index }))
52
+ .filter(({ event }) => matches(event, selector))
53
+ .sort((a, b) => timestamp(a.event) - timestamp(b.event) || a.index - b.index);
54
+ let fields = {};
55
+ for (const { event } of ordered)
56
+ fields = applySessionStateEvent(fields, event);
57
+ return {
58
+ ...fields,
59
+ activity: fields.activity ?? "unknown",
60
+ task_state: fields.task_state ?? "active",
61
+ };
62
+ }
63
+ function setActivity(target, activity, event) {
64
+ target.activity = activity;
65
+ target.activity_updated_at = event.ts;
66
+ target.activity_source = event.event_type;
67
+ }
68
+ function matches(event, selector) {
69
+ if (selector.instance_id && event.instance_id !== selector.instance_id)
70
+ return false;
71
+ if (selector.session_id && event.session_id !== selector.session_id)
72
+ return false;
73
+ return true;
74
+ }
75
+ function timestamp(event) {
76
+ const parsed = Date.parse(event.ts);
77
+ return Number.isFinite(parsed) ? parsed : 0;
78
+ }
79
+ function field(data, key) {
80
+ return data[key];
81
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../../../src/core/hooks/adapter/events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,YAAY,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAEhE,OAAO,KAAK,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAEhE,MAAM,WAAW,SAAS;IACxB,6FAA6F;IAC7F,WAAW,EAAE,MAAM,CAAC;IACpB,sDAAsD;IACtD,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEjD,MAAM,WAAW,WAAW;IAC1B,2DAA2D;IAC3D,YAAY,EAAE,MAAM,CAAC;IACrB,mEAAmE;IACnE,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,wDAAwD;IACxD,UAAU,EAAE,cAAc,CAAC;IAC3B,uFAAuF;IACvF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yFAAyF;IACzF,YAAY,CAAC,EAAE,SAAS,EAAE,CAAC;IAC3B,0FAA0F;IAC1F,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC/B,+FAA+F;IAC/F,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,4CAA4C;AAC5C,eAAO,MAAM,kBAAkB,EAAE,SAAS,EAYzC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,aAAa,EAAE,SAAS,EAWpC,CAAC;AAEF,gFAAgF;AAChF,eAAO,MAAM,YAAY,EAAE,SAAS,EAUnC,CAAC;AAEF,mFAAmF;AACnF,eAAO,MAAM,mBAAmB,EAAE,SAAS,EAI1C,CAAC;AAEF,yFAAyF;AACzF,eAAO,MAAM,wBAAwB,UAWpC,CAAC;AAEF,8DAA8D;AAC9D,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,SAAS,EAAE,WAAW,CAqBxD,CAAC"}
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../../../src/core/hooks/adapter/events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,YAAY,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAEhE,OAAO,KAAK,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAEhE,MAAM,WAAW,SAAS;IACxB,6FAA6F;IAC7F,WAAW,EAAE,MAAM,CAAC;IACpB,sDAAsD;IACtD,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEjD,MAAM,WAAW,WAAW;IAC1B,2DAA2D;IAC3D,YAAY,EAAE,MAAM,CAAC;IACrB,mEAAmE;IACnE,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,wDAAwD;IACxD,UAAU,EAAE,cAAc,CAAC;IAC3B,uFAAuF;IACvF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yFAAyF;IACzF,YAAY,CAAC,EAAE,SAAS,EAAE,CAAC;IAC3B,0FAA0F;IAC1F,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC/B,+FAA+F;IAC/F,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,4CAA4C;AAC5C,eAAO,MAAM,kBAAkB,EAAE,SAAS,EAYzC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,aAAa,EAAE,SAAS,EAWpC,CAAC;AAEF,gFAAgF;AAChF,eAAO,MAAM,YAAY,EAAE,SAAS,EAWnC,CAAC;AAEF,mFAAmF;AACnF,eAAO,MAAM,mBAAmB,EAAE,SAAS,EAI1C,CAAC;AAEF,yFAAyF;AACzF,eAAO,MAAM,wBAAwB,UAWpC,CAAC;AAEF,8DAA8D;AAC9D,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,SAAS,EAAE,WAAW,CAqBxD,CAAC"}
@@ -42,6 +42,7 @@ export const CURSOR_EVENTS = [
42
42
  export const CODEX_EVENTS = [
43
43
  { settingsKey: "SessionStart", subcommand: "session-start" },
44
44
  { settingsKey: "PreToolUse", subcommand: "pre-tool-use" },
45
+ { settingsKey: "PermissionRequest", subcommand: "permission-request" },
45
46
  { settingsKey: "PostToolUse", subcommand: "post-tool-use" },
46
47
  { settingsKey: "UserPromptSubmit", subcommand: "user-prompt-submit" },
47
48
  { settingsKey: "SubagentStart", subcommand: "sub-agent-start" },
@@ -63,5 +63,5 @@ export declare function normalizeEventName(eventName: string): {
63
63
  event_type: NormalizedEventType;
64
64
  intra_turn: boolean;
65
65
  } | null;
66
- export type NormalizedEventType = "session.start" | "session.end" | "user_prompt.submit" | "turn.stop" | "subagent.start" | "subagent.stop" | "tool.pre_use" | "tool.post_use" | "tool.post_use_failure" | "context.compaction.started" | "context.compaction.completed";
66
+ export type NormalizedEventType = "session.start" | "session.end" | "user_prompt.submit" | "turn.stop" | "subagent.start" | "subagent.stop" | "tool.pre_use" | "interaction.input_requested" | "tool.post_use" | "tool.post_use_failure" | "context.compaction.started" | "context.compaction.completed";
67
67
  //# sourceMappingURL=parse.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../../../src/core/hooks/adapter/parse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAEnD,MAAM,WAAW,aAAa;IAC5B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uFAAuF;IACvF,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC9B;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,aAAa,GAAG,IAAI,CA+ChF;AAQD;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,SAAS,EAAE,OAAO,GACjB,MAAM,GAAG,SAAS,CASpB;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAO7E;AAmBD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,GAChB;IAAE,UAAU,EAAE,mBAAmB,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAiCjE;AAED,MAAM,MAAM,mBAAmB,GAC3B,eAAe,GACf,aAAa,GACb,oBAAoB,GACpB,WAAW,GACX,gBAAgB,GAChB,eAAe,GACf,cAAc,GACd,eAAe,GACf,uBAAuB,GACvB,4BAA4B,GAC5B,8BAA8B,CAAC"}
1
+ {"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../../../src/core/hooks/adapter/parse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAEnD,MAAM,WAAW,aAAa;IAC5B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uFAAuF;IACvF,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC9B;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,aAAa,GAAG,IAAI,CA+ChF;AAQD;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,SAAS,EAAE,OAAO,GACjB,MAAM,GAAG,SAAS,CASpB;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAO7E;AAmBD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,GAChB;IAAE,UAAU,EAAE,mBAAmB,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAmCjE;AAED,MAAM,MAAM,mBAAmB,GAC3B,eAAe,GACf,aAAa,GACb,oBAAoB,GACpB,WAAW,GACX,gBAAgB,GAChB,eAAe,GACf,cAAc,GACd,6BAA6B,GAC7B,eAAe,GACf,uBAAuB,GACvB,4BAA4B,GAC5B,8BAA8B,CAAC"}
@@ -139,6 +139,8 @@ export function normalizeEventName(eventName) {
139
139
  case "pre-tool-use":
140
140
  case "before-shell-execution":
141
141
  return { event_type: "tool.pre_use", intra_turn: true };
142
+ case "permission-request":
143
+ return { event_type: "interaction.input_requested", intra_turn: true };
142
144
  case "post-tool-use":
143
145
  return { event_type: "tool.post_use", intra_turn: true };
144
146
  case "post-tool-use-failure":
@@ -20,12 +20,14 @@ import { spawnSync } from "node:child_process";
20
20
  import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
21
21
  import { dirname, join } from "node:path";
22
22
  import { coordEnv } from "../../lib/env.js";
23
+ import { buildInstructionBundle } from "../../lib/instructions/bundle.js";
23
24
  import { replayCodexJsonl } from "../agents/codex-replay.js";
24
25
  import { coordBinPath } from "../agents/coord-bin.js";
25
26
  import { consumeSince, writeCursor } from "../agents/events/consume.js";
26
27
  import { classifyWriteClaimFinalization, formatWriteClaimFinalizationDenial, } from "../agents/finalization.js";
27
28
  import { evaluateStopHook } from "../agents/rules/stop-hook.js";
28
29
  import { projectHeartbeats } from "../agents/state/heartbeat-projector.js";
30
+ import { stampSessionStateEvent } from "../agents/state/heartbeat-writer.js";
29
31
  import { writePidmapRow } from "../agents/state/pidmap.js";
30
32
  import { shellMutationPaths } from "../agents/state/shell-mutation.js";
31
33
  import { agentsRequireGitFinalization, resolveBinName } from "../config.js";
@@ -210,6 +212,7 @@ function buildEventData(eventType, ctx) {
210
212
  if (adapterPid) {
211
213
  writePidmapViaAgentCoord(ctx.coordRoot, adapterPid, ctx.instanceId, adapterPlatform);
212
214
  }
215
+ const bundle = safeInstructionBundle(ctx.coordRoot, p?.cwd ?? process.cwd(), ctx.adapter);
213
216
  return {
214
217
  started_at: new Date().toISOString(),
215
218
  cwd: p?.cwd ?? process.cwd(),
@@ -223,6 +226,14 @@ function buildEventData(eventType, ctx) {
223
226
  name: assigned?.name,
224
227
  kind: "session",
225
228
  agent_id: ctx.instanceId,
229
+ ...(bundle
230
+ ? {
231
+ instruction_bundle_id: bundle.instruction_bundle_id,
232
+ instruction_source_id: bundle.canonical_source_id,
233
+ instruction_profile_root: bundle.profile_root,
234
+ instruction_component_count: bundle.components.length,
235
+ }
236
+ : {}),
226
237
  // Recorded fork lineage rides the canonical event too, so full replay
227
238
  // and derived readers converge on the same parent linkage that
228
239
  // .name-history carries (the ADR 0017 dual-write pattern).
@@ -340,6 +351,15 @@ function buildEventData(eventType, ctx) {
340
351
  ...(clamped.truncated ? { truncated: true } : {}),
341
352
  };
342
353
  }
354
+ case "interaction.input_requested": {
355
+ const description = extractToolDescription(p?.tool_input);
356
+ const reason = description ? clampString(description, 500).value : undefined;
357
+ return {
358
+ request_kind: "permission",
359
+ tool_name: p?.tool_name,
360
+ ...(reason ? { reason } : {}),
361
+ };
362
+ }
343
363
  case "tool.post_use": {
344
364
  const toolName = p?.tool_name ?? "unknown";
345
365
  const summary = summarizeOutput(p?.tool_response);
@@ -386,6 +406,15 @@ function buildEventData(eventType, ctx) {
386
406
  }
387
407
  }
388
408
  }
409
+ function safeInstructionBundle(coordRoot, cwd, adapter) {
410
+ try {
411
+ return buildInstructionBundle({ coordRoot, cwd, adapter });
412
+ }
413
+ catch (err) {
414
+ logError(coordRoot, err, { phase: "instruction-bundle-identity" });
415
+ return null;
416
+ }
417
+ }
389
418
  function objectRecord(value) {
390
419
  return typeof value === "object" && value !== null && !Array.isArray(value)
391
420
  ? value
@@ -488,6 +517,19 @@ async function main() {
488
517
  owner_source: owner.source,
489
518
  event_id: envelope.event_id,
490
519
  });
520
+ // Activity is only useful while it is current. Stamp prompt and permission
521
+ // evidence directly from this envelope rather than draining the global
522
+ // projector: a drain here could apply a tool event before its authorization
523
+ // verdict. Tool progress is stamped later, after the guard allows it.
524
+ if (norm.event_type === "user_prompt.submit" ||
525
+ norm.event_type === "interaction.input_requested") {
526
+ try {
527
+ stampSessionStateEvent(coordRoot, owner.instance_id, envelope);
528
+ }
529
+ catch (err) {
530
+ logError(coordRoot, err, { phase: "activity-projection" });
531
+ }
532
+ }
491
533
  // Context telemetry is opportunistic and truthful: only persist a sample
492
534
  // when the adapter payload actually exposes usage/window data. Identical
493
535
  // measurements are de-duplicated before they reach the canonical stream.
@@ -811,6 +853,23 @@ async function main() {
811
853
  bypass: coordEnv("AGENT_COORD_BYPASS_STOP") === "1",
812
854
  workflow_child: coordEnv("WORKFLOW_CHILD") === "1",
813
855
  });
856
+ const enforcementMode = stopEnforcementMode(verdict.rule);
857
+ emit(coordRoot, {
858
+ event_type: "stop.verdict",
859
+ instance_id: owner.instance_id,
860
+ session_id: sessionId,
861
+ turn_id: payload?.turn_id,
862
+ parent_turn_id: payload?.parent_turn_id,
863
+ adapter,
864
+ data: {
865
+ allow: verdict.allow,
866
+ rule: verdict.rule,
867
+ ...(verdict.reason ? { reason: verdict.reason } : {}),
868
+ enforcement_mode: enforcementMode,
869
+ eligible: enforcementMode === "enforced",
870
+ nag_delivered: enforcementMode === "enforced" && !verdict.allow,
871
+ },
872
+ });
814
873
  if (!verdict.allow) {
815
874
  // Adapter-aware enforcement channel: Claude Code / Codex honor exit-2 +
816
875
  // stderr as a turn block; Cursor ignores exit codes (fail-open) and
@@ -894,12 +953,21 @@ async function main() {
894
953
  // apply_patch (codex) parses paths from the patch body and runs verdict
895
954
  // per-path; Edit/Write/NotebookEdit resolve a single target. Non-write tools
896
955
  // (incl. Agent) yield no targets and pass through with no deny.
956
+ let guardAllowed = true;
897
957
  try {
898
- await runPreToolUseGuard(coordRoot, owner.instance_id, sessionId, data, adapter);
958
+ guardAllowed = await runPreToolUseGuard(coordRoot, owner.instance_id, sessionId, data, adapter);
899
959
  }
900
960
  catch (err) {
901
961
  logError(coordRoot, err, { phase: "pre-tool-use-guard" });
902
962
  }
963
+ if (guardAllowed) {
964
+ try {
965
+ stampSessionStateEvent(coordRoot, owner.instance_id, envelope);
966
+ }
967
+ catch (err) {
968
+ logError(coordRoot, err, { phase: "activity-projection" });
969
+ }
970
+ }
903
971
  // Shell-mutation warn (warn-only, never blocks). Was the cursor
904
972
  // beforeShellExecution + codex preToolUse-Bash shell-mutation-claim-log in
905
973
  // the per-adapter shell adapters. Cursor sends the command at payload.command;
@@ -974,11 +1042,20 @@ async function main() {
974
1042
  }
975
1043
  return 0;
976
1044
  }
1045
+ function stopEnforcementMode(rule) {
1046
+ if (rule === "stop-hook.codex_observe_only")
1047
+ return "observe_only";
1048
+ if (rule === "stop-hook.fail_open" || rule === "stop-hook.no_history")
1049
+ return "fail_open";
1050
+ if (rule === "stop-hook.bypass" || rule === "stop-hook.workflow_child")
1051
+ return "exempt";
1052
+ return "enforced";
1053
+ }
977
1054
  async function runPreToolUseGuard(coordRoot, instanceId, sessionId, data, adapter) {
978
1055
  const toolName = data.tool_name ?? "";
979
1056
  const rawTargets = collectGuardTargets(toolName, data);
980
1057
  if (rawTargets.length === 0)
981
- return;
1058
+ return true;
982
1059
  let targets;
983
1060
  if (agentsRequireGitFinalization(coordRoot)) {
984
1061
  const decisions = rawTargets.map((path) => classifyWriteClaimFinalization(coordRoot, path));
@@ -986,7 +1063,7 @@ async function runPreToolUseGuard(coordRoot, instanceId, sessionId, data, adapte
986
1063
  if (denied && !denied.allow) {
987
1064
  const { emitDeny } = await import("./adapter/output.js");
988
1065
  emitDeny(adapter, formatWriteClaimFinalizationDenial(denied, resolveBinName(coordRoot)));
989
- return;
1066
+ return false;
990
1067
  }
991
1068
  const allowed = decisions.filter((decision) => decision.allow);
992
1069
  targets = allowed.map((decision) => ({
@@ -1001,14 +1078,15 @@ async function runPreToolUseGuard(coordRoot, instanceId, sessionId, data, adapte
1001
1078
  .map((path) => ({ path }));
1002
1079
  }
1003
1080
  if (targets.length === 0)
1004
- return;
1081
+ return true;
1005
1082
  const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
1006
1083
  if (!existsSync(agentCoordBin)) {
1007
1084
  if (agentsRequireGitFinalization(coordRoot)) {
1008
1085
  const { emitDeny } = await import("./adapter/output.js");
1009
1086
  emitDeny(adapter, "Harnery denied the write before mutation because the claim coordinator is unavailable; repair the project's Harnery installation and retry.");
1087
+ return false;
1010
1088
  }
1011
- return;
1089
+ return true;
1012
1090
  }
1013
1091
  // For apply_patch (multi-file), collect siblings so the deny reason names
1014
1092
  // them. For single-file tools the array has one entry.
@@ -1048,7 +1126,7 @@ async function runPreToolUseGuard(coordRoot, instanceId, sessionId, data, adapte
1048
1126
  }
1049
1127
  const { emitDeny } = await import("./adapter/output.js");
1050
1128
  emitDeny(adapter, reason);
1051
- return;
1129
+ return false;
1052
1130
  }
1053
1131
  // The verdict mutates the live heartbeat immediately, but that file is a
1054
1132
  // projection and can be rebuilt at any later hook. Persist the acquisition
@@ -1068,6 +1146,7 @@ async function runPreToolUseGuard(coordRoot, instanceId, sessionId, data, adapte
1068
1146
  },
1069
1147
  });
1070
1148
  }
1149
+ return true;
1071
1150
  }
1072
1151
  /** Canonicalize a path to monorepo-relative form. Absolute paths under
1073
1152
  * coordRoot get the prefix stripped; relative paths pass through (assumed
@@ -1384,11 +1463,16 @@ async function emitUserPromptSubmitSystemMessage(coordRoot, instanceId, sessionI
1384
1463
  // staleness nudges; Claude Code enforces those via its Stop transcript.
1385
1464
  // Codex gets a fresh status-footer reminder on every prompt. Its Stop hook
1386
1465
  // stays observe-only, so missing the footer never replaces the answer.
1466
+ // Claude Code + Cursor (the Stop-ENFORCING adapters) get a fresh per-turn
1467
+ // ritual reminder: satisfying the contract up front is far cheaper than
1468
+ // the bounce-and-retry the Stop hook otherwise forces.
1387
1469
  args.push("--session-name-nudge");
1388
1470
  if (adapter === "cursor" || adapter === "codex")
1389
1471
  args.push("--task-nudge");
1390
1472
  if (adapter === "codex")
1391
1473
  args.push("--status-footer-nudge");
1474
+ if (adapter === "claude-code" || adapter === "cursor")
1475
+ args.push("--turn-ritual-nudge", adapter);
1392
1476
  const result = spawnSync(agentCoordBin, args, { encoding: "utf8", timeout: 3000 });
1393
1477
  if (result.status === 0 && result.stdout)
1394
1478
  additionalContext = result.stdout.trim();
@@ -0,0 +1,30 @@
1
+ export type CodexHookTrustStatus = "managed" | "trusted" | "untrusted" | "modified";
2
+ export type CodexHookAuthorizationStatus = "runnable" | "review_required" | "disabled" | "not_wired" | "error";
3
+ export interface CodexHookAuthorizationResult {
4
+ status: CodexHookAuthorizationStatus;
5
+ detail: string;
6
+ hookCount: number;
7
+ disabledCount: number;
8
+ trustCounts: Partial<Record<CodexHookTrustStatus, number>>;
9
+ }
10
+ export interface CodexHookAuthorizationProbeOptions {
11
+ cwd: string;
12
+ codexBin?: string;
13
+ appServerArgs?: string[];
14
+ env?: NodeJS.ProcessEnv;
15
+ timeoutMs?: number;
16
+ }
17
+ /**
18
+ * Convert Codex app-server's `hooks/list` response into the one authorization
19
+ * state doctor needs. This is intentionally read-only: hashes are observed but
20
+ * never persisted, and only commands containing Harnery's `agent-hook` binary
21
+ * are included in the result.
22
+ */
23
+ export declare function assessCodexHookList(response: unknown, cwd: string): CodexHookAuthorizationResult;
24
+ /**
25
+ * Ask the active Codex CLI for hook authorization through its app-server
26
+ * protocol. The child is closed as soon as `hooks/list` responds. Harnery never
27
+ * calls the trust-write method and never reads Codex's private config format.
28
+ */
29
+ export declare function probeCodexHookAuthorization(options: CodexHookAuthorizationProbeOptions): Promise<CodexHookAuthorizationResult>;
30
+ //# sourceMappingURL=codex-authorization.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codex-authorization.d.ts","sourceRoot":"","sources":["../../../src/core/hooks/codex-authorization.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,oBAAoB,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,UAAU,CAAC;AAEpF,MAAM,MAAM,4BAA4B,GACpC,UAAU,GACV,iBAAiB,GACjB,UAAU,GACV,WAAW,GACX,OAAO,CAAC;AAEZ,MAAM,WAAW,4BAA4B;IAC3C,MAAM,EAAE,4BAA4B,CAAC;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC,MAAM,CAAC,oBAAoB,EAAE,MAAM,CAAC,CAAC,CAAC;CAC5D;AAED,MAAM,WAAW,kCAAkC;IACjD,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA0BD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,4BAA4B,CAqGhG;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,kCAAkC,GAC1C,OAAO,CAAC,4BAA4B,CAAC,CAoFvC"}