mtech-ai-local-runner 0.1.114 → 0.1.115
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 +12 -0
- package/dist/src/gateway-client.js +6 -1
- package/dist/src/menubar/panel.js +17 -4
- package/dist/src/menubar/protocol.js +8 -1
- package/native/app/AI Gateway.app/Contents/Info.plist +2 -2
- package/native/app/AI Gateway.app/Contents/MacOS/AIGatewayMenubar +0 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## 0.1.115: close a request from the menu bar window
|
|
2
|
+
|
|
3
|
+
Open a request in the menu bar window and press **Close request…** to close it without answering. A sheet names the
|
|
4
|
+
request and takes an optional reason (up to 300 characters); nothing is sent until you press **Close request**. The
|
|
5
|
+
request leaves your list, its Slack card says who closed it, and the person who sent it sees it as closed. The Gateway
|
|
6
|
+
refuses the close while a local investigation for the request is still queued or waiting, and closing never stops or
|
|
7
|
+
changes an investigation.
|
|
8
|
+
|
|
9
|
+
A strict request, one that only the AI session it was sent to may accept or answer, no longer shows an answer box in
|
|
10
|
+
the window: it says "Answer this in the AI session it was sent to, or close it here." The menu bar app is now version
|
|
11
|
+
1.4.0; `setup` installs it.
|
|
12
|
+
|
|
1
13
|
## 0.1.114: ask your AI from the menu bar window
|
|
2
14
|
|
|
3
15
|
Select a request, decision, review or outcome record in the menu bar window, or a thread in its Rooms tab, and press
|
|
@@ -768,10 +768,11 @@ export class McpGatewayClient {
|
|
|
768
768
|
const item = record(row) ?? {};
|
|
769
769
|
if (typeof item.kind !== 'string' || typeof item.id !== 'string' || item.id === '')
|
|
770
770
|
return [];
|
|
771
|
+
const boundSession = item.answer_needs_bound_session === true;
|
|
771
772
|
return [{ kind: item.kind, id: item.id, workItemId: panelWorkItemId(item.work_item_id), title: text(item.title).slice(0, PANEL_TITLE),
|
|
772
773
|
from: text(item.asked_by) || text(item.asked_by_actor_id), status: text(item.status),
|
|
773
774
|
changedAt: Number.isSafeInteger(item.changed_at) ? item.changed_at : 0,
|
|
774
|
-
answerable: item.kind === 'request' && item.answer_tool === 'work_request_answer' }];
|
|
775
|
+
answerable: item.kind === 'request' && item.answer_tool === 'work_request_answer' && !boundSession, boundSession }];
|
|
775
776
|
}).slice(0, PANEL_ITEMS);
|
|
776
777
|
}
|
|
777
778
|
/** work_request_show for the request panel. */
|
|
@@ -793,6 +794,10 @@ export class McpGatewayClient {
|
|
|
793
794
|
async answerWorkRequest(requestId, body, signal) {
|
|
794
795
|
await this.callTool('work_request_answer', { request_id: requestId, body }, signal);
|
|
795
796
|
}
|
|
797
|
+
/** work_request_close for the request panel, sent only after the person confirmed it in the app (Work Item af41c9bd). */
|
|
798
|
+
async closeWorkRequest(requestId, reason, signal) {
|
|
799
|
+
await this.callTool('work_request_close', reason === '' ? { request_id: requestId } : { request_id: requestId, reason }, signal);
|
|
800
|
+
}
|
|
796
801
|
/** work_room_read activity, share_only (the projection the runner counts), for the menu bar Rooms tab: one page of 50. */
|
|
797
802
|
async readRoomListPage(afterRoomId, signal) {
|
|
798
803
|
const invalid = () => new GatewayToolError('Invalid Room list page', 'work_room_read', 'GATEWAY_INVALID_TOOL_RESULT');
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MAX_ANSWER_BYTES } from "./protocol.js";
|
|
1
|
+
import { MAX_ANSWER_BYTES, MAX_CLOSE_REASON_CHARS } from "./protocol.js";
|
|
2
2
|
import { callRoom, checkRoomRequest } from "./room-panel.js";
|
|
3
3
|
/** Each app message the panel answers is given up after this long. */
|
|
4
4
|
export const PANEL_CALL_MS = 30_000;
|
|
@@ -8,7 +8,7 @@ const FAILED = "The runner could not reach the AI Gateway. Try again.";
|
|
|
8
8
|
const AUDIENCE_CHANGED = "The Room members changed. Check the new list and post again.";
|
|
9
9
|
/**
|
|
10
10
|
* The daemon's side of the menu bar window (Gateway Work Items d2b6d0da and b35d65cb). It is the allowlist for what
|
|
11
|
-
* the app may ask: the request panel lists, shows and
|
|
11
|
+
* the app may ask: the request panel lists, shows, answers and closes Work Requests; the Rooms tab's messages are checked and
|
|
12
12
|
* called in room-panel.ts. Ids and text are checked before any call, one call runs at a time, and nothing is kept
|
|
13
13
|
* after the reply. Logs carry the message type, an error code and counts only, never a name, title or body.
|
|
14
14
|
*/
|
|
@@ -25,7 +25,7 @@ export function createPanelHandler(input) {
|
|
|
25
25
|
case "list":
|
|
26
26
|
return { items: (await input.gateway.readInboxForPanel(signal)).map(item => ({ kind: item.kind, id: item.id,
|
|
27
27
|
work_item_id: item.workItemId, title: item.title, from: item.from, status: item.status, changed_at: item.changedAt,
|
|
28
|
-
answerable: item.answerable })) };
|
|
28
|
+
answerable: item.answerable, closable: item.kind === "request", bound_session: item.boundSession })) };
|
|
29
29
|
case "show": {
|
|
30
30
|
const shown = await input.gateway.showWorkRequest(request.requestId, signal);
|
|
31
31
|
return { request: { id: shown.id, work_item_id: shown.workItemId, title: shown.title, body: shown.body,
|
|
@@ -35,6 +35,9 @@ export function createPanelHandler(input) {
|
|
|
35
35
|
case "answer":
|
|
36
36
|
await input.gateway.answerWorkRequest(request.requestId, request.body.trim(), signal);
|
|
37
37
|
return { answered: true };
|
|
38
|
+
case "close":
|
|
39
|
+
await input.gateway.closeWorkRequest(request.requestId, request.reason.trim(), signal);
|
|
40
|
+
return { closed: true };
|
|
38
41
|
default:
|
|
39
42
|
return callRoom(request, input.gateway, signal);
|
|
40
43
|
}
|
|
@@ -55,6 +58,13 @@ export function createPanelHandler(input) {
|
|
|
55
58
|
}
|
|
56
59
|
catch { /* A refresh that fails never undoes a sent answer. */ }
|
|
57
60
|
}
|
|
61
|
+
if (request.type === "close") {
|
|
62
|
+
log({ event: "menubar_panel_closed" });
|
|
63
|
+
try {
|
|
64
|
+
input.onAnswered();
|
|
65
|
+
}
|
|
66
|
+
catch { /* A refresh that fails never undoes a close. */ }
|
|
67
|
+
}
|
|
58
68
|
if (request.type === "room_presented")
|
|
59
69
|
log({ event: "menubar_panel_presented", count: data.presented });
|
|
60
70
|
if (request.type === "room_mark_read") {
|
|
@@ -87,10 +97,13 @@ export function createPanelHandler(input) {
|
|
|
87
97
|
function check(request) {
|
|
88
98
|
if (request.type === "list")
|
|
89
99
|
return null;
|
|
90
|
-
if (request.type !== "show" && request.type !== "answer")
|
|
100
|
+
if (request.type !== "show" && request.type !== "answer" && request.type !== "close")
|
|
91
101
|
return checkRoomRequest(request);
|
|
92
102
|
if (!ID.test(request.requestId))
|
|
93
103
|
return "That request id is not valid.";
|
|
104
|
+
if (request.type === "close" && [...request.reason.trim()].length > MAX_CLOSE_REASON_CHARS) {
|
|
105
|
+
return `A reason can be at most ${MAX_CLOSE_REASON_CHARS} characters.`;
|
|
106
|
+
}
|
|
94
107
|
if (request.type === "answer") {
|
|
95
108
|
const bytes = Buffer.byteLength(request.body.trim());
|
|
96
109
|
if (bytes === 0)
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
* {"v":1,"type":"list","req":"<uuid>"}
|
|
18
18
|
* {"v":1,"type":"show","req":"<uuid>","request_id":"<uuid>"}
|
|
19
19
|
* {"v":1,"type":"answer","req":"<uuid>","request_id":"<uuid>","body":"…"}
|
|
20
|
+
* {"v":1,"type":"close","req":"<uuid>","request_id":"<uuid>","reason":"…"} sent only after the person confirmed;
|
|
21
|
+
* reason may be empty (Work Item af41c9bd)
|
|
20
22
|
* {"v":1,"type":"reply","req":"<uuid>","ok":true,"data":{…}}
|
|
21
23
|
* {"v":1,"type":"reply","req":"<uuid>","ok":false,"error":"busy|invalid|refused|failed","message":"…"}
|
|
22
24
|
*
|
|
@@ -42,6 +44,8 @@ export const MENUBAR_SOCKET = "menubar.sock";
|
|
|
42
44
|
export const MAX_MENUBAR_LINE_BYTES = 4 * 1024 * 1024;
|
|
43
45
|
/** The Gateway's own limit for one answer, in UTF-8 bytes after trimming. */
|
|
44
46
|
export const MAX_ANSWER_BYTES = 8_000;
|
|
47
|
+
/** The Gateway's own limit for a close reason, in characters after trimming (Work Item af41c9bd). */
|
|
48
|
+
export const MAX_CLOSE_REASON_CHARS = 300;
|
|
45
49
|
/** The Gateway's own limit for one Room message, in UTF-8 bytes after trimming. */
|
|
46
50
|
export const MAX_ROOM_POST_BYTES = 8_000;
|
|
47
51
|
const NOTICE_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
@@ -49,7 +53,7 @@ const APP_VERSION = /^\d+\.\d+\.\d+$/;
|
|
|
49
53
|
/** No legitimate message carries more ids than this; the handler checks the real limits. */
|
|
50
54
|
const MAX_LIST_ITEMS = 1_000;
|
|
51
55
|
export const ASK_AI_SUBJECTS = ["work_request", "work_decision", "work_item_review", "outcome_record"];
|
|
52
|
-
const PANEL_TYPES = new Set(["list", "show", "answer", "room_list", "room_show", "room_thread", "room_presented",
|
|
56
|
+
const PANEL_TYPES = new Set(["list", "show", "answer", "close", "room_list", "room_show", "room_thread", "room_presented",
|
|
53
57
|
"room_mark_read", "room_post"]);
|
|
54
58
|
/** True for every message the panel handler answers with a reply. */
|
|
55
59
|
export function isPanelRequest(message) {
|
|
@@ -100,6 +104,9 @@ export function parseAppLine(line) {
|
|
|
100
104
|
case "answer":
|
|
101
105
|
return keys === 5 && isId(record.req) && typeof record.request_id === "string" && typeof record.body === "string"
|
|
102
106
|
? { type: "answer", req: record.req, requestId: record.request_id, body: record.body } : null;
|
|
107
|
+
case "close":
|
|
108
|
+
return keys === 5 && isId(record.req) && typeof record.request_id === "string" && typeof record.reason === "string"
|
|
109
|
+
? { type: "close", req: record.req, requestId: record.request_id, reason: record.reason } : null;
|
|
103
110
|
case "room_list":
|
|
104
111
|
return keys === 3 && isId(record.req) ? { type: "room_list", req: record.req } : null;
|
|
105
112
|
case "room_show":
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
<key>CFBundleDisplayName</key><string>AI Gateway</string>
|
|
8
8
|
<key>CFBundleExecutable</key><string>AIGatewayMenubar</string>
|
|
9
9
|
<key>CFBundlePackageType</key><string>APPL</string>
|
|
10
|
-
<key>CFBundleShortVersionString</key><string>1.
|
|
11
|
-
<key>CFBundleVersion</key><string>
|
|
10
|
+
<key>CFBundleShortVersionString</key><string>1.4.0</string>
|
|
11
|
+
<key>CFBundleVersion</key><string>5</string>
|
|
12
12
|
<key>LSMinimumSystemVersion</key><string>13.0</string>
|
|
13
13
|
<key>LSUIElement</key><true/>
|
|
14
14
|
</dict>
|
|
Binary file
|