@windyroad/voice-tone 0.9.7 → 0.9.8

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.
@@ -145,5 +145,5 @@
145
145
  }
146
146
  },
147
147
  "name": "wr-voice-tone",
148
- "version": "0.9.7"
148
+ "version": "0.9.8"
149
149
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wr-voice-tone",
3
- "version": "0.9.7",
3
+ "version": "0.9.8",
4
4
  "description": "Voice, tone, and external-communications governance",
5
5
  "author": {
6
6
  "name": "Windy Road Technology",
package/hooks/hooks.json CHANGED
@@ -145,6 +145,17 @@
145
145
  }
146
146
  ]
147
147
  }
148
+ ],
149
+ "SubagentStart": [
150
+ {
151
+ "matcher": "^wr-voice-tone:agent$",
152
+ "hooks": [
153
+ {
154
+ "type": "command",
155
+ "command": "node \"${PLUGIN_ROOT}/hooks-codex/codex-agent-completion.mjs\""
156
+ }
157
+ ]
158
+ }
148
159
  ]
149
160
  }
150
161
  }
@@ -37,6 +37,10 @@ function registrationPath(sessionId, target) {
37
37
  return join(transportDir(), `registration-${transportId(sessionId, target)}.json`);
38
38
  }
39
39
 
40
+ function registeredPath(value) { return registrationPath(value.parentSession, value.source === "subagent-start" ? "native:" + value.target : value.target); }
41
+ function nativePath(sessionId) { return join(stateDir(sessionId), "native-review-" + role); }
42
+ function nativeEnabled(input) { return role !== "wr-voice-tone:external-comms" && existsSync(nativePath(input.session_id)); }
43
+
40
44
  function receiptPath(sessionId, target, suffix = "") {
41
45
  return join(transportDir(), `receipt-${transportId(sessionId, target)}.json${suffix}`);
42
46
  }
@@ -136,7 +140,30 @@ function clear(input, target) {
136
140
  for (const suffix of ["", ".claim", ".done"]) rmSync(receiptPath(input.session_id, target, suffix), { force: true });
137
141
  }
138
142
 
143
+ function rememberNative(input) {
144
+ if (role === "wr-voice-tone:external-comms" || input.agent_type !== role || typeof input.agent_id !== "string" || !/^[A-Za-z0-9-]+$/.test(input.agent_id)) return;
145
+ const bound = checkout(input.cwd);
146
+ const hash = bound && policyHash(bound.root);
147
+ if (!bound || !hash) return diagnostic("invalid-native-binding", input);
148
+ const target = input.agent_id;
149
+ if (existsSync(receiptPath(input.session_id, target, ".done")) || existsSync(statePath(input, target, ".done"))) return;
150
+ const registered = { source: "subagent-start", parentSession: input.session_id, role, target, ...bound, policyHash: hash, createdAt: Date.now() };
151
+ mkdirSync(transportDir(), { recursive: true, mode: 0o700 });
152
+ try { writeFileSync(registeredPath(registered), JSON.stringify(registered), { flag: "wx", mode: 0o600 }); }
153
+ catch (error) {
154
+ if (error?.code !== "EEXIST") throw error;
155
+ const previous = JSON.parse(readFileSync(registeredPath(registered), "utf8"));
156
+ for (const key of ["source", "parentSession", "role", "target", "root", "physical", "policyHash"]) {
157
+ if (previous[key] !== registered[key]) return diagnostic("native-registration-conflict", input);
158
+ }
159
+ }
160
+ mkdirSync(stateDir(input.session_id), { recursive: true });
161
+ try { writeFileSync(nativePath(input.session_id), "", { flag: "wx", mode: 0o600 }); }
162
+ catch (error) { if (error?.code !== "EEXIST") throw error; }
163
+ }
164
+
139
165
  function remember(input) {
166
+ if (nativeEnabled(input)) return;
140
167
  reapTransport();
141
168
  const target = normalizeTarget(targetFromSpawn(input));
142
169
  if (!target) return;
@@ -226,7 +253,7 @@ function writeMarker(input, registered, target, output, completedAt, claimed) {
226
253
  }
227
254
  renameSync(claimed.path, claimed.done);
228
255
  rmSync(statePath({ session_id: registered.parentSession }, target), { force: true });
229
- rmSync(registrationPath(registered.parentSession, target), { force: true });
256
+ rmSync(registeredPath(registered), { force: true });
230
257
  const assessedAt = new Date(completedAt);
231
258
  for (const candidate of [
232
259
  join(process.env.TMPDIR || "/tmp", `voice-tone-reviewed-${registered.parentSession}`),
@@ -246,6 +273,7 @@ function writeMarker(input, registered, target, output, completedAt, claimed) {
246
273
  }
247
274
 
248
275
  function complete(input, target, output) {
276
+ if (nativeEnabled(input)) return;
249
277
  target = completionTarget(target, stateDir(input.session_id), "codex-review-", role + ":");
250
278
  if (!target || typeof output !== "string" || !output) return;
251
279
  const path = statePath(input, target);
@@ -298,8 +326,11 @@ function persistPending(input, target, output) {
298
326
  target = normalizeTarget(target);
299
327
  if (!target || typeof output !== "string" || !output) return;
300
328
  const registrationsForCheckout = registrations(input, target);
301
- const exact = registrationsForCheckout.filter(({ registered }) => registered.target === target);
302
- const candidates = exact.length ? exact : registrationsForCheckout.filter(({ registered }) => registered.parentSession === input.session_id);
329
+ const native = registrationsForCheckout.filter(({ registered }) => registered.source === "subagent-start");
330
+ const nativeExact = native.filter(({ registered }) => registered.parentSession === input.session_id && registered.target === input.agent_id);
331
+ const legacy = registrationsForCheckout.filter(({ registered }) => registered.source !== "subagent-start");
332
+ const exact = legacy.filter(({ registered }) => registered.target === target);
333
+ const candidates = nativeEnabled(input) || nativeExact.length ? nativeExact : exact.length ? exact : legacy.filter(({ registered }) => registered.parentSession === input.session_id);
303
334
  if (candidates.length !== 1) {
304
335
  diagnostic(candidates.length ? "ambiguous-parent-registration" : "missing-parent-registration", input);
305
336
  return;
@@ -335,6 +366,7 @@ function consumePending(input) {
335
366
  continue;
336
367
  }
337
368
  if (pending.parentSession !== input.session_id || pending.role !== role) continue;
369
+ if (nativeEnabled(input) && pending.source !== "subagent-start") continue;
338
370
  if (!validRegistration(pending, input, pending.target, age)) continue;
339
371
  const claimPath = path + ".claim";
340
372
  const donePath = path + ".done";
@@ -369,6 +401,7 @@ if (!/^[0-9]+$/.test(ttlSeconds) || !Number.isSafeInteger(ttl) || ttl <= 0) {
369
401
  }
370
402
 
371
403
  try {
404
+ if (input.hook_event_name === "SubagentStart") rememberNative(input);
372
405
  if (SPAWN_TOOLS.has(input.tool_name)) remember(input);
373
406
  if (CLOSE_TOOLS.has(input.tool_name)) close(input);
374
407
  if (WAIT_TOOLS.has(input.tool_name)) wait(input);
@@ -378,7 +411,7 @@ try {
378
411
  else {
379
412
  const target = input.task_name || input.agent_name || input.agent_id;
380
413
  const local = statePath(input, normalizeTarget(target));
381
- if (existsSync(local)) complete(input, target, input.last_assistant_message);
414
+ if (!nativeEnabled(input) && existsSync(local)) complete(input, target, input.last_assistant_message);
382
415
  else persistPending(input, target, input.last_assistant_message);
383
416
  }
384
417
  }
@@ -37,6 +37,10 @@ function registrationPath(sessionId, target) {
37
37
  return join(transportDir(), `registration-${transportId(sessionId, target)}.json`);
38
38
  }
39
39
 
40
+ function registeredPath(value) { return registrationPath(value.parentSession, value.source === "subagent-start" ? "native:" + value.target : value.target); }
41
+ function nativePath(sessionId) { return join(stateDir(sessionId), "native-review-" + role); }
42
+ function nativeEnabled(input) { return role !== "wr-voice-tone:external-comms" && existsSync(nativePath(input.session_id)); }
43
+
40
44
  function receiptPath(sessionId, target, suffix = "") {
41
45
  return join(transportDir(), `receipt-${transportId(sessionId, target)}.json${suffix}`);
42
46
  }
@@ -136,7 +140,30 @@ function clear(input, target) {
136
140
  for (const suffix of ["", ".claim", ".done"]) rmSync(receiptPath(input.session_id, target, suffix), { force: true });
137
141
  }
138
142
 
143
+ function rememberNative(input) {
144
+ if (role === "wr-voice-tone:external-comms" || input.agent_type !== role || typeof input.agent_id !== "string" || !/^[A-Za-z0-9-]+$/.test(input.agent_id)) return;
145
+ const bound = checkout(input.cwd);
146
+ const hash = bound && policyHash(bound.root);
147
+ if (!bound || !hash) return diagnostic("invalid-native-binding", input);
148
+ const target = input.agent_id;
149
+ if (existsSync(receiptPath(input.session_id, target, ".done")) || existsSync(statePath(input, target, ".done"))) return;
150
+ const registered = { source: "subagent-start", parentSession: input.session_id, role, target, ...bound, policyHash: hash, createdAt: Date.now() };
151
+ mkdirSync(transportDir(), { recursive: true, mode: 0o700 });
152
+ try { writeFileSync(registeredPath(registered), JSON.stringify(registered), { flag: "wx", mode: 0o600 }); }
153
+ catch (error) {
154
+ if (error?.code !== "EEXIST") throw error;
155
+ const previous = JSON.parse(readFileSync(registeredPath(registered), "utf8"));
156
+ for (const key of ["source", "parentSession", "role", "target", "root", "physical", "policyHash"]) {
157
+ if (previous[key] !== registered[key]) return diagnostic("native-registration-conflict", input);
158
+ }
159
+ }
160
+ mkdirSync(stateDir(input.session_id), { recursive: true });
161
+ try { writeFileSync(nativePath(input.session_id), "", { flag: "wx", mode: 0o600 }); }
162
+ catch (error) { if (error?.code !== "EEXIST") throw error; }
163
+ }
164
+
139
165
  function remember(input) {
166
+ if (nativeEnabled(input)) return;
140
167
  reapTransport();
141
168
  const target = normalizeTarget(targetFromSpawn(input));
142
169
  if (!target) return;
@@ -226,7 +253,7 @@ function writeMarker(input, registered, target, output, completedAt, claimed) {
226
253
  }
227
254
  renameSync(claimed.path, claimed.done);
228
255
  rmSync(statePath({ session_id: registered.parentSession }, target), { force: true });
229
- rmSync(registrationPath(registered.parentSession, target), { force: true });
256
+ rmSync(registeredPath(registered), { force: true });
230
257
  const assessedAt = new Date(completedAt);
231
258
  for (const candidate of [
232
259
  join(process.env.TMPDIR || "/tmp", `voice-tone-reviewed-${registered.parentSession}`),
@@ -246,6 +273,7 @@ function writeMarker(input, registered, target, output, completedAt, claimed) {
246
273
  }
247
274
 
248
275
  function complete(input, target, output) {
276
+ if (nativeEnabled(input)) return;
249
277
  target = completionTarget(target, stateDir(input.session_id), "codex-review-", role + ":");
250
278
  if (!target || typeof output !== "string" || !output) return;
251
279
  const path = statePath(input, target);
@@ -298,8 +326,11 @@ function persistPending(input, target, output) {
298
326
  target = normalizeTarget(target);
299
327
  if (!target || typeof output !== "string" || !output) return;
300
328
  const registrationsForCheckout = registrations(input, target);
301
- const exact = registrationsForCheckout.filter(({ registered }) => registered.target === target);
302
- const candidates = exact.length ? exact : registrationsForCheckout.filter(({ registered }) => registered.parentSession === input.session_id);
329
+ const native = registrationsForCheckout.filter(({ registered }) => registered.source === "subagent-start");
330
+ const nativeExact = native.filter(({ registered }) => registered.parentSession === input.session_id && registered.target === input.agent_id);
331
+ const legacy = registrationsForCheckout.filter(({ registered }) => registered.source !== "subagent-start");
332
+ const exact = legacy.filter(({ registered }) => registered.target === target);
333
+ const candidates = nativeEnabled(input) || nativeExact.length ? nativeExact : exact.length ? exact : legacy.filter(({ registered }) => registered.parentSession === input.session_id);
303
334
  if (candidates.length !== 1) {
304
335
  diagnostic(candidates.length ? "ambiguous-parent-registration" : "missing-parent-registration", input);
305
336
  return;
@@ -335,6 +366,7 @@ function consumePending(input) {
335
366
  continue;
336
367
  }
337
368
  if (pending.parentSession !== input.session_id || pending.role !== role) continue;
369
+ if (nativeEnabled(input) && pending.source !== "subagent-start") continue;
338
370
  if (!validRegistration(pending, input, pending.target, age)) continue;
339
371
  const claimPath = path + ".claim";
340
372
  const donePath = path + ".done";
@@ -369,6 +401,7 @@ if (!/^[0-9]+$/.test(ttlSeconds) || !Number.isSafeInteger(ttl) || ttl <= 0) {
369
401
  }
370
402
 
371
403
  try {
404
+ if (input.hook_event_name === "SubagentStart") rememberNative(input);
372
405
  if (SPAWN_TOOLS.has(input.tool_name)) remember(input);
373
406
  if (CLOSE_TOOLS.has(input.tool_name)) close(input);
374
407
  if (WAIT_TOOLS.has(input.tool_name)) wait(input);
@@ -378,7 +411,7 @@ try {
378
411
  else {
379
412
  const target = input.task_name || input.agent_name || input.agent_id;
380
413
  const local = statePath(input, normalizeTarget(target));
381
- if (existsSync(local)) complete(input, target, input.last_assistant_message);
414
+ if (!nativeEnabled(input) && existsSync(local)) complete(input, target, input.last_assistant_message);
382
415
  else persistPending(input, target, input.last_assistant_message);
383
416
  }
384
417
  }
@@ -145,6 +145,17 @@
145
145
  }
146
146
  ]
147
147
  }
148
+ ],
149
+ "SubagentStart": [
150
+ {
151
+ "matcher": "^wr-voice-tone:agent$",
152
+ "hooks": [
153
+ {
154
+ "type": "command",
155
+ "command": "node \"${PLUGIN_ROOT}/hooks-codex/codex-agent-completion.mjs\""
156
+ }
157
+ ]
158
+ }
148
159
  ]
149
160
  }
150
161
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windyroad/voice-tone",
3
- "version": "0.9.7",
3
+ "version": "0.9.8",
4
4
  "description": "Voice and tone enforcement for user-facing copy",
5
5
  "bin": {
6
6
  "windyroad-voice-tone": "./bin/install.mjs"