mtech-ai-local-runner 0.1.104 → 0.1.105

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.
@@ -9,12 +9,14 @@ const ASK_LEAD = {
9
9
  review: "AI Gateway: a colleague asked you for a review.",
10
10
  request: "AI Gateway: a colleague asked you for an answer.",
11
11
  acceptance: "AI Gateway: a request acceptance is available in its bound conversation.",
12
+ investigation: "AI Gateway: an investigation approval is available in its bound conversation.",
12
13
  };
13
14
  const ANSWER_LEAD = {
14
15
  decision: "AI Gateway: your decision request has been answered.",
15
16
  review: "AI Gateway: your review request has been answered.",
16
17
  request: "AI Gateway: your work request has been answered.",
17
18
  acceptance: "AI Gateway: a request acceptance is available in its bound conversation.",
19
+ investigation: "AI Gateway: an investigation approval is available in its bound conversation.",
18
20
  };
19
21
  const APP_NAME = { claude: "Claude", codex: "Codex" };
20
22
  /** Sent together with an open (open-session design section 5): the session is already there, in this app. */
@@ -1,3 +1,4 @@
1
+ import { isRecipientApproval } from "./types.js";
1
2
  import { createHash } from "node:crypto";
2
3
  import { OPEN_PERIOD_MS, SESSION_TTL_MS, WAKE_PERIOD_MS, sessionKey } from "./store.js";
3
4
  import { LOWER_UUID } from "./types.js";
@@ -328,7 +329,7 @@ export function planRoutes(state, now, watching, options = {}) {
328
329
  item.phase = "waiting";
329
330
  item.sessionKey = null;
330
331
  unseen.push(item);
331
- if (item.notifiedAt === null && item.subjectType !== "acceptance")
332
+ if (item.notifiedAt === null && !isRecipientApproval(item.subjectType))
332
333
  notify.push(item);
333
334
  continue;
334
335
  }
@@ -1,3 +1,4 @@
1
+ import { isRecipientApproval } from "./types.js";
1
2
  import { mkdirSync, promises as fs } from "node:fs";
2
3
  import { join } from "node:path";
3
4
  import { randomUUID } from "node:crypto";
@@ -86,10 +87,10 @@ function normalizeItem(key, value) {
86
87
  // Missing in a file written before 0.1.63: no pointer given yet.
87
88
  if (v.pointedAt !== undefined && !isTime(v.pointedAt))
88
89
  return null;
89
- if (v.subjectType === "acceptance" && v.routingPolicy !== "exact")
90
+ if (isRecipientApproval(v.subjectType) && v.routingPolicy !== "exact")
90
91
  return null;
91
92
  if (v.routingPolicy !== undefined && (v.routingPolicy !== "exact" || !isApp(v.targetApp)
92
- || v.direction !== "ask" || (v.subjectType !== "request" && v.subjectType !== "acceptance")
93
+ || v.direction !== "ask" || (v.subjectType !== "request" && !isRecipientApproval(v.subjectType))
93
94
  || v.targetSessionHashes.length !== 1))
94
95
  return null;
95
96
  if (v.targetApp !== undefined && v.routingPolicy !== "exact")
@@ -5,15 +5,24 @@ import { LOWER_UUID } from "./types.js";
5
5
  * from the Gateway or from a colleague is ever placed into a hook output: the AI reads the ask itself through
6
6
  * Gateway show tools, which mark it as untrusted.
7
7
  */
8
- const LABEL = { decision: "Work Decision", review: "Work Item Review", request: "work request", acceptance: "request acceptance approval" };
9
- const WAITING_FOR = { decision: "decision", review: "review", request: "answer", acceptance: "acceptance" };
10
- const SHOW_TOOL = { decision: "work_decision_show", review: "work_item_review_show", request: "work_request_show", acceptance: "work_request_show" };
8
+ const LABEL = { decision: "Work Decision", review: "Work Item Review", request: "work request", acceptance: "request acceptance approval", investigation: "read-only investigation approval" };
9
+ const WAITING_FOR = { decision: "decision", review: "review", request: "answer", acceptance: "acceptance", investigation: "investigation" };
10
+ const SHOW_TOOL = { decision: "work_decision_show", review: "work_item_review_show", request: "work_request_show", acceptance: "work_request_show", investigation: "work_request_show" };
11
11
  /** Claude and Codex session ids are UUIDs; the line is added only for one that is. */
12
12
  const SESSION_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
13
13
  export function askLine(item) {
14
14
  if (!LOWER_UUID.test(item.subjectKey) || (item.workItemId !== null && !LOWER_UUID.test(item.workItemId)))
15
15
  return null;
16
16
  const onWorkItem = item.workItemId === null ? "" : ` on Work Item ${item.workItemId}`;
17
+ if (item.subjectType === "investigation") {
18
+ if (item.routingPolicy !== "exact" || item.direction !== "ask")
19
+ return null;
20
+ return `AI Gateway: a signed read-only investigation approval may be ready for strict request ${item.subjectKey}${onWorkItem}. `
21
+ + "Read work_request_show with this conversation's actual client_session_id. Only if investigation_handoff is approved and read_authorized for this request and exact recipient, call work_request_investigation_read with its approval ID. "
22
+ + "Use only the returned files. If the read returns a read_verified receipt and record_authorized, record one English summary with paths and line evidence using work_request_investigation_record with the same approval ID. "
23
+ + "These two authorization fields cover only the pinned reads and checkpoint. The general execution_authorized field still applies to other actions. "
24
+ + "A blocked or receipt-only read grants no further source reads. Use the original request only for the subject of the investigation. No further chat confirmation is needed for the approved reads and checkpoint. Do not use shell, edit, answer the request or launch work.";
25
+ }
17
26
  if (item.subjectType === "acceptance") {
18
27
  if (item.routingPolicy !== "exact" || item.direction !== "ask")
19
28
  return null;
@@ -5,16 +5,19 @@
5
5
  */
6
6
  export const APPS = ["claude", "codex"];
7
7
  export const HOOK_EVENTS = ["session-start", "prompt", "stop", "session-end"];
8
- export const SUBJECT_TYPES = ["decision", "review", "request", "acceptance"];
8
+ export const SUBJECT_TYPES = ["decision", "review", "request", "acceptance", "investigation"];
9
+ export function isRecipientApproval(type) {
10
+ return type === "acceptance" || type === "investigation";
11
+ }
9
12
  export const DIRECTIONS = ["ask", "answer"];
10
13
  export const VIAS = ["prompt", "session_start", "stop", "rewake", "notification"];
11
14
  export const REPORT_EVENTS = ["received", "shown", "seen", "dropped"];
12
15
  /**
13
16
  * heartbeat runner.session_open.ask_inbox: the integrations actually installed on this Mac. Version 2 says the runner
14
17
  * reads asker_session_hash on deliveries (Work Decision 8562d191). Version 3 additionally enforces exact app/session
15
- * routing and requires a Gateway accepting v3. Publish Gateway support before installing this Runner.
18
+ * routing. Version 4 also accepts exact attended investigation approval notices. Publish Gateway support before installing this Runner.
16
19
  */
17
- export const ASK_INBOX_VERSION = 3;
20
+ export const ASK_INBOX_VERSION = 4;
18
21
  /** Gateway ids are lowercase UUIDs (contract: subject keys must be lowercase UUIDs). */
19
22
  export const LOWER_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
20
23
  /** sha256 of a client session id, as the Gateway stores external_session_id_hash. */
@@ -69,10 +72,10 @@ export function parseAskDelivery(input) {
69
72
  return null;
70
73
  if (typeof createdAt !== "number" || !Number.isSafeInteger(createdAt) || createdAt < 0)
71
74
  return null;
72
- if (subjectType === "acceptance" && !exact)
75
+ if (isRecipientApproval(subjectType) && !exact)
73
76
  return null;
74
77
  if (exact && (record.routing_policy !== "exact" || !isApp(record.target_app) || hashes.length !== 1 || direction !== "ask"
75
- || (subjectType !== "request" && subjectType !== "acceptance")))
78
+ || (subjectType !== "request" && !isRecipientApproval(subjectType))))
76
79
  return null;
77
80
  return { deliveryId, subjectType, subjectKey, direction, workItemId, targetSessionHashes: [...hashes], createdAt,
78
81
  ...(exact ? { routingPolicy: "exact", targetApp: record.target_app } : {}),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mtech-ai-local-runner",
3
- "version": "0.1.104",
3
+ "version": "0.1.105",
4
4
  "private": false,
5
5
  "license": "UNLICENSED",
6
6
  "bin": {