@deeeed/metamask-harness 0.14.7 → 0.15.0
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/CHANGELOG.md +81 -6
- package/adapters/core/cleanup.sh +0 -0
- package/adapters/core/inject.sh +0 -0
- package/adapters/extension/cleanup.mjs +0 -0
- package/adapters/extension/ensure-browser.sh +4 -2
- package/adapters/extension/inject.mjs +0 -0
- package/adapters/extension/launch-browser.cjs +73 -34
- package/adapters/extension/launch.sh +0 -0
- package/adapters/extension/live.sh +21 -10
- package/adapters/extension/readiness.mjs +0 -0
- package/adapters/extension/reattach.sh +3 -3
- package/adapters/extension/refresh-build.sh +0 -0
- package/adapters/extension/seed-fixture.sh +0 -0
- package/adapters/extension/sidepanel-toggle.sh +10 -4
- package/adapters/extension/snapshot-dist.sh +0 -0
- package/adapters/extension/start-watch.sh +2 -2
- package/adapters/extension/stop-viewers.sh +0 -0
- package/adapters/extension/verify.sh +19 -2
- package/adapters/extension/wallet-fixture-state.cjs +14 -1
- package/adapters/manifest.json +19 -3
- package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +0 -0
- package/adapters/mobile/bridge-runtime/setup-wallet.sh +6 -4
- package/adapters/mobile/cleanup.sh +0 -0
- package/adapters/mobile/inject.sh +0 -0
- package/adapters/mobile/launch-metro.cjs +74 -0
- package/adapters/mobile/lib/metro-listener.sh +0 -0
- package/adapters/mobile/lib/tmux-viewer.sh +4 -4
- package/adapters/mobile/open-device.sh +3 -1
- package/adapters/mobile/prewarm-bundle.sh +0 -0
- package/adapters/mobile/start-metro.sh +12 -18
- package/adapters/mobile/stop-metro.sh +1 -0
- package/adapters/mobile/verify.sh +5 -5
- package/adapters/mobile/wait-for-bridge.sh +0 -0
- package/adapters/mobile/yarn-setup.sh +0 -0
- package/adapters/shared/activate-repo-node.sh +0 -0
- package/adapters/shared/activate-repo-ruby.sh +0 -0
- package/adapters/shared/cli-ux.sh +0 -0
- package/adapters/shared/ensure-runner-deps.sh +0 -0
- package/adapters/shared/harness-path.sh +0 -0
- package/adapters/shared/hash-helpers.sh +0 -0
- package/adapters/shared/install-repo-deps.sh +29 -0
- package/adapters/shared/json-field.sh +0 -0
- package/adapters/shared/open-debug.mjs +35 -4
- package/adapters/shared/open-log-window.sh +1 -1
- package/adapters/shared/reap-checkout-metros.sh +0 -0
- package/adapters/shared/resolve-farmslot-ports.mjs +0 -0
- package/adapters/shared/resolve-farmslot-ports.sh +0 -0
- package/adapters/shared/resolve-slot-ports-core.mjs +72 -1
- package/adapters/shared/resolve-slot-ports.mjs +0 -0
- package/adapters/shared/resolve-slot-ports.sh +21 -17
- package/adapters/shared/sync-wallet-fixture.sh +0 -0
- package/adapters/shared/tmux-session.sh +0 -5
- package/adapters/shared/tmux-viewer.sh +7 -2
- package/dist/adapters/core/surface.js +3 -0
- package/dist/adapters/extension/ensure-ready.js +0 -7
- package/dist/adapters/extension/runtime-decision.js +93 -3
- package/dist/adapters/extension/surface.js +17 -6
- package/dist/adapters/mobile/metro-env.js +67 -0
- package/dist/adapters/mobile/perps-env.js +101 -0
- package/dist/adapters/mobile/prepare.js +32 -14
- package/dist/adapters/mobile/runtime-decision.js +21 -2
- package/dist/adapters/mobile/source-freshness.js +116 -0
- package/dist/adapters/mobile/surface.js +3 -0
- package/dist/adapters/resolve-slot-ports.js +4 -0
- package/dist/adapters/slot-ports.js +131 -49
- package/dist/adapters.js +7 -2
- package/dist/checkout-lock.js +72 -0
- package/dist/cli.js +2 -0
- package/dist/commands/call.js +91 -54
- package/dist/commands/check.js +266 -46
- package/dist/commands/checklist.js +136 -0
- package/dist/commands/debug.js +21 -2
- package/dist/commands/doctor.js +187 -12
- package/dist/commands/fixtures.js +177 -42
- package/dist/commands/launch/extension.js +6 -1
- package/dist/commands/launch/index.js +54 -22
- package/dist/commands/logs.js +24 -4
- package/dist/commands/run-engine.js +223 -7
- package/dist/commands/run.js +67 -50
- package/dist/commands/shared.js +86 -5
- package/dist/commands/stop.js +1 -2
- package/dist/doctor.js +39 -17
- package/dist/harness.js +5 -2
- package/dist/heal-bounds.js +1 -1
- package/dist/mm-harness-cli.js +37 -9
- package/dist/runner.js +35 -5
- package/dist/runtime-context.js +228 -0
- package/docs/CLI-SPEC.md +15 -11
- package/docs/MENTAL-MODEL.md +6 -6
- package/library/actions/extension/perps/perps.mjs +29 -8
- package/library/actions/extension/platform/cdp.mjs +128 -28
- package/library/actions/extension/ui/navigate.mjs +1 -1
- package/library/actions/mobile/perps/perps.mjs +13 -1
- package/library/actions/mobile/platform/bridge.mjs +302 -29
- package/library/actions/mobile/wallet/ensure_unlocked.mjs +7 -2
- package/library/manifests/extension.action-manifest.json +32 -12
- package/library/manifests/mobile.action-manifest.json +25 -3
- package/package.json +5 -5
- package/scripts/completions.sh +7 -4
- package/scripts/install-completions.sh +0 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { spawn } = require('node:child_process');
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
|
|
8
|
+
function parseArgs(argv) {
|
|
9
|
+
const values = {};
|
|
10
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
11
|
+
const arg = argv[index];
|
|
12
|
+
if (arg === '-h' || arg === '--help') {
|
|
13
|
+
values.help = true;
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
if (arg === '--clear') {
|
|
17
|
+
values.clear = true;
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (!arg.startsWith('--') || index + 1 >= argv.length) throw new Error(`invalid argument: ${arg}`);
|
|
21
|
+
values[arg.slice(2)] = argv[++index];
|
|
22
|
+
}
|
|
23
|
+
return values;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function main() {
|
|
27
|
+
const args = parseArgs(process.argv.slice(2));
|
|
28
|
+
if (args.help) {
|
|
29
|
+
console.log('Usage: launch-metro.cjs --target <path> --port <port> --log <path> --pid-file <path> [--workers <n>] [--clear]');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
for (const required of ['target', 'port', 'log', 'pid-file']) {
|
|
33
|
+
if (!args[required]) throw new Error(`--${required} is required`);
|
|
34
|
+
}
|
|
35
|
+
const target = path.resolve(args.target);
|
|
36
|
+
const log = path.resolve(args.log);
|
|
37
|
+
const pidFile = path.resolve(args['pid-file']);
|
|
38
|
+
fs.mkdirSync(path.dirname(log), { recursive: true });
|
|
39
|
+
fs.mkdirSync(path.dirname(pidFile), { recursive: true });
|
|
40
|
+
const logFd = fs.openSync(log, 'a');
|
|
41
|
+
const childArgs = ['expo', 'start', '--port', String(args.port)];
|
|
42
|
+
if (args.clear) childArgs.push('--clear');
|
|
43
|
+
const env = { ...process.env };
|
|
44
|
+
if (args.workers) env.METRO_MAX_WORKERS = String(args.workers);
|
|
45
|
+
const child = spawn('yarn', childArgs, {
|
|
46
|
+
cwd: target,
|
|
47
|
+
detached: true,
|
|
48
|
+
env,
|
|
49
|
+
stdio: ['ignore', logFd, logFd],
|
|
50
|
+
});
|
|
51
|
+
await new Promise((resolve, reject) => {
|
|
52
|
+
child.once('spawn', resolve);
|
|
53
|
+
child.once('error', reject);
|
|
54
|
+
});
|
|
55
|
+
fs.closeSync(logFd);
|
|
56
|
+
fs.writeFileSync(pidFile, `${String(child.pid)}\n`);
|
|
57
|
+
fs.writeFileSync(
|
|
58
|
+
path.join(path.dirname(pidFile), 'metro-launch.json'),
|
|
59
|
+
`${JSON.stringify({
|
|
60
|
+
schemaVersion: 1,
|
|
61
|
+
pid: child.pid,
|
|
62
|
+
startedAt: new Date().toISOString(),
|
|
63
|
+
target,
|
|
64
|
+
port: Number(args.port),
|
|
65
|
+
clear: Boolean(args.clear),
|
|
66
|
+
}, null, 2)}\n`,
|
|
67
|
+
);
|
|
68
|
+
child.unref();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
main().catch((error) => {
|
|
72
|
+
console.error(`launch-metro: ${error.message}`);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
});
|
|
File without changes
|
|
@@ -11,11 +11,11 @@
|
|
|
11
11
|
# resolved, skip the viewer entirely (Metro still runs detached to the log).
|
|
12
12
|
#
|
|
13
13
|
# Inputs (env/args): $1 = metro log path; PORT, LOG_DIR, and optional `slot` from
|
|
14
|
-
# the caller
|
|
14
|
+
# the caller.
|
|
15
15
|
|
|
16
|
-
# Session resolution (RECIPE_TMUX_SESSION → agentic-runtime.json session
|
|
17
|
-
#
|
|
18
|
-
#
|
|
16
|
+
# Session resolution (RECIPE_TMUX_SESSION → agentic-runtime.json session). Sourced
|
|
17
|
+
# relative to this lib; harness-path fallback for the installed layout where shared
|
|
18
|
+
# libs live beside this file.
|
|
19
19
|
_tv_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
20
20
|
# shellcheck disable=SC1091
|
|
21
21
|
for _tv_src in "$_tv_dir/tmux-session.sh" "$_tv_dir/../../shared/tmux-session.sh"; do
|
|
@@ -220,7 +220,9 @@ if [ "$PLATFORM" = "ios" ]; then
|
|
|
220
220
|
DEV_CLIENT_SCHEME="${IOS_DEV_CLIENT_SCHEME:-expo-metamask}"
|
|
221
221
|
|
|
222
222
|
boot_simulator_if_needed "$SIM_TARGET" || exit 1
|
|
223
|
-
|
|
223
|
+
if [ "${MM_HARNESS_FOCUS_SIMULATOR:-0}" = "1" ]; then
|
|
224
|
+
show_simulator "$SIM_TARGET"
|
|
225
|
+
fi
|
|
224
226
|
|
|
225
227
|
LAUNCHED=false
|
|
226
228
|
for BUNDLE_ID in "${BUNDLE_IDS[@]}"; do
|
|
File without changes
|
|
@@ -89,11 +89,8 @@ start_console_forwarder() {
|
|
|
89
89
|
# Only one debugger can own a React Native page. Reap any forwarder already
|
|
90
90
|
# attached to this Metro port, even if it was launched from another checkout or
|
|
91
91
|
# package install path and therefore does not own this runtime dir's pid file.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
pid="${line%% *}"
|
|
95
|
-
cmd="${line#* }"
|
|
96
|
-
[ "$pid" != "$line" ] || continue
|
|
92
|
+
local pid cmd
|
|
93
|
+
while read -r pid cmd; do
|
|
97
94
|
case "$cmd" in
|
|
98
95
|
*console-forwarder.cjs*" --port $PORT "*|*console-forwarder.cjs*" --port=$PORT "*)
|
|
99
96
|
kill "$pid" 2>/dev/null || true
|
|
@@ -227,19 +224,16 @@ if ! start_metro_tmux; then
|
|
|
227
224
|
export EXPO_NO_TYPESCRIPT_SETUP=1
|
|
228
225
|
export WATCHER_PORT="${PORT}" METRO_PORT="${PORT}"
|
|
229
226
|
export METRO_MAX_WORKERS="${METRO_WORKERS}"
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
echo "$metro_pid" > "$PID_FILE"
|
|
241
|
-
disown "$metro_pid" 2>/dev/null || true
|
|
242
|
-
)
|
|
227
|
+
launcher_args=(
|
|
228
|
+
--target "$TARGET"
|
|
229
|
+
--port "$PORT"
|
|
230
|
+
--log "$LOG_FILE"
|
|
231
|
+
--pid-file "$PID_FILE"
|
|
232
|
+
--workers "$METRO_WORKERS"
|
|
233
|
+
)
|
|
234
|
+
[ "$CLEAR" = true ] && launcher_args+=(--clear)
|
|
235
|
+
node "$SCRIPT_DIR/launch-metro.cjs" "${launcher_args[@]}"
|
|
236
|
+
) || exit 1
|
|
243
237
|
|
|
244
238
|
start_viewer_window "$LOG_FILE"
|
|
245
239
|
fi
|
|
@@ -61,6 +61,7 @@ fi
|
|
|
61
61
|
# incidental non-zero (platform-dependent) can never turn success into exit 1.
|
|
62
62
|
set +e
|
|
63
63
|
rm -f "$PID_FILE"
|
|
64
|
+
rm -f "$LOG_DIR/metro-launch.json"
|
|
64
65
|
|
|
65
66
|
# start-metro runs a per-checkout console-forwarder holding the device debugger
|
|
66
67
|
# slot; Metro going down must take it too, or the orphan keeps polling and later
|
|
@@ -283,7 +283,7 @@ const fs = require('fs');
|
|
|
283
283
|
const path = require('path');
|
|
284
284
|
const name = 'agentic overlay matches runner (HUD freshness)';
|
|
285
285
|
const harnessDir = process.env.HARNESS_DIR_FOR_DRIFT;
|
|
286
|
-
const targetDir = path.join(process.env.TARGET_FOR_DRIFT, 'app', '
|
|
286
|
+
const targetDir = path.join(process.env.TARGET_FOR_DRIFT, 'app', 'dev-tools', 'AgenticService');
|
|
287
287
|
function readTrimmed(file) {
|
|
288
288
|
try { return fs.readFileSync(file, 'utf8').trim(); } catch { return ''; }
|
|
289
289
|
}
|
|
@@ -298,7 +298,7 @@ if (!runnerDir) {
|
|
|
298
298
|
process.stdout.write(JSON.stringify({ name, status: 'warn', detail: 'runner source unavailable; overlay freshness compare skipped' }));
|
|
299
299
|
process.exit(0);
|
|
300
300
|
}
|
|
301
|
-
const overlayDir = path.join(runnerDir, 'adapters', 'mobile', 'overlay', 'app', '
|
|
301
|
+
const overlayDir = path.join(runnerDir, 'adapters', 'mobile', 'overlay', 'app', 'dev-tools', 'AgenticService');
|
|
302
302
|
let checked = 0;
|
|
303
303
|
const drifted = [];
|
|
304
304
|
const missing = [];
|
|
@@ -318,13 +318,13 @@ try {
|
|
|
318
318
|
process.stdout.write(JSON.stringify({ name, status: 'warn', detail: 'runner overlay compare skipped: ' + String((error && error.message) || error) }));
|
|
319
319
|
process.exit(0);
|
|
320
320
|
}
|
|
321
|
-
if (
|
|
322
|
-
process.stdout.write(JSON.stringify({ name, status: 'pass' }));
|
|
323
|
-
} else if (drifted.length || missing.length) {
|
|
321
|
+
if (drifted.length || missing.length) {
|
|
324
322
|
const parts = [];
|
|
325
323
|
if (drifted.length) parts.push('behind runner: ' + drifted.join(', '));
|
|
326
324
|
if (missing.length) parts.push('absent in repo: ' + missing.join(', '));
|
|
327
325
|
process.stdout.write(JSON.stringify({ name, status: 'warn', detail: parts.join('; ') + ' — rerun mm-harness install --force-overlay or mm-harness fixtures sync to refresh the in-repo HUD/AgenticService from the runner' }));
|
|
326
|
+
} else if (checked === 0) {
|
|
327
|
+
process.stdout.write(JSON.stringify({ name, status: 'pass' }));
|
|
328
328
|
} else {
|
|
329
329
|
process.stdout.write(JSON.stringify({ name, status: 'pass' }));
|
|
330
330
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
TARGET=""
|
|
5
|
+
while [[ $# -gt 0 ]]; do
|
|
6
|
+
case "$1" in
|
|
7
|
+
--target)
|
|
8
|
+
[[ $# -ge 2 ]] || { echo "Usage: install-repo-deps.sh --target <checkout>" >&2; exit 2; }
|
|
9
|
+
TARGET="$2"
|
|
10
|
+
shift 2
|
|
11
|
+
;;
|
|
12
|
+
-h|--help)
|
|
13
|
+
echo "Usage: install-repo-deps.sh --target <checkout>"
|
|
14
|
+
exit 0
|
|
15
|
+
;;
|
|
16
|
+
*) echo "Unknown flag: $1" >&2; exit 2 ;;
|
|
17
|
+
esac
|
|
18
|
+
done
|
|
19
|
+
|
|
20
|
+
[[ -n "$TARGET" ]] || { echo "install-repo-deps: --target is required" >&2; exit 2; }
|
|
21
|
+
TARGET="$(cd "$TARGET" && pwd)"
|
|
22
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
23
|
+
|
|
24
|
+
# shellcheck disable=SC1091
|
|
25
|
+
. "$SCRIPT_DIR/activate-repo-node.sh"
|
|
26
|
+
require_repo_node "$TARGET"
|
|
27
|
+
|
|
28
|
+
cd "$TARGET"
|
|
29
|
+
corepack yarn install --immutable
|
|
File without changes
|
|
@@ -75,6 +75,12 @@ function emit(result, json) {
|
|
|
75
75
|
if (result.ok) {
|
|
76
76
|
const detail = result.method ? ` via ${result.method}` : '';
|
|
77
77
|
const where = result.port ? ` (port ${result.port})` : result.cdpPort ? ` (cdp ${result.cdpPort})` : '';
|
|
78
|
+
if (result.opened === false) {
|
|
79
|
+
if (result.endpoint) console.log(`Resolved debugger endpoint${detail}${where}: ${result.endpoint}`);
|
|
80
|
+
else console.log(`Debugger action not invoked${detail}${where}`);
|
|
81
|
+
if (result.detail) console.log(result.detail);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
78
84
|
console.log(`Opened debugger${detail}${where}`);
|
|
79
85
|
if (result.target?.url) console.log(`Target: ${result.target.url}`);
|
|
80
86
|
if (result.configureHint) console.log(`Configure chrome://inspect target: ${result.configureHint}`);
|
|
@@ -144,16 +150,30 @@ async function openMobileDebugger(port, action, shouldOpen) {
|
|
|
144
150
|
if (!Number.isInteger(numericPort) || numericPort <= 0) {
|
|
145
151
|
return { ok: false, adapter: 'mobile', action: normalizedAction, error: 'invalid-port', port };
|
|
146
152
|
}
|
|
153
|
+
const endpoint = `http://127.0.0.1:${numericPort}${urlPath}`;
|
|
154
|
+
if (!shouldOpen) {
|
|
155
|
+
return {
|
|
156
|
+
ok: true,
|
|
157
|
+
adapter: 'mobile',
|
|
158
|
+
action: normalizedAction,
|
|
159
|
+
method: 'metro-http',
|
|
160
|
+
endpoint,
|
|
161
|
+
port: numericPort,
|
|
162
|
+
opened: false,
|
|
163
|
+
detail: 'Open action not invoked (--no-open).',
|
|
164
|
+
};
|
|
165
|
+
}
|
|
147
166
|
|
|
148
167
|
let metroError = null;
|
|
149
168
|
try {
|
|
150
|
-
const opened = await fetchOk(
|
|
169
|
+
const opened = await fetchOk(endpoint, { method: 'POST' });
|
|
151
170
|
if (opened) {
|
|
152
171
|
return {
|
|
153
172
|
ok: true,
|
|
154
173
|
adapter: 'mobile',
|
|
155
174
|
action: normalizedAction,
|
|
156
175
|
method: 'metro-http',
|
|
176
|
+
endpoint,
|
|
157
177
|
port: numericPort,
|
|
158
178
|
opened: shouldOpen,
|
|
159
179
|
};
|
|
@@ -171,6 +191,9 @@ async function openMobileDebugger(port, action, shouldOpen) {
|
|
|
171
191
|
adapter: 'mobile',
|
|
172
192
|
action: normalizedAction,
|
|
173
193
|
method: 'cdp-eval',
|
|
194
|
+
endpoint: normalizedAction === 'dev-menu'
|
|
195
|
+
? 'NativeModules.DevSettings.show()'
|
|
196
|
+
: 'NativeModules.DevSettings.openDebugger()',
|
|
174
197
|
port: numericPort,
|
|
175
198
|
opened: shouldOpen,
|
|
176
199
|
};
|
|
@@ -181,6 +204,7 @@ async function openMobileDebugger(port, action, shouldOpen) {
|
|
|
181
204
|
adapter: 'mobile',
|
|
182
205
|
action: normalizedAction,
|
|
183
206
|
method: 'metro-http',
|
|
207
|
+
endpoint,
|
|
184
208
|
port: numericPort,
|
|
185
209
|
error: metroError,
|
|
186
210
|
hint: 'Metro may be down or the debug target is not ready. Check metro status and try again.',
|
|
@@ -214,9 +238,11 @@ async function openExtensionDevtools(cdpPort, targetKind, shouldOpen) {
|
|
|
214
238
|
|
|
215
239
|
const inspectUrl = 'chrome://inspect/#devices';
|
|
216
240
|
const configureHint = `127.0.0.1:${numericPort}`;
|
|
241
|
+
const versionEndpoint = `http://127.0.0.1:${numericPort}/json/version`;
|
|
242
|
+
const listEndpoint = `http://127.0.0.1:${numericPort}/json/list`;
|
|
217
243
|
|
|
218
244
|
try {
|
|
219
|
-
await fetchJson(
|
|
245
|
+
await fetchJson(versionEndpoint);
|
|
220
246
|
} catch (error) {
|
|
221
247
|
return {
|
|
222
248
|
ok: false,
|
|
@@ -224,6 +250,7 @@ async function openExtensionDevtools(cdpPort, targetKind, shouldOpen) {
|
|
|
224
250
|
targetKind,
|
|
225
251
|
cdpPort: numericPort,
|
|
226
252
|
error: 'cdp-unreachable',
|
|
253
|
+
endpoint: versionEndpoint,
|
|
227
254
|
inspectUrl,
|
|
228
255
|
configureHint,
|
|
229
256
|
hint: `Start the extension runtime first (mm-harness launch). Then open chrome://inspect and add ${configureHint}.`,
|
|
@@ -233,7 +260,7 @@ async function openExtensionDevtools(cdpPort, targetKind, shouldOpen) {
|
|
|
233
260
|
|
|
234
261
|
let targets;
|
|
235
262
|
try {
|
|
236
|
-
targets = await fetchJson(
|
|
263
|
+
targets = await fetchJson(listEndpoint);
|
|
237
264
|
} catch (error) {
|
|
238
265
|
return {
|
|
239
266
|
ok: false,
|
|
@@ -241,6 +268,7 @@ async function openExtensionDevtools(cdpPort, targetKind, shouldOpen) {
|
|
|
241
268
|
targetKind,
|
|
242
269
|
cdpPort: numericPort,
|
|
243
270
|
error: 'cdp-list-failed',
|
|
271
|
+
endpoint: listEndpoint,
|
|
244
272
|
inspectUrl,
|
|
245
273
|
configureHint,
|
|
246
274
|
hint: `CDP is up but /json/list failed. Open chrome://inspect and add ${configureHint}.`,
|
|
@@ -255,6 +283,7 @@ async function openExtensionDevtools(cdpPort, targetKind, shouldOpen) {
|
|
|
255
283
|
targetKind,
|
|
256
284
|
cdpPort: numericPort,
|
|
257
285
|
error: 'cdp-list-invalid',
|
|
286
|
+
endpoint: listEndpoint,
|
|
258
287
|
inspectUrl,
|
|
259
288
|
configureHint,
|
|
260
289
|
hint: `Open chrome://inspect and add ${configureHint}.`,
|
|
@@ -269,6 +298,7 @@ async function openExtensionDevtools(cdpPort, targetKind, shouldOpen) {
|
|
|
269
298
|
adapter: 'extension',
|
|
270
299
|
targetKind,
|
|
271
300
|
method: 'devtools-frontend',
|
|
301
|
+
endpoint: picked.devtoolsFrontendUrl,
|
|
272
302
|
cdpPort: numericPort,
|
|
273
303
|
target: {
|
|
274
304
|
type: picked.type,
|
|
@@ -291,6 +321,7 @@ async function openExtensionDevtools(cdpPort, targetKind, shouldOpen) {
|
|
|
291
321
|
adapter: 'extension',
|
|
292
322
|
targetKind,
|
|
293
323
|
method: picked?.devtoolsFrontendUrl ? 'devtools-frontend' : 'chrome-inspect',
|
|
324
|
+
endpoint: picked?.devtoolsFrontendUrl || inspectUrl,
|
|
294
325
|
cdpPort: numericPort,
|
|
295
326
|
target: picked
|
|
296
327
|
? { type: picked.type, url: picked.url, title: picked.title || null }
|
|
@@ -341,4 +372,4 @@ main().catch((error) => {
|
|
|
341
372
|
console.error(payload.error);
|
|
342
373
|
}
|
|
343
374
|
process.exit(1);
|
|
344
|
-
});
|
|
375
|
+
});
|
|
@@ -45,7 +45,7 @@ session=""
|
|
|
45
45
|
command -v resolve_run_tmux_session >/dev/null 2>&1 && session="$(resolve_run_tmux_session "$RUNTIME_DIR")"
|
|
46
46
|
if [ -z "$session" ] || ! tmux has-session -t "=$session" 2>/dev/null; then
|
|
47
47
|
echo "open-log-window: no run-owned tmux session to place the window in." >&2
|
|
48
|
-
echo " Next:
|
|
48
|
+
echo " Next: set RECIPE_TMUX_SESSION=<session>, or configure agentic-runtime.json session, then retry" >&2
|
|
49
49
|
exit 1
|
|
50
50
|
fi
|
|
51
51
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -139,8 +139,77 @@ export function resolveDefaultExtensionPorts(repo) {
|
|
|
139
139
|
});
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
function parseKvLines(output) {
|
|
143
|
+
if (!output?.trim()) return {};
|
|
144
|
+
const kv = {};
|
|
145
|
+
for (const line of output.split('\n')) {
|
|
146
|
+
const trimmed = line.trim();
|
|
147
|
+
if (!trimmed) continue;
|
|
148
|
+
const eq = trimmed.indexOf('=');
|
|
149
|
+
if (eq <= 0) continue;
|
|
150
|
+
const key = trimmed.slice(0, eq);
|
|
151
|
+
const val = trimmed.slice(eq + 1);
|
|
152
|
+
if (val === 'null' || val === 'undefined' || val === '') continue;
|
|
153
|
+
kv[key] = val;
|
|
154
|
+
}
|
|
155
|
+
return kv;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function mergeKvLayers(...layers) {
|
|
159
|
+
const result = {};
|
|
160
|
+
for (const layer of layers) {
|
|
161
|
+
const source = typeof layer === 'string' ? layer : layer?.source;
|
|
162
|
+
const overwrite = typeof layer === 'string' ? false : Boolean(layer?.overwrite);
|
|
163
|
+
const kv = parseKvLines(source);
|
|
164
|
+
for (const [key, val] of Object.entries(kv)) {
|
|
165
|
+
if (overwrite || result[key] === undefined) result[key] = val;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return Object.keys(result).length ? formatKvLines(result) : '';
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function extensionRuntimeContextPath(repo) {
|
|
172
|
+
const repoRoot = path.resolve(repo);
|
|
173
|
+
const envCtx = process.env.RECIPE_RUNTIME_CONTEXT;
|
|
174
|
+
if (envCtx) {
|
|
175
|
+
const resolved = path.resolve(envCtx);
|
|
176
|
+
if (resolved === repoRoot || resolved.startsWith(`${repoRoot}${path.sep}`)) {
|
|
177
|
+
return resolved;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return path.join(repoRoot, recipeRuntimeDir(), 'agentic-runtime.json');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function resolveExtensionRuntimeContext(repo) {
|
|
184
|
+
const ctxPath = extensionRuntimeContextPath(repo);
|
|
185
|
+
if (!fs.existsSync(ctxPath)) return null;
|
|
186
|
+
try {
|
|
187
|
+
const c = JSON.parse(fs.readFileSync(ctxPath, 'utf8'));
|
|
188
|
+
const cdp = c.cdpPort;
|
|
189
|
+
const watcher = c.devServerPort ?? c.watcherPort ?? c.metroPort;
|
|
190
|
+
if ((cdp == null || cdp === '') && (watcher == null || watcher === '') && !c.slotId) return null;
|
|
191
|
+
return formatKvLines({
|
|
192
|
+
...(cdp != null && cdp !== '' ? { CDP_PORT: cdp } : {}),
|
|
193
|
+
...(watcher != null && watcher !== '' ? { WATCHER_PORT: watcher } : {}),
|
|
194
|
+
...(c.slotId ? { SLOT_ID: c.slotId } : {}),
|
|
195
|
+
});
|
|
196
|
+
} catch {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Checkout-scoped extension ports: runtime context (authoritative) → pool → suffix formula.
|
|
202
|
+
export function formatExtensionSlotPorts(repo) {
|
|
203
|
+
const realRepo = path.resolve(repo);
|
|
204
|
+
return mergeKvLayers(
|
|
205
|
+
{ source: resolveExtensionRuntimeContext(realRepo), overwrite: true },
|
|
206
|
+
{ source: resolveSlotPortsByRepo(realRepo), overwrite: false },
|
|
207
|
+
{ source: resolveDefaultExtensionPorts(realRepo), overwrite: false },
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
142
211
|
export function resolveExtensionRuntimePorts(repo) {
|
|
143
|
-
return
|
|
212
|
+
return formatExtensionSlotPorts(repo);
|
|
144
213
|
}
|
|
145
214
|
|
|
146
215
|
export function resolveMobileSlotDefaults(repo) {
|
|
@@ -216,6 +285,8 @@ export const cliFns = {
|
|
|
216
285
|
// Back-compat cli key for one release.
|
|
217
286
|
resolve_farmslot_ports_by_repo: resolveSlotPortsByRepo,
|
|
218
287
|
resolve_default_extension_ports: resolveDefaultExtensionPorts,
|
|
288
|
+
resolve_extension_runtime_context: resolveExtensionRuntimeContext,
|
|
289
|
+
resolve_extension_slot_ports: (repo) => formatExtensionSlotPorts(repo) || null,
|
|
219
290
|
resolve_extension_runtime_ports: (repo) => resolveExtensionRuntimePorts(repo) || null,
|
|
220
291
|
resolve_mobile_runtime_context: resolveMobileRuntimeContext,
|
|
221
292
|
resolve_mobile_slot_defaults: resolveMobileSlotDefaults,
|
|
File without changes
|
|
@@ -43,33 +43,37 @@ resolve_extension_runtime_ports() {
|
|
|
43
43
|
_resolve_ports_cli resolve_extension_runtime_ports "${1:-.}"
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
46
|
+
resolve_extension_slot_ports() {
|
|
47
|
+
_resolve_ports_cli resolve_extension_slot_ports "${1:-.}"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# Apply the checkout-scoped extension port ladder (context → pool → formula).
|
|
51
|
+
apply_extension_slot_ports() {
|
|
52
|
+
local repo="${1:-.}" line key val resolved="" ctx_file="" runtime_rel="${RECIPE_RUNTIME_DIR:-temp/recipe/runtime}"
|
|
53
|
+
repo="$(cd "$repo" && pwd)"
|
|
54
|
+
ctx_file="$repo/$runtime_rel/agentic-runtime.json"
|
|
55
|
+
if [ -f "$ctx_file" ]; then
|
|
56
|
+
export RECIPE_RUNTIME_CONTEXT="$ctx_file"
|
|
52
57
|
else
|
|
53
|
-
|
|
58
|
+
unset RECIPE_RUNTIME_CONTEXT
|
|
54
59
|
fi
|
|
60
|
+
resolved="$(_resolve_ports_cli resolve_extension_slot_ports "$repo" 2>/dev/null)" || return 0
|
|
55
61
|
while IFS= read -r line; do
|
|
56
62
|
[ -n "$line" ] || continue
|
|
57
63
|
key="${line%%=*}"
|
|
58
64
|
val="${line#*=}"
|
|
59
65
|
case "$key" in
|
|
60
|
-
CDP_PORT)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
WATCHER_PORT)
|
|
64
|
-
if [ "$from_pool" = true ] || [ -z "${WATCHER_PORT:-}" ]; then WATCHER_PORT="$val"; fi
|
|
65
|
-
;;
|
|
66
|
-
SLOT_ID)
|
|
67
|
-
if [ "$from_pool" = true ] || [ -z "${RECIPE_SLOT_ID:-}" ]; then RECIPE_SLOT_ID="$val"; fi
|
|
68
|
-
;;
|
|
66
|
+
CDP_PORT) CDP_PORT="$val" ;;
|
|
67
|
+
WATCHER_PORT) WATCHER_PORT="$val" ;;
|
|
68
|
+
SLOT_ID) RECIPE_SLOT_ID="$val" ;;
|
|
69
69
|
esac
|
|
70
70
|
done <<< "$resolved"
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
apply_resolved_extension_ports() {
|
|
74
|
+
apply_extension_slot_ports "$@"
|
|
75
|
+
}
|
|
76
|
+
|
|
73
77
|
resolve_mobile_slot_defaults() {
|
|
74
78
|
_resolve_ports_cli resolve_mobile_slot_defaults "$1"
|
|
75
79
|
}
|
|
@@ -107,4 +111,4 @@ apply_resolved_mobile_ports() {
|
|
|
107
111
|
;;
|
|
108
112
|
esac
|
|
109
113
|
done <<< "$resolved"
|
|
110
|
-
}
|
|
114
|
+
}
|
|
File without changes
|
|
@@ -7,8 +7,6 @@
|
|
|
7
7
|
# tail -F) in the wrong place. Resolution ladder, first hit wins:
|
|
8
8
|
# 1. RECIPE_TMUX_SESSION — the orchestrator's explicit hand-off.
|
|
9
9
|
# 2. agentic-runtime.json `session` — the prepared checkout's recorded session.
|
|
10
|
-
# 3. the current session — but ONLY inside a tmux client (never the
|
|
11
|
-
# last-attached session reported outside one, which would be a foreign leak).
|
|
12
10
|
# Prints the resolved name (empty when none). Callers still gate on has-session
|
|
13
11
|
# before creating a window.
|
|
14
12
|
|
|
@@ -28,8 +26,5 @@ resolve_run_tmux_session() {
|
|
|
28
26
|
return 0
|
|
29
27
|
fi
|
|
30
28
|
fi
|
|
31
|
-
if [ -n "${TMUX:-}" ]; then
|
|
32
|
-
tmux display-message -p '#S' 2>/dev/null || true
|
|
33
|
-
fi
|
|
34
29
|
return 0
|
|
35
30
|
}
|
|
@@ -53,9 +53,14 @@ tmux_viewer_close_marker() {
|
|
|
53
53
|
local marker_file="$1"
|
|
54
54
|
command -v tmux >/dev/null 2>&1 || { rm -f "$marker_file"; return 0; }
|
|
55
55
|
if [ -f "$marker_file" ]; then
|
|
56
|
-
local target
|
|
56
|
+
local target marker_session owned_session runtime_dir
|
|
57
57
|
target="$(cat "$marker_file" 2>/dev/null || true)"
|
|
58
|
-
|
|
58
|
+
marker_session="${target%%:*}"
|
|
59
|
+
runtime_dir="$(dirname "$marker_file")"
|
|
60
|
+
owned_session="$(resolve_run_tmux_session "$runtime_dir")"
|
|
61
|
+
if [ -n "$target" ] && [ -n "$owned_session" ] && [ "$marker_session" = "$owned_session" ]; then
|
|
62
|
+
tmux kill-window -t "$target" >/dev/null 2>&1 || true
|
|
63
|
+
fi
|
|
59
64
|
fi
|
|
60
65
|
rm -f "$marker_file"
|
|
61
66
|
}
|
|
@@ -27,12 +27,6 @@ async function closeTab(port, id) {
|
|
|
27
27
|
} catch {
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
|
-
async function activateTab(port, id) {
|
|
31
|
-
try {
|
|
32
|
-
await fetch(`http://127.0.0.1:${port}/json/activate/${id}`, { signal: AbortSignal.timeout(4e3) });
|
|
33
|
-
} catch {
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
30
|
function isStrayTab(url) {
|
|
37
31
|
if (typeof url !== "string") return false;
|
|
38
32
|
return /^chrome:\/\/newtab\/?$/.test(url) || /^chrome:\/\/new-tab-page\b/.test(url) || url === "about:blank";
|
|
@@ -131,7 +125,6 @@ async function ensureExtensionReady(target, options) {
|
|
|
131
125
|
for (const t of convergedTargets) {
|
|
132
126
|
if (t.type === "page" && t.id && isStrayTab(t.url)) await closeTab(cdpPort, String(t.id));
|
|
133
127
|
}
|
|
134
|
-
if (homesNow[0]?.id) await activateTab(cdpPort, String(homesNow[0].id));
|
|
135
128
|
}
|
|
136
129
|
const finalListing = await jsonList(cdpPort);
|
|
137
130
|
if (!finalListing.ok) {
|