mtech-ai-local-runner 0.1.103 → 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.
package/README.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
## 0.1.104: the token prompt is hidden
|
|
2
|
+
|
|
3
|
+
`setup --claude-token` no longer prints the pasted token. If you used it on 0.1.103, revoke that token and store a
|
|
4
|
+
fresh one. Released in 0.1.104; its Local Fix receipt must be listed on the Gateway before a runner on it prepares a
|
|
5
|
+
Local Fix.
|
|
6
|
+
|
|
1
7
|
## 0.1.103: Claude logs in with the runner's own token
|
|
2
8
|
|
|
3
9
|
Run `npx mtech-ai-local-runner@latest setup --claude-token` once and paste the token from `claude setup-token`. Local
|
|
@@ -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. */
|
package/dist/src/asks/routing.js
CHANGED
|
@@ -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
|
|
332
|
+
if (item.notifiedAt === null && !isRecipientApproval(item.subjectType))
|
|
332
333
|
notify.push(item);
|
|
333
334
|
continue;
|
|
334
335
|
}
|
package/dist/src/asks/store.js
CHANGED
|
@@ -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
|
|
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
|
|
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;
|
package/dist/src/asks/types.js
CHANGED
|
@@ -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
|
|
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 =
|
|
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
|
|
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
|
|
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 } : {}),
|
|
@@ -1,29 +1,33 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
|
-
import readline from "node:readline
|
|
2
|
+
import readline from "node:readline";
|
|
3
3
|
import { ClaudeRunnerTokenStore, normalizeClaudeRunnerToken } from "../safety/claude-runner-token.js";
|
|
4
4
|
export const CLAUDE_TOKEN_FLAG = "--claude-token";
|
|
5
5
|
export const CLAUDE_TOKEN_RESET_FLAG = "--claude-token-reset";
|
|
6
|
+
/** Reads pasted lines without ever writing them back: readline gets no output stream and runs as a non-terminal, and
|
|
7
|
+
* terminal echo is off while it reads. Lines are joined, so a wrapped paste is fine; an empty line ends the input. */
|
|
8
|
+
export async function readHiddenLines(input, output, prompt, setEcho = () => undefined) {
|
|
9
|
+
output.write(prompt);
|
|
10
|
+
setEcho(false);
|
|
11
|
+
const rl = readline.createInterface({ input, terminal: false });
|
|
12
|
+
try {
|
|
13
|
+
let value = "";
|
|
14
|
+
for await (const raw of rl) {
|
|
15
|
+
const line = raw.replace(/\s+/g, "");
|
|
16
|
+
if (line === "" && value !== "")
|
|
17
|
+
break;
|
|
18
|
+
value += line;
|
|
19
|
+
}
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
finally {
|
|
23
|
+
rl.close();
|
|
24
|
+
setEcho(true);
|
|
25
|
+
output.write("\n");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
6
28
|
export const terminalHiddenReader = {
|
|
7
29
|
isInteractive: () => process.stdin.isTTY === true,
|
|
8
|
-
|
|
9
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
10
|
-
process.stdout.write(prompt);
|
|
11
|
-
execFileSync("/bin/stty", ["-echo"], { stdio: "inherit" });
|
|
12
|
-
try {
|
|
13
|
-
let value = "";
|
|
14
|
-
for (;;) {
|
|
15
|
-
const line = (await rl.question("")).replace(/\s+/g, "");
|
|
16
|
-
if (line === "" && value !== "")
|
|
17
|
-
return value;
|
|
18
|
-
value += line;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
finally {
|
|
22
|
-
execFileSync("/bin/stty", ["echo"], { stdio: "inherit" });
|
|
23
|
-
process.stdout.write("\n");
|
|
24
|
-
rl.close();
|
|
25
|
-
}
|
|
26
|
-
},
|
|
30
|
+
readHiddenLines: prompt => readHiddenLines(process.stdin, process.stdout, prompt, on => execFileSync("/bin/stty", [on ? "echo" : "-echo"], { stdio: "inherit" })),
|
|
27
31
|
};
|
|
28
32
|
export async function runClaudeTokenCommand(input) {
|
|
29
33
|
const store = input.store ?? new ClaudeRunnerTokenStore({ stateRoot: input.stateRoot });
|