@integrity-labs/agt-cli 0.28.848 → 0.28.849
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/dist/bin/agt.js +5 -5
- package/dist/{chunk-N4TMATK2.js → chunk-JFCISESR.js} +4 -4
- package/dist/{chunk-MFU2FD4N.js → chunk-WUXQVP7A.js} +17 -5
- package/dist/{chunk-MFU2FD4N.js.map → chunk-WUXQVP7A.js.map} +1 -1
- package/dist/{chunk-PMQNTU7W.js → chunk-XCAI3GSF.js} +2 -2
- package/dist/{claude-pair-runtime-BQ5JUW5F.js → claude-pair-runtime-CJXQTVSC.js} +2 -2
- package/dist/lib/manager-worker.js +13 -13
- package/dist/mcp/computer-use-proxy.js +317 -4
- package/dist/mcp/direct-chat-channel.js +16 -4
- package/dist/mcp/index.js +16 -4
- package/dist/mcp/origami.js +16 -4
- package/dist/mcp/slack-channel.js +16 -4
- package/dist/mcp/telegram-channel.js +16 -4
- package/dist/{persistent-session-JKES3FMW.js → persistent-session-YGK7YHPR.js} +3 -3
- package/dist/{responsiveness-probe-P7EAWN6J.js → responsiveness-probe-NGWO3JK3.js} +3 -3
- package/dist/{session-auth-dead-IFU2XLHO.js → session-auth-dead-4UZR5655.js} +2 -2
- package/package.json +1 -1
- /package/dist/{chunk-N4TMATK2.js.map → chunk-JFCISESR.js.map} +0 -0
- /package/dist/{chunk-PMQNTU7W.js.map → chunk-XCAI3GSF.js.map} +0 -0
- /package/dist/{claude-pair-runtime-BQ5JUW5F.js.map → claude-pair-runtime-CJXQTVSC.js.map} +0 -0
- /package/dist/{persistent-session-JKES3FMW.js.map → persistent-session-YGK7YHPR.js.map} +0 -0
- /package/dist/{responsiveness-probe-P7EAWN6J.js.map → responsiveness-probe-NGWO3JK3.js.map} +0 -0
- /package/dist/{session-auth-dead-IFU2XLHO.js.map → session-auth-dead-4UZR5655.js.map} +0 -0
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
rotateDailySession,
|
|
19
19
|
sessionFileExists,
|
|
20
20
|
todayLocalIso
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-WUXQVP7A.js";
|
|
22
22
|
import {
|
|
23
23
|
reapOrphanChannelMcps
|
|
24
24
|
} from "./chunk-XWVM4KPK.js";
|
|
@@ -5431,4 +5431,4 @@ export {
|
|
|
5431
5431
|
stopAllSessionsAndWait,
|
|
5432
5432
|
getProjectDir
|
|
5433
5433
|
};
|
|
5434
|
-
//# sourceMappingURL=chunk-
|
|
5434
|
+
//# sourceMappingURL=chunk-XCAI3GSF.js.map
|
|
@@ -100,7 +100,7 @@ async function spawnPairSession(session) {
|
|
|
100
100
|
return { ok: true };
|
|
101
101
|
} catch {
|
|
102
102
|
}
|
|
103
|
-
const { resolveClaudeBinary } = await import("./persistent-session-
|
|
103
|
+
const { resolveClaudeBinary } = await import("./persistent-session-YGK7YHPR.js");
|
|
104
104
|
const claudeBin = resolveClaudeBinary();
|
|
105
105
|
const pairEnv = {
|
|
106
106
|
...process.env,
|
|
@@ -373,4 +373,4 @@ export {
|
|
|
373
373
|
startClaudePair,
|
|
374
374
|
submitClaudePairCode
|
|
375
375
|
};
|
|
376
|
-
//# sourceMappingURL=claude-pair-runtime-
|
|
376
|
+
//# sourceMappingURL=claude-pair-runtime-CJXQTVSC.js.map
|
|
@@ -60,7 +60,7 @@ import {
|
|
|
60
60
|
safeWriteJsonAtomic,
|
|
61
61
|
setConfigHash,
|
|
62
62
|
tripClass
|
|
63
|
-
} from "../chunk-
|
|
63
|
+
} from "../chunk-JFCISESR.js";
|
|
64
64
|
import {
|
|
65
65
|
getProjectDir as getProjectDir2,
|
|
66
66
|
getReadyTasks,
|
|
@@ -126,7 +126,7 @@ import {
|
|
|
126
126
|
takeZombieDetection,
|
|
127
127
|
toOpencodeModel,
|
|
128
128
|
writeEgressAllowlist
|
|
129
|
-
} from "../chunk-
|
|
129
|
+
} from "../chunk-XCAI3GSF.js";
|
|
130
130
|
import {
|
|
131
131
|
ACCOUNT_ENFORCEMENT_MARKER_FILENAME,
|
|
132
132
|
AnchorSessionClient,
|
|
@@ -221,7 +221,7 @@ import {
|
|
|
221
221
|
subagentActivityAgeSeconds,
|
|
222
222
|
sumTranscriptUsageInWindow,
|
|
223
223
|
transcriptActivityAgeSeconds
|
|
224
|
-
} from "../chunk-
|
|
224
|
+
} from "../chunk-WUXQVP7A.js";
|
|
225
225
|
import {
|
|
226
226
|
reapOrphanChannelMcps
|
|
227
227
|
} from "../chunk-XWVM4KPK.js";
|
|
@@ -12996,7 +12996,7 @@ var pendingDayRolloverReset = /* @__PURE__ */ new Set();
|
|
|
12996
12996
|
var dayRolloverInboundHold = /* @__PURE__ */ new Map();
|
|
12997
12997
|
var INBOUND_HOLD_EPISODE_GAP_MS = 12e4;
|
|
12998
12998
|
async function channelInboundActivityAgeSecondsFor(codeName) {
|
|
12999
|
-
const { newestPendingInboundActivityMtimeMs } = await import("../responsiveness-probe-
|
|
12999
|
+
const { newestPendingInboundActivityMtimeMs } = await import("../responsiveness-probe-NGWO3JK3.js");
|
|
13000
13000
|
const newest = newestPendingInboundActivityMtimeMs(dirname11(paneLogPath(codeName)));
|
|
13001
13001
|
if (newest === null) return null;
|
|
13002
13002
|
return Math.max(0, Math.floor((Date.now() - newest) / 1e3));
|
|
@@ -13664,7 +13664,7 @@ var agentRestartTimezoneInputs = /* @__PURE__ */ new Map();
|
|
|
13664
13664
|
var lastVersionCheckAt = 0;
|
|
13665
13665
|
var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
|
|
13666
13666
|
var lastResponsivenessProbeAt = 0;
|
|
13667
|
-
var agtCliVersion = true ? "0.28.
|
|
13667
|
+
var agtCliVersion = true ? "0.28.849" : "dev";
|
|
13668
13668
|
function resolveBrewPath(execFileSync2) {
|
|
13669
13669
|
try {
|
|
13670
13670
|
const out = execFileSync2("which", ["brew"], { timeout: 5e3 }).toString().trim();
|
|
@@ -15254,7 +15254,7 @@ function flushRestartedAgentDiagnostics(hostId, codeNames) {
|
|
|
15254
15254
|
if (codeNames.length === 0) return;
|
|
15255
15255
|
void (async () => {
|
|
15256
15256
|
try {
|
|
15257
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
15257
|
+
const { collectDiagnostics } = await import("../persistent-session-YGK7YHPR.js");
|
|
15258
15258
|
await api.post("/host/heartbeat", {
|
|
15259
15259
|
host_id: hostId,
|
|
15260
15260
|
agent_diagnostics: collectDiagnostics(codeNames, quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor)
|
|
@@ -15396,7 +15396,7 @@ async function pollCycleInner() {
|
|
|
15396
15396
|
}
|
|
15397
15397
|
try {
|
|
15398
15398
|
const { detectHostSecurity } = await import("../host-security-6PDFG7F5.js");
|
|
15399
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
15399
|
+
const { collectDiagnostics } = await import("../persistent-session-YGK7YHPR.js");
|
|
15400
15400
|
const diagCodeNames = [...agentState.persistentSessionAgents];
|
|
15401
15401
|
const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames, quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor) : void 0;
|
|
15402
15402
|
let tailscaleHostname;
|
|
@@ -15565,7 +15565,7 @@ async function pollCycleInner() {
|
|
|
15565
15565
|
collectPanelessActivityProbes,
|
|
15566
15566
|
getResponsivenessIntervalMs,
|
|
15567
15567
|
occupancyQualificationClassifications
|
|
15568
|
-
} = await import("../responsiveness-probe-
|
|
15568
|
+
} = await import("../responsiveness-probe-NGWO3JK3.js");
|
|
15569
15569
|
const probeIntervalMs = getResponsivenessIntervalMs();
|
|
15570
15570
|
if (now - lastResponsivenessProbeAt > probeIntervalMs) {
|
|
15571
15571
|
const probeCodeNames = [...agentState.persistentSessionAgents];
|
|
@@ -15682,7 +15682,7 @@ async function pollCycleInner() {
|
|
|
15682
15682
|
collectResponsivenessProbes,
|
|
15683
15683
|
livePendingInboundOldestAgeSeconds,
|
|
15684
15684
|
parkPendingInbound
|
|
15685
|
-
} = await import("../responsiveness-probe-
|
|
15685
|
+
} = await import("../responsiveness-probe-NGWO3JK3.js");
|
|
15686
15686
|
const { getProjectDir: wedgeProjectDir } = await import("../scheduler-engine-NDP36U7O.js");
|
|
15687
15687
|
const wedgeNow = /* @__PURE__ */ new Date();
|
|
15688
15688
|
const liveAgents = agentState.persistentSessionAgents;
|
|
@@ -15838,7 +15838,7 @@ async function pollCycleInner() {
|
|
|
15838
15838
|
}
|
|
15839
15839
|
try {
|
|
15840
15840
|
const { scrapeMcpFailedBannerCount } = await import("../pane-mcp-banner-scraper-JA437JIB.js");
|
|
15841
|
-
const { probeSessionAuth } = await import("../session-auth-dead-
|
|
15841
|
+
const { probeSessionAuth } = await import("../session-auth-dead-4UZR5655.js");
|
|
15842
15842
|
const observations = [];
|
|
15843
15843
|
const pendingCacheCommits = [];
|
|
15844
15844
|
const modelApiErrorReportingOn = hostFlagStore().getBoolean("model-api-error-reporting");
|
|
@@ -20071,7 +20071,7 @@ async function handleRestartDoorbell(agentId, requestedAt, restartReason) {
|
|
|
20071
20071
|
void api.post("/host/restart-ack", { host_id: hostId, agent_id: agentId, restart_requested_at: requestedAt }).catch((err) => log(`[restart-lane] ack failed for '${codeName}': ${err.message}`));
|
|
20072
20072
|
void (async () => {
|
|
20073
20073
|
try {
|
|
20074
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
20074
|
+
const { collectDiagnostics } = await import("../persistent-session-YGK7YHPR.js");
|
|
20075
20075
|
await api.post("/host/heartbeat", {
|
|
20076
20076
|
host_id: hostId,
|
|
20077
20077
|
agent_diagnostics: collectDiagnostics([codeName], quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor)
|
|
@@ -20117,7 +20117,7 @@ async function respawnAgentAfterMcpStop(codeName, reason) {
|
|
|
20117
20117
|
}
|
|
20118
20118
|
try {
|
|
20119
20119
|
const hostId = await getHostId();
|
|
20120
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
20120
|
+
const { collectDiagnostics } = await import("../persistent-session-YGK7YHPR.js");
|
|
20121
20121
|
await api.post("/host/heartbeat", {
|
|
20122
20122
|
host_id: hostId,
|
|
20123
20123
|
agent_diagnostics: collectDiagnostics([codeName], quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor)
|
|
@@ -20753,7 +20753,7 @@ async function processClaudePairSessions(agents) {
|
|
|
20753
20753
|
killPairSession,
|
|
20754
20754
|
pairTmuxSession,
|
|
20755
20755
|
finalizeClaudePairOnboarding
|
|
20756
|
-
} = await import("../claude-pair-runtime-
|
|
20756
|
+
} = await import("../claude-pair-runtime-CJXQTVSC.js");
|
|
20757
20757
|
for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
|
|
20758
20758
|
log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
|
|
20759
20759
|
const killed = await killPairSession(pairTmuxSession(pairId));
|
|
@@ -7,6 +7,303 @@ import { createInterface } from "readline";
|
|
|
7
7
|
import { spawn } from "child_process";
|
|
8
8
|
import { fileURLToPath } from "url";
|
|
9
9
|
import { resolve as resolvePath } from "path";
|
|
10
|
+
|
|
11
|
+
// src/computer-use-gate.ts
|
|
12
|
+
import { createHash } from "crypto";
|
|
13
|
+
var MINIMUM_TOOL_FLOOR = "write_high_risk";
|
|
14
|
+
var TOOL_FLOORS = Object.freeze({
|
|
15
|
+
// Reads - of the operator's screen and window state.
|
|
16
|
+
get_app_state: "write_high_risk",
|
|
17
|
+
// State-changing: focus, input, navigation.
|
|
18
|
+
open_app: "write_high_risk",
|
|
19
|
+
scroll: "write_high_risk",
|
|
20
|
+
click: "write_destructive",
|
|
21
|
+
type_text: "write_destructive",
|
|
22
|
+
press_key: "write_destructive",
|
|
23
|
+
set_value: "write_destructive",
|
|
24
|
+
perform_secondary_action: "write_destructive"
|
|
25
|
+
});
|
|
26
|
+
var UNGATED_TOOLS = Object.freeze(["list_apps"]);
|
|
27
|
+
var APP_ARG_KEYS = ["app_name", "app", "application", "bundle_id"];
|
|
28
|
+
var TEXT_PREVIEW_LIMIT = 200;
|
|
29
|
+
var SESSION_PATH = "/host/computer-use/session";
|
|
30
|
+
var ACTION_PATH = "/host/computer-use/action";
|
|
31
|
+
function refuse(text) {
|
|
32
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
33
|
+
}
|
|
34
|
+
function previewText(value, limit = TEXT_PREVIEW_LIMIT) {
|
|
35
|
+
if (typeof value !== "string" || value.length === 0) return void 0;
|
|
36
|
+
if (value.length <= limit) return value;
|
|
37
|
+
return `${value.slice(0, limit)}... (${value.length - limit} more characters)`;
|
|
38
|
+
}
|
|
39
|
+
function extractAppIdentifiers(args) {
|
|
40
|
+
if (typeof args !== "object" || args === null) return [];
|
|
41
|
+
const rec = args;
|
|
42
|
+
const found = [];
|
|
43
|
+
for (const key of APP_ARG_KEYS) {
|
|
44
|
+
const v = rec[key];
|
|
45
|
+
if (typeof v !== "string" || !v.trim()) continue;
|
|
46
|
+
const named = v.trim();
|
|
47
|
+
if (!found.some((f) => sameApp(f, named))) found.push(named);
|
|
48
|
+
}
|
|
49
|
+
return found;
|
|
50
|
+
}
|
|
51
|
+
function sameApp(a, b) {
|
|
52
|
+
if (!a || !b) return false;
|
|
53
|
+
return a.trim().toLowerCase() === b.trim().toLowerCase();
|
|
54
|
+
}
|
|
55
|
+
function actionIdempotencyKey(sessionRequestId, tool, args) {
|
|
56
|
+
const canonical = `${sessionRequestId} ${tool} ${stableStringify(args)}`;
|
|
57
|
+
const digest = createHash("sha256").update(canonical).digest("hex").slice(0, 32);
|
|
58
|
+
return `cu-${sessionRequestId}-${tool}-${digest}`;
|
|
59
|
+
}
|
|
60
|
+
function stableStringify(value) {
|
|
61
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "null";
|
|
62
|
+
if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
|
|
63
|
+
const rec = value;
|
|
64
|
+
const keys = Object.keys(rec).sort();
|
|
65
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${stableStringify(rec[k])}`).join(",")}}`;
|
|
66
|
+
}
|
|
67
|
+
function createApprovalGate(deps) {
|
|
68
|
+
const logErr2 = deps.logErr ?? ((msg) => process.stderr.write(`[computer-use-gate] ${msg}
|
|
69
|
+
`));
|
|
70
|
+
return async (toolName, args, ctx) => {
|
|
71
|
+
if (UNGATED_TOOLS.includes(toolName)) return null;
|
|
72
|
+
const floor = TOOL_FLOORS[toolName];
|
|
73
|
+
if (!floor) {
|
|
74
|
+
return refuse(
|
|
75
|
+
`computer-use: '${toolName || "(unnamed)"}' has no declared approval floor, so it is refused. Every tool this proxy forwards must be classified at '${MINIMUM_TOOL_FLOOR}' or above (packages/mcp/src/computer-use-gate.ts TOOL_FLOORS). Tell your operator which tool you needed.`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
const appIds = extractAppIdentifiers(args);
|
|
79
|
+
if (appIds.length > 1) {
|
|
80
|
+
return refuse(
|
|
81
|
+
`computer-use: this call names more than one application (${appIds.map((a) => `'${a}'`).join(", ")}), so it was NOT sent to the Mac. A grant covers ONE application, and when the arguments disagree this proxy cannot tell which one the Mac would actually act on - so it refuses rather than guessing. Re-send the call naming the target exactly once.`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
const requestedApp = appIds.length === 1 ? appIds[0] : void 0;
|
|
85
|
+
let grant;
|
|
86
|
+
try {
|
|
87
|
+
grant = await deps.apiGet(
|
|
88
|
+
`${SESSION_PATH}?agent_id=${encodeURIComponent(deps.agentId)}`
|
|
89
|
+
);
|
|
90
|
+
} catch (err) {
|
|
91
|
+
logErr2(`session lookup failed: ${err.message}`);
|
|
92
|
+
return refuse(
|
|
93
|
+
`computer-use: cannot reach the control plane to check your session, so this call was NOT sent to the Mac. This is not a denial and not a Mac problem - approval could not be checked, so nothing ran. Retry shortly.`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
async function requestSession(lead) {
|
|
97
|
+
if (!requestedApp) {
|
|
98
|
+
return refuse(
|
|
99
|
+
`${lead} This call also does not name an application, so a session cannot be requested for it. Call list_apps first, then retry naming the app.`
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
const filed = await deps.apiPost(SESSION_PATH, {
|
|
104
|
+
agent_id: deps.agentId,
|
|
105
|
+
target_app: requestedApp,
|
|
106
|
+
summary: `Drive ${requestedApp} on the operator's Mac (first call: ${toolName})`,
|
|
107
|
+
idempotency_key: `cu-session-${deps.agentId}-${requestedApp.toLowerCase()}`
|
|
108
|
+
});
|
|
109
|
+
return refuse(
|
|
110
|
+
`${lead} A session request for '${requestedApp}' has been filed` + (filed?.request_id ? ` (request ${filed.request_id})` : "") + `. A human will approve or deny it and choose whether you are prompted for each action. Poll with check_approval; once it is active, re-issue this call.`
|
|
111
|
+
);
|
|
112
|
+
} catch (err) {
|
|
113
|
+
logErr2(`session request failed: ${err.message}`);
|
|
114
|
+
return refuse(
|
|
115
|
+
`${lead} The request for a session could not be filed (${err.message}), so nothing is pending either. Tell your operator.`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
switch (grant.state) {
|
|
120
|
+
case "active":
|
|
121
|
+
break;
|
|
122
|
+
case "pending":
|
|
123
|
+
return refuse(
|
|
124
|
+
`computer-use: your session request is still awaiting a human decision` + (grant.request_id ? ` (request ${grant.request_id})` : "") + `. Nothing has been sent to the Mac. Do not re-request - poll with check_approval, or do something else and come back.`
|
|
125
|
+
);
|
|
126
|
+
case "revoked":
|
|
127
|
+
return refuse(
|
|
128
|
+
`computer-use: your session was REVOKED by a human before it expired. Do not request another for the same task without asking them first - a revocation mid-session usually means they wanted you to stop, not to retry.`
|
|
129
|
+
);
|
|
130
|
+
case "denied":
|
|
131
|
+
return refuse(
|
|
132
|
+
`computer-use: a human DENIED your session request` + (grant.denial_reason ? `: ${grant.denial_reason}` : ".") + ` Do not retry the same request. Ask them what they would approve instead.`
|
|
133
|
+
);
|
|
134
|
+
case "expired":
|
|
135
|
+
return requestSession(
|
|
136
|
+
`computer-use: your session has EXPIRED - a human did grant it, and the window simply ran out. Nothing was sent to the Mac.`
|
|
137
|
+
);
|
|
138
|
+
case "none":
|
|
139
|
+
default:
|
|
140
|
+
return requestSession(
|
|
141
|
+
`computer-use: you have no active session, so nothing was sent to the Mac.`
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
if (requestedApp !== void 0 && !sameApp(grant.target_app, requestedApp)) {
|
|
145
|
+
return refuse(
|
|
146
|
+
`computer-use: your session is bound to '${grant.target_app ?? "(unknown)"}' and this call targets '${requestedApp ?? "(no app named)"}'. Nothing was sent to the Mac. A grant covers ONE application - if you genuinely need the other one, request a separate session for it and say why.`
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
const sessionRequestId = grant.request_id ?? "";
|
|
150
|
+
try {
|
|
151
|
+
const filed = await deps.apiPost(ACTION_PATH, {
|
|
152
|
+
agent_id: deps.agentId,
|
|
153
|
+
session_request_id: sessionRequestId,
|
|
154
|
+
tool: toolName,
|
|
155
|
+
target_app: grant.target_app,
|
|
156
|
+
target_element: previewText(readElement(args), 80),
|
|
157
|
+
text_preview: previewText(readTypedText(args)),
|
|
158
|
+
returns_screenshot: ctx?.includeScreenshot === true,
|
|
159
|
+
floor,
|
|
160
|
+
idempotency_key: actionIdempotencyKey(sessionRequestId, toolName, args),
|
|
161
|
+
summary: describeAction(toolName, grant.target_app, args)
|
|
162
|
+
});
|
|
163
|
+
switch (filed.status) {
|
|
164
|
+
case "auto_approve":
|
|
165
|
+
case "active":
|
|
166
|
+
return null;
|
|
167
|
+
// forward to the Mac
|
|
168
|
+
case "pending":
|
|
169
|
+
return refuse(
|
|
170
|
+
`computer-use: this action needs a human tap first (request ${filed.request_id}). Nothing was sent to the Mac. Poll with check_approval and re-issue this EXACT call once it is approved - re-issuing it re-attaches to the same request rather than filing another.`
|
|
171
|
+
);
|
|
172
|
+
case "denied":
|
|
173
|
+
case "hard_deny":
|
|
174
|
+
return refuse(
|
|
175
|
+
`computer-use: a human DENIED this action` + (filed.denial_reason ? `: ${filed.denial_reason}` : ".") + ` Nothing was sent to the Mac. Do not retry it - pick a different approach or ask them what to do.`
|
|
176
|
+
);
|
|
177
|
+
case "expired":
|
|
178
|
+
return refuse(
|
|
179
|
+
`computer-use: the approval for this action expired before anyone answered (request ${filed.request_id}). Nothing was sent to the Mac. This is not a refusal - re-issue the call to ask again.`
|
|
180
|
+
);
|
|
181
|
+
case "revoked":
|
|
182
|
+
return refuse(
|
|
183
|
+
`computer-use: your session was revoked while this action was awaiting approval. Nothing was sent to the Mac.`
|
|
184
|
+
);
|
|
185
|
+
case "consumed":
|
|
186
|
+
return refuse(
|
|
187
|
+
`computer-use: this exact action was already approved and already performed once, so it was NOT sent again. An approval covers ONE execution - if you genuinely need to repeat it, that is a new request. (If you are retrying because you could not tell whether the first one landed: read the accessibility tree, isError:false is not evidence either way.)`
|
|
188
|
+
);
|
|
189
|
+
default:
|
|
190
|
+
return refuse(
|
|
191
|
+
`computer-use: the control plane returned an approval status this proxy does not recognise ('${String(filed.status)}'), so the call was NOT sent to the Mac. Tell your operator.`
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
} catch (err) {
|
|
195
|
+
logErr2(`action filing failed: ${err.message}`);
|
|
196
|
+
return refuse(
|
|
197
|
+
`computer-use: the approval for this action could not be filed (${err.message}), so it was NOT sent to the Mac. This is not a denial. Retry shortly; if it persists, tell your operator.`
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function readElement(args) {
|
|
203
|
+
if (typeof args !== "object" || args === null) return void 0;
|
|
204
|
+
const rec = args;
|
|
205
|
+
for (const key of ["element_index", "element", "role", "title"]) {
|
|
206
|
+
const v = rec[key];
|
|
207
|
+
if (typeof v === "string" && v.trim()) return v;
|
|
208
|
+
if (typeof v === "number") return String(v);
|
|
209
|
+
}
|
|
210
|
+
return void 0;
|
|
211
|
+
}
|
|
212
|
+
function readTypedText(args) {
|
|
213
|
+
if (typeof args !== "object" || args === null) return void 0;
|
|
214
|
+
const rec = args;
|
|
215
|
+
for (const key of ["text", "value", "keys", "key"]) {
|
|
216
|
+
const v = rec[key];
|
|
217
|
+
if (typeof v === "string" && v.length > 0) return v;
|
|
218
|
+
}
|
|
219
|
+
return void 0;
|
|
220
|
+
}
|
|
221
|
+
function describeAction(tool, app, args) {
|
|
222
|
+
const where = app ? ` in ${app}` : "";
|
|
223
|
+
const el = readElement(args);
|
|
224
|
+
const elPart = el !== void 0 ? ` on element ${String(el)}` : "";
|
|
225
|
+
const text = previewText(readTypedText(args), 80);
|
|
226
|
+
const textPart = text ? ` with "${text}"` : "";
|
|
227
|
+
return `${tool}${elPart}${textPart}${where}`;
|
|
228
|
+
}
|
|
229
|
+
function createUnconfiguredGate(missing) {
|
|
230
|
+
return async () => refuse(
|
|
231
|
+
`computer-use: this proxy is not configured to check approvals (missing ${missing.join(", ")}), so it refuses every call rather than forwarding un-approved ones to the Mac. Nothing has been sent. Ask your operator to re-provision this agent.`
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
function resolveGate(env, makeClient) {
|
|
235
|
+
const agtHost = env.AGT_HOST?.trim();
|
|
236
|
+
const agtApiKey = env.AGT_API_KEY?.trim();
|
|
237
|
+
const agentId = env.AGT_AGENT_ID?.trim();
|
|
238
|
+
const missing = [];
|
|
239
|
+
if (!agtHost) missing.push("AGT_HOST");
|
|
240
|
+
if (!agtApiKey) missing.push("AGT_API_KEY");
|
|
241
|
+
if (!agentId) missing.push("AGT_AGENT_ID");
|
|
242
|
+
if (missing.length > 0) return { gate: createUnconfiguredGate(missing), missing };
|
|
243
|
+
const client = makeClient({ agtHost, agtApiKey, agentId });
|
|
244
|
+
return {
|
|
245
|
+
gate: createApprovalGate({ agentId, apiGet: client.apiGet, apiPost: client.apiPost }),
|
|
246
|
+
missing: []
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// src/computer-use-api-client.ts
|
|
251
|
+
var REQUEST_TIMEOUT_MS = 1e4;
|
|
252
|
+
var TOKEN_SKEW_MS = 6e4;
|
|
253
|
+
function createHostApiClient(config) {
|
|
254
|
+
const fetchImpl = config.fetchImpl ?? fetch;
|
|
255
|
+
const now = config.now ?? Date.now;
|
|
256
|
+
const base = config.agtHost.replace(/\/+$/, "");
|
|
257
|
+
let cachedToken = null;
|
|
258
|
+
let cachedTokenExpiresAt = 0;
|
|
259
|
+
async function getToken() {
|
|
260
|
+
if (cachedToken && now() < cachedTokenExpiresAt) return cachedToken;
|
|
261
|
+
const res = await fetchImpl(`${base}/host/exchange`, {
|
|
262
|
+
method: "POST",
|
|
263
|
+
headers: { "Content-Type": "application/json" },
|
|
264
|
+
body: JSON.stringify({ host_key: config.agtApiKey, agent_id: config.agentId }),
|
|
265
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
266
|
+
});
|
|
267
|
+
if (!res.ok) {
|
|
268
|
+
const text = await res.text().catch(() => res.statusText);
|
|
269
|
+
throw new Error(`host key exchange returned ${res.status}: ${text}`);
|
|
270
|
+
}
|
|
271
|
+
const body = await res.json();
|
|
272
|
+
if (!body.token) throw new Error("host key exchange returned no token");
|
|
273
|
+
cachedToken = body.token;
|
|
274
|
+
const lifetimeMs = (body.expires_in ?? 3600) * 1e3;
|
|
275
|
+
cachedTokenExpiresAt = now() + Math.max(0, lifetimeMs - TOKEN_SKEW_MS);
|
|
276
|
+
return cachedToken;
|
|
277
|
+
}
|
|
278
|
+
async function request(path, init, retried = false) {
|
|
279
|
+
const token = await getToken();
|
|
280
|
+
const res = await fetchImpl(`${base}${path}`, {
|
|
281
|
+
...init,
|
|
282
|
+
headers: { ...init.headers ?? {}, Authorization: `Bearer ${token}` },
|
|
283
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
284
|
+
});
|
|
285
|
+
if (res.status === 401 && !retried) {
|
|
286
|
+
cachedToken = null;
|
|
287
|
+
cachedTokenExpiresAt = 0;
|
|
288
|
+
return request(path, init, true);
|
|
289
|
+
}
|
|
290
|
+
if (!res.ok) {
|
|
291
|
+
const text = await res.text().catch(() => res.statusText);
|
|
292
|
+
throw new Error(`API ${path} returned ${res.status}: ${text}`);
|
|
293
|
+
}
|
|
294
|
+
return await res.json();
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
apiGet: (path) => request(path, { method: "GET" }),
|
|
298
|
+
apiPost: (path, body) => request(path, {
|
|
299
|
+
method: "POST",
|
|
300
|
+
headers: { "Content-Type": "application/json" },
|
|
301
|
+
body: JSON.stringify(body)
|
|
302
|
+
})
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// src/computer-use-proxy.ts
|
|
10
307
|
var INCLUDE_SCREENSHOT_PARAM = "include_screenshot";
|
|
11
308
|
var STRIPPED_IMAGE_STUB = `[screenshot omitted to save tokens \u2014 pass ${INCLUDE_SCREENSHOT_PARAM}: true to receive it. Element indices in the accessibility tree above are usually enough to act.]`;
|
|
12
309
|
var RELIABILITY_NOTE = `
|
|
@@ -108,6 +405,7 @@ function logErr(msg) {
|
|
|
108
405
|
var allowAll = async () => null;
|
|
109
406
|
function createProxy(deps) {
|
|
110
407
|
const beforeForward = deps.beforeForward ?? allowAll;
|
|
408
|
+
const allowUnrequestedScreenshot = deps.allowUnrequestedScreenshot !== false;
|
|
111
409
|
const pending = /* @__PURE__ */ new Map();
|
|
112
410
|
async function handleDownstream(line) {
|
|
113
411
|
const text = line.trim();
|
|
@@ -126,7 +424,7 @@ function createProxy(deps) {
|
|
|
126
424
|
const { include, params } = takeIncludeScreenshot(frame.params);
|
|
127
425
|
const toolName = typeof params?.name === "string" ? params.name : "";
|
|
128
426
|
const args = params?.arguments;
|
|
129
|
-
const short = await beforeForward(toolName, args);
|
|
427
|
+
const short = await beforeForward(toolName, args, { includeScreenshot: include });
|
|
130
428
|
if (short) {
|
|
131
429
|
if (!isRequest) {
|
|
132
430
|
logErr(`short-circuited notification tools/call ${toolName} (no reply sent)`);
|
|
@@ -171,8 +469,11 @@ function createProxy(deps) {
|
|
|
171
469
|
(b) => typeof b === "object" && b !== null && b.type === "text"
|
|
172
470
|
);
|
|
173
471
|
if (treeBlock && !treeHasActionableContent(treeBlock.text)) {
|
|
174
|
-
|
|
175
|
-
|
|
472
|
+
if (allowUnrequestedScreenshot) {
|
|
473
|
+
logErr("tree carries no actionable content; keeping screenshot");
|
|
474
|
+
return [text];
|
|
475
|
+
}
|
|
476
|
+
logErr("tree carries no actionable content, but screenshots are gated; stripping");
|
|
176
477
|
}
|
|
177
478
|
}
|
|
178
479
|
const { result, stripped } = stripImages(frame.result);
|
|
@@ -249,11 +550,23 @@ async function main(argv = process.argv) {
|
|
|
249
550
|
logErr(`failed to spawn upstream: ${err.message}`);
|
|
250
551
|
process.exit(1);
|
|
251
552
|
}
|
|
553
|
+
const { gate, missing } = resolveGate(process.env, (c) => createHostApiClient(c));
|
|
554
|
+
if (missing.length > 0) {
|
|
555
|
+
logErr(
|
|
556
|
+
`NOT CONFIGURED for approval checks (missing ${missing.join(", ")}); refusing every call. No computer-use tool will reach the Mac until this agent is re-provisioned.`
|
|
557
|
+
);
|
|
558
|
+
}
|
|
252
559
|
await wirePump({
|
|
253
560
|
child,
|
|
254
561
|
clientIn: process.stdin,
|
|
255
562
|
clientOut: process.stdout,
|
|
256
|
-
proxy: createProxy({
|
|
563
|
+
proxy: createProxy({
|
|
564
|
+
upstream,
|
|
565
|
+
beforeForward: gate,
|
|
566
|
+
// With a gate in place an image must never arrive unrequested - see the
|
|
567
|
+
// field's own note on why the accessibility-blind hatch closes here.
|
|
568
|
+
allowUnrequestedScreenshot: false
|
|
569
|
+
})
|
|
257
570
|
});
|
|
258
571
|
}
|
|
259
572
|
var invokedDirectly = (() => {
|
|
@@ -37310,10 +37310,22 @@ var FLAG_REGISTRY = [
|
|
|
37310
37310
|
// See the description: `shadow` is what prod compiles today, so it is the
|
|
37311
37311
|
// only default that ships this flag as a no-op.
|
|
37312
37312
|
defaultValue: "shadow",
|
|
37313
|
-
// enforce changes the admission rule on the platform's front door
|
|
37314
|
-
//
|
|
37315
|
-
//
|
|
37316
|
-
//
|
|
37313
|
+
// enforce changes the admission rule on the platform's front door. This is
|
|
37314
|
+
// precisely the deliberate, audited flip ADR-0022 §4 reserves confirmation
|
|
37315
|
+
// for, and it stays `sensitive` — but NOT for the reason first written here.
|
|
37316
|
+
// That read "the families it starts refusing include mcp-proxy, one of the
|
|
37317
|
+
// fleet's dominant callers", and ENG-9895 measured otherwise: mcp-proxy
|
|
37318
|
+
// verifies its own token on a route carrying no `authMiddleware`, so it is a
|
|
37319
|
+
// dominant caller of a different door and cannot be in the refused set. The
|
|
37320
|
+
// measured population at that door is ZERO over 298,839,353 prod log records
|
|
37321
|
+
// / 14 days, and the impersonation families are absent too — one
|
|
37322
|
+
// structurally (`X-Agent-Impersonation` header, own verifier), one by
|
|
37323
|
+
// measurement (307 requests through that door, zero lines). What keeps this
|
|
37324
|
+
// `sensitive` is therefore NOT an unresolved residue: it is that `enforce`
|
|
37325
|
+
// changes an admission rule on the front door, and that the predicate is
|
|
37326
|
+
// structural, so a FUTURE family 13 is born refusable without an edit here.
|
|
37327
|
+
// That is exactly the deliberate, audited flip ADR-0022 §4 reserves
|
|
37328
|
+
// confirmation for. docs/research/eng-9895-prod-shadow-measurement.md
|
|
37317
37329
|
sensitive: true
|
|
37318
37330
|
},
|
|
37319
37331
|
{
|
package/dist/mcp/index.js
CHANGED
|
@@ -29580,10 +29580,22 @@ var FLAG_REGISTRY = [
|
|
|
29580
29580
|
// See the description: `shadow` is what prod compiles today, so it is the
|
|
29581
29581
|
// only default that ships this flag as a no-op.
|
|
29582
29582
|
defaultValue: "shadow",
|
|
29583
|
-
// enforce changes the admission rule on the platform's front door
|
|
29584
|
-
//
|
|
29585
|
-
//
|
|
29586
|
-
//
|
|
29583
|
+
// enforce changes the admission rule on the platform's front door. This is
|
|
29584
|
+
// precisely the deliberate, audited flip ADR-0022 §4 reserves confirmation
|
|
29585
|
+
// for, and it stays `sensitive` — but NOT for the reason first written here.
|
|
29586
|
+
// That read "the families it starts refusing include mcp-proxy, one of the
|
|
29587
|
+
// fleet's dominant callers", and ENG-9895 measured otherwise: mcp-proxy
|
|
29588
|
+
// verifies its own token on a route carrying no `authMiddleware`, so it is a
|
|
29589
|
+
// dominant caller of a different door and cannot be in the refused set. The
|
|
29590
|
+
// measured population at that door is ZERO over 298,839,353 prod log records
|
|
29591
|
+
// / 14 days, and the impersonation families are absent too — one
|
|
29592
|
+
// structurally (`X-Agent-Impersonation` header, own verifier), one by
|
|
29593
|
+
// measurement (307 requests through that door, zero lines). What keeps this
|
|
29594
|
+
// `sensitive` is therefore NOT an unresolved residue: it is that `enforce`
|
|
29595
|
+
// changes an admission rule on the front door, and that the predicate is
|
|
29596
|
+
// structural, so a FUTURE family 13 is born refusable without an edit here.
|
|
29597
|
+
// That is exactly the deliberate, audited flip ADR-0022 §4 reserves
|
|
29598
|
+
// confirmation for. docs/research/eng-9895-prod-shadow-measurement.md
|
|
29587
29599
|
sensitive: true
|
|
29588
29600
|
},
|
|
29589
29601
|
{
|
package/dist/mcp/origami.js
CHANGED
|
@@ -43533,10 +43533,22 @@ var FLAG_REGISTRY = [
|
|
|
43533
43533
|
// See the description: `shadow` is what prod compiles today, so it is the
|
|
43534
43534
|
// only default that ships this flag as a no-op.
|
|
43535
43535
|
defaultValue: "shadow",
|
|
43536
|
-
// enforce changes the admission rule on the platform's front door
|
|
43537
|
-
//
|
|
43538
|
-
//
|
|
43539
|
-
//
|
|
43536
|
+
// enforce changes the admission rule on the platform's front door. This is
|
|
43537
|
+
// precisely the deliberate, audited flip ADR-0022 §4 reserves confirmation
|
|
43538
|
+
// for, and it stays `sensitive` — but NOT for the reason first written here.
|
|
43539
|
+
// That read "the families it starts refusing include mcp-proxy, one of the
|
|
43540
|
+
// fleet's dominant callers", and ENG-9895 measured otherwise: mcp-proxy
|
|
43541
|
+
// verifies its own token on a route carrying no `authMiddleware`, so it is a
|
|
43542
|
+
// dominant caller of a different door and cannot be in the refused set. The
|
|
43543
|
+
// measured population at that door is ZERO over 298,839,353 prod log records
|
|
43544
|
+
// / 14 days, and the impersonation families are absent too — one
|
|
43545
|
+
// structurally (`X-Agent-Impersonation` header, own verifier), one by
|
|
43546
|
+
// measurement (307 requests through that door, zero lines). What keeps this
|
|
43547
|
+
// `sensitive` is therefore NOT an unresolved residue: it is that `enforce`
|
|
43548
|
+
// changes an admission rule on the front door, and that the predicate is
|
|
43549
|
+
// structural, so a FUTURE family 13 is born refusable without an edit here.
|
|
43550
|
+
// That is exactly the deliberate, audited flip ADR-0022 §4 reserves
|
|
43551
|
+
// confirmation for. docs/research/eng-9895-prod-shadow-measurement.md
|
|
43540
43552
|
sensitive: true
|
|
43541
43553
|
},
|
|
43542
43554
|
{
|
|
@@ -38088,10 +38088,22 @@ var FLAG_REGISTRY = [
|
|
|
38088
38088
|
// See the description: `shadow` is what prod compiles today, so it is the
|
|
38089
38089
|
// only default that ships this flag as a no-op.
|
|
38090
38090
|
defaultValue: "shadow",
|
|
38091
|
-
// enforce changes the admission rule on the platform's front door
|
|
38092
|
-
//
|
|
38093
|
-
//
|
|
38094
|
-
//
|
|
38091
|
+
// enforce changes the admission rule on the platform's front door. This is
|
|
38092
|
+
// precisely the deliberate, audited flip ADR-0022 §4 reserves confirmation
|
|
38093
|
+
// for, and it stays `sensitive` — but NOT for the reason first written here.
|
|
38094
|
+
// That read "the families it starts refusing include mcp-proxy, one of the
|
|
38095
|
+
// fleet's dominant callers", and ENG-9895 measured otherwise: mcp-proxy
|
|
38096
|
+
// verifies its own token on a route carrying no `authMiddleware`, so it is a
|
|
38097
|
+
// dominant caller of a different door and cannot be in the refused set. The
|
|
38098
|
+
// measured population at that door is ZERO over 298,839,353 prod log records
|
|
38099
|
+
// / 14 days, and the impersonation families are absent too — one
|
|
38100
|
+
// structurally (`X-Agent-Impersonation` header, own verifier), one by
|
|
38101
|
+
// measurement (307 requests through that door, zero lines). What keeps this
|
|
38102
|
+
// `sensitive` is therefore NOT an unresolved residue: it is that `enforce`
|
|
38103
|
+
// changes an admission rule on the front door, and that the predicate is
|
|
38104
|
+
// structural, so a FUTURE family 13 is born refusable without an edit here.
|
|
38105
|
+
// That is exactly the deliberate, audited flip ADR-0022 §4 reserves
|
|
38106
|
+
// confirmation for. docs/research/eng-9895-prod-shadow-measurement.md
|
|
38095
38107
|
sensitive: true
|
|
38096
38108
|
},
|
|
38097
38109
|
{
|
|
@@ -38220,10 +38220,22 @@ var FLAG_REGISTRY = [
|
|
|
38220
38220
|
// See the description: `shadow` is what prod compiles today, so it is the
|
|
38221
38221
|
// only default that ships this flag as a no-op.
|
|
38222
38222
|
defaultValue: "shadow",
|
|
38223
|
-
// enforce changes the admission rule on the platform's front door
|
|
38224
|
-
//
|
|
38225
|
-
//
|
|
38226
|
-
//
|
|
38223
|
+
// enforce changes the admission rule on the platform's front door. This is
|
|
38224
|
+
// precisely the deliberate, audited flip ADR-0022 §4 reserves confirmation
|
|
38225
|
+
// for, and it stays `sensitive` — but NOT for the reason first written here.
|
|
38226
|
+
// That read "the families it starts refusing include mcp-proxy, one of the
|
|
38227
|
+
// fleet's dominant callers", and ENG-9895 measured otherwise: mcp-proxy
|
|
38228
|
+
// verifies its own token on a route carrying no `authMiddleware`, so it is a
|
|
38229
|
+
// dominant caller of a different door and cannot be in the refused set. The
|
|
38230
|
+
// measured population at that door is ZERO over 298,839,353 prod log records
|
|
38231
|
+
// / 14 days, and the impersonation families are absent too — one
|
|
38232
|
+
// structurally (`X-Agent-Impersonation` header, own verifier), one by
|
|
38233
|
+
// measurement (307 requests through that door, zero lines). What keeps this
|
|
38234
|
+
// `sensitive` is therefore NOT an unresolved residue: it is that `enforce`
|
|
38235
|
+
// changes an admission rule on the front door, and that the predicate is
|
|
38236
|
+
// structural, so a FUTURE family 13 is born refusable without an edit here.
|
|
38237
|
+
// That is exactly the deliberate, audited flip ADR-0022 §4 reserves
|
|
38238
|
+
// confirmation for. docs/research/eng-9895-prod-shadow-measurement.md
|
|
38227
38239
|
sensitive: true
|
|
38228
38240
|
},
|
|
38229
38241
|
{
|