@optimuslabs/harness-map-staging 1.5.40 → 1.5.42

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.
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Runs the blocked-agent window process as a one-off launchd job in the user's GUI session
3
+ * (docs/agent-exception-requests/DESIGN.md), instead of as a child of the hook.
4
+ *
5
+ * A child of the hook lives in the agent's process tree and context: an agent that stops what its
6
+ * hook started once the hook returns, or runs hooks where no window can open (seen with old Cursor
7
+ * versions), left the user with no pop-up at all. launchd starts the job itself, in the session the
8
+ * Dock and Finder run in, so neither can reach it. The job runs once (no KeepAlive), from a plist in
9
+ * a folder of its own (mode 600); the window process removes the job when it is done, and jobs a
10
+ * crashed process left behind are removed the next time one starts. Nothing here needs root.
11
+ */
12
+ import { spawnSync } from 'node:child_process';
13
+ import { randomBytes } from 'node:crypto';
14
+ import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
15
+ import { tmpdir } from 'node:os';
16
+ import { basename, dirname, join } from 'node:path';
17
+ const LABEL_PREFIX = 'io.optimuslabs.agent-window.';
18
+ /** A label this module made, and nothing else: the prefix and 12 hex characters. */
19
+ const OUR_LABEL = /^io\.optimuslabs\.agent-window\.[0-9a-f]{12}$/;
20
+ /** The user's GUI launchd domain, and nothing else. */
21
+ const GUI_DOMAIN = /^gui\/\d+$/;
22
+ /** The env names the window process needs (story id, endpoint, home, temp folder): nothing else. */
23
+ const PASSED_ENV = /^(OPTIMUS_[A-Z0-9_]+|HOME|USER|PATH|TMPDIR|LANG)$/;
24
+ /** Set on the job, so the window process knows its label and plist folder. */
25
+ export const JOB_LABEL_ENV = 'OPTIMUS_WINDOW_JOB';
26
+ const JOB_DIR_ENV = 'OPTIMUS_WINDOW_JOB_DIR';
27
+ function xml(value) {
28
+ return value.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
29
+ }
30
+ /** The job's plist: run `program` once, now, with `env`. */
31
+ export function jobPlist(label, program, env) {
32
+ const args = program.map((a) => `<string>${xml(a)}</string>`).join('');
33
+ const vars = Object.entries(env)
34
+ .map(([k, v]) => `<key>${xml(k)}</key><string>${xml(v)}</string>`)
35
+ .join('');
36
+ return ('<?xml version="1.0" encoding="UTF-8"?>\n' +
37
+ '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n' +
38
+ `<plist version="1.0"><dict><key>Label</key><string>${xml(label)}</string>` +
39
+ `<key>ProgramArguments</key><array>${args}</array>` +
40
+ `<key>EnvironmentVariables</key><dict>${vars}</dict>` +
41
+ '<key>RunAtLoad</key><true/><key>KeepAlive</key><false/></dict></plist>\n');
42
+ }
43
+ /** The env the job gets: what the window process needs from this one, and its own job details. */
44
+ function jobEnv(label, dir) {
45
+ const env = {};
46
+ for (const [k, v] of Object.entries(process.env))
47
+ if (PASSED_ENV.test(k) && v !== undefined)
48
+ env[k] = v;
49
+ env.TMPDIR = tmpdir();
50
+ env[JOB_LABEL_ENV] = label;
51
+ env[JOB_DIR_ENV] = dir;
52
+ return env;
53
+ }
54
+ function guiDomain() {
55
+ return typeof process.getuid === 'function' ? `gui/${process.getuid()}` : null;
56
+ }
57
+ /** Remove one of our jobs from the domain; anything that is not exactly ours is left alone. */
58
+ function bootout(domain, label) {
59
+ if (!GUI_DOMAIN.test(domain) || !OUR_LABEL.test(label))
60
+ return;
61
+ const target = `${domain}/${label}`;
62
+ // A fixed command, no shell, and a target checked above to be our own job in the user's domain.
63
+ spawnSync('launchctl', ['bootout', target]); // nosafe: SAFE801
64
+ }
65
+ /** Remove the window jobs no process runs any more (one a crashed window process left behind). */
66
+ function sweepFinishedJobs(domain) {
67
+ const out = spawnSync('launchctl', ['list'], { encoding: 'utf8' });
68
+ for (const line of (out.stdout || '').split('\n')) {
69
+ const [pid, , label] = line.trim().split(/\s+/);
70
+ if (pid === '-' && label)
71
+ bootout(domain, label);
72
+ }
73
+ }
74
+ /**
75
+ * Start `program` as a one-off launchd job in the user's GUI session; the job's label, or null when
76
+ * launchd would not take it (the caller then starts the process itself).
77
+ */
78
+ export function startLaunchdJob(program) {
79
+ const domain = guiDomain();
80
+ if (!domain)
81
+ return null;
82
+ const dir = mkdtempSync(join(tmpdir(), 'optimus-window-job-'));
83
+ const label = `${LABEL_PREFIX}${randomBytes(6).toString('hex')}`;
84
+ const plist = join(dir, `${label}.plist`);
85
+ writeFileSync(plist, jobPlist(label, program, jobEnv(label, dir)), { mode: 0o600 });
86
+ sweepFinishedJobs(domain);
87
+ // A fixed command, no shell; the plist is one this process just wrote in its own folder.
88
+ const out = spawnSync('launchctl', ['bootstrap', domain, plist], { encoding: 'utf8' }); // nosafe: SAFE801
89
+ if (out.status === 0)
90
+ return label;
91
+ rmSync(dir, { recursive: true, force: true });
92
+ throw new Error(`launchctl bootstrap failed (exit ${out.status}): ${(out.stderr || '').trim()}`);
93
+ }
94
+ /** In the window process: remove its own job and plist folder, if launchd started it. */
95
+ export function endLaunchdJob() {
96
+ const label = process.env[JOB_LABEL_ENV] || '';
97
+ const dir = process.env[JOB_DIR_ENV] || '';
98
+ const domain = guiDomain();
99
+ if (dir && basename(dir).startsWith('optimus-window-job-') && dirname(dir) === tmpdir()) {
100
+ rmSync(dir, { recursive: true, force: true });
101
+ }
102
+ // Last: removing the job ends this process.
103
+ if (domain)
104
+ bootout(domain, label);
105
+ }
@@ -2,28 +2,39 @@
2
2
  * Shows the blocked-agent window (docs/agent-exception-requests/DESIGN.md).
3
3
  *
4
4
  * Started detached by the prompt gate (`launchAgentAccessWindow`), so the hook returns its block at
5
- * once. Run as `node window_runner.js <agent> [<words.json>]`: an agent's own block reads the window's
5
+ * once. Run as `node window_runner.js <agent> <kind> [<words.json>]`: an agent's own block reads the window's
6
6
  * words from the synced approvals; a tenant or email-domain block passes them, built by the gate, in
7
7
  * a file of its own temporary folder. It draws the window with `osascript` (macOS only),
8
8
  * then acts on what the user chose: Open launches the approved app, Cancel only closes it. The prompt
9
- * was already blocked. Nothing here talks to the network; a failure is logged and never thrown.
9
+ * was already blocked. Whether the window was shown, or why not (the `osascript` error), is reported
10
+ * into the prompt's story as a `compliance/agent_access_window` functional run, so a machine where `osascript`
11
+ * cannot draw it is visible on the server; the gate had already told the hook the window was up, so
12
+ * the user saw no pop-up at all. Nothing is ever thrown.
10
13
  */
11
14
  import { spawn, spawnSync } from 'node:child_process';
12
15
  import { mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
13
- import { tmpdir } from 'node:os';
16
+ import { release, tmpdir } from 'node:os';
14
17
  import { basename, dirname, join } from 'node:path';
15
18
  import { fileURLToPath } from 'node:url';
19
+ import { reportFunctionalRun } from '../log_config_files/runtime/client_event_reporter.js';
16
20
  import { hookRunLog } from '../log_config_files/runtime/hook_logger.js';
17
21
  import { readRemediationInstructionsFile } from '../log_config_files/runtime/management_storage.js';
18
22
  import { approvalId } from './decision.js';
23
+ import { endLaunchdJob, JOB_LABEL_ENV, startLaunchdJob } from './launchd_job.js';
19
24
  const HERE = dirname(fileURLToPath(import.meta.url));
20
25
  const WINDOW_SCRIPT = join(HERE, '..', 'agent_block_window', 'agent_block_window.jxa');
21
26
  /** The agents the gate checks, by approval id: nothing else is ever passed to a process. */
22
27
  const AGENTS = ['claude', 'cursor', 'codex', 'copilot', 'opencode'];
28
+ const KINDS = ['agent', 'login', 'skill'];
23
29
  /** A macOS bundle identifier, and nothing that could be read as an option or a path. */
24
30
  const BUNDLE_ID = /^[A-Za-z0-9][A-Za-z0-9.-]{0,254}$/;
25
31
  /** Where the gate leaves a login block's words for the runner: a folder of its own under tmpdir. */
26
32
  const WORDS_PREFIX = 'optimus-window-words-';
33
+ /**
34
+ * How long the window may stay open. It waits for a person, so this is long; it exists so a window
35
+ * that never appears (osascript stuck before drawing) is stopped and reported, not left spinning.
36
+ */
37
+ const WINDOW_TIME_LIMIT_MS = 5 * 60 * 1000;
27
38
  /** Write a login block's words where only the runner reads them; returns the file's path. */
28
39
  function writeWords(words) {
29
40
  const dir = mkdtempSync(join(tmpdir(), WORDS_PREFIX));
@@ -56,18 +67,23 @@ function syncedWindow(agent) {
56
67
  return readRemediationInstructionsFile().agent_access_windows?.[approvalId(agent)];
57
68
  }
58
69
  /**
59
- * Start the runner in the background, detached from the hook, on macOS only; true when it started.
60
- * `words` are a tenant or email-domain block's, built by the gate; without them the runner reads the
61
- * agent's own block window from the synced approvals. False when there is nothing to show (an
62
- * approvals file synced before the window existed), so Cursor's hook keeps its own alert.
70
+ * Start the runner in the background, on macOS only; true when it started. `words` are a tenant or
71
+ * email-domain block's, built by the gate; without them the runner reads the agent's own block
72
+ * window from the synced approvals. False when there is nothing to show (an approvals file synced
73
+ * before the window existed), so Cursor's hook keeps its own alert.
74
+ *
75
+ * launchd starts it in the user's GUI session (`launchd_job.ts`), out of reach of what the agent
76
+ * does to its hook's processes; when launchd will not take the job, it is started here, detached.
63
77
  */
64
- export function launchAgentAccessWindow(agent, words) {
65
- if (process.platform !== 'darwin' || !AGENTS.includes(agent))
78
+ export function launchAgentAccessWindow(agent, kind, words) {
79
+ if (process.platform !== 'darwin' || !AGENTS.includes(agent) || !KINDS.includes(kind))
66
80
  return false;
67
81
  if (!words && !syncedWindow(agent))
68
82
  return false;
69
83
  try {
70
- const args = [fileURLToPath(import.meta.url), agent, ...(words ? [writeWords(words)] : [])];
84
+ const args = [fileURLToPath(import.meta.url), agent, kind, ...(words ? [writeWords(words)] : [])];
85
+ if (startByLaunchd([process.execPath, ...args]))
86
+ return true;
71
87
  const options = { detached: true, stdio: 'ignore' };
72
88
  // An argument list, no shell: an agent from the fixed list above, and the path of a file it wrote.
73
89
  const child = spawn(process.execPath, args, options); // nosafe: SAFE801
@@ -79,24 +95,68 @@ export function launchAgentAccessWindow(agent, words) {
79
95
  return false;
80
96
  }
81
97
  }
98
+ /** Start the runner as a launchd job; false (logged) when launchd would not take it. */
99
+ function startByLaunchd(program) {
100
+ try {
101
+ return startLaunchdJob(program) !== null;
102
+ }
103
+ catch (err) {
104
+ hookRunLog(`agent_access_window: launchd: ${err instanceof Error ? err.message : String(err)}`);
105
+ return false;
106
+ }
107
+ }
108
+ /** The installed Cursor's version ('' for another agent or when it cannot be read). */
109
+ function agentVersion(agent) {
110
+ if (agent !== 'cursor')
111
+ return '';
112
+ const plist = '/Applications/Cursor.app/Contents/Info.plist';
113
+ const out = spawnSync('plutil', ['-extract', 'CFBundleShortVersionString', 'raw', plist], { encoding: 'utf8' });
114
+ return out.status === 0 ? (out.stdout || '').trim().slice(0, 32) : '';
115
+ }
116
+ /**
117
+ * True when this process is Intel code under Rosetta on Apple Silicon (an x64 node, such as Intel
118
+ * Homebrew's). Its children run as Intel code too, and osascript as Intel code hangs in
119
+ * `ObjC.registerSubclass` before the window is drawn. False on a real Intel Mac (no such sysctl).
120
+ */
121
+ export function underRosetta() {
122
+ const out = spawnSync('sysctl', ['-n', 'sysctl.proc_translated'], { encoding: 'utf8' });
123
+ return out.status === 0 && (out.stdout || '').trim() === '1';
124
+ }
125
+ /** The osascript command line for the window: native arm64 through `arch` when under Rosetta. */
126
+ function osascriptCommand(args, rosetta) {
127
+ return rosetta ? ['/usr/bin/arch', ['-arm64', '/usr/bin/osascript', ...args]] : ['osascript', args];
128
+ }
82
129
  /** Draw the window and return what the user did; null when it could not open. */
83
130
  export function showWindow(words) {
131
+ return drawWindow(words).result;
132
+ }
133
+ /** Draw the window: what the user did, or why it could not be shown (`error`, '' when it was). */
134
+ export function drawWindow(words, rosetta = underRosetta()) {
84
135
  if (!words)
85
- return null;
136
+ return { result: null, error: 'no words to show' };
86
137
  const dir = mkdtempSync(join(tmpdir(), 'optimus-window-'));
87
138
  const payloadPath = join(dir, 'payload.json');
88
139
  try {
89
140
  writeFileSync(payloadPath, JSON.stringify({ words }));
90
- const out = spawnSync('osascript', ['-l', 'JavaScript', WINDOW_SCRIPT, payloadPath], { encoding: 'utf8' });
141
+ const [cmd, args] = osascriptCommand(['-l', 'JavaScript', WINDOW_SCRIPT, payloadPath], rosetta);
142
+ // A fixed command, no shell: osascript (or arch running it) with our own script and payload.
143
+ const out = spawnSync(cmd, args, { encoding: 'utf8', timeout: WINDOW_TIME_LIMIT_MS }); // nosafe: SAFE801
144
+ if (out.error?.code === 'ETIMEDOUT') {
145
+ const error = `osascript stopped: still running after ${WINDOW_TIME_LIMIT_MS / 1000}s`;
146
+ hookRunLog(`agent_access_window: ${error}`);
147
+ return { result: null, error };
148
+ }
91
149
  if (out.status !== 0) {
92
- hookRunLog(`agent_access_window: osascript failed: ${(out.stderr || '').trim()}`);
93
- return null;
150
+ const error = `osascript failed (exit ${out.status ?? out.signal}): ${(out.stderr || out.error?.message || '').trim()}`;
151
+ hookRunLog(`agent_access_window: ${error}`);
152
+ return { result: null, error };
94
153
  }
95
- return JSON.parse(out.stdout.trim());
154
+ return { result: JSON.parse(out.stdout.trim()), error: '' };
96
155
  }
97
156
  catch (err) {
98
- hookRunLog(`agent_access_window: ${err instanceof Error ? err.message : String(err)}`);
99
- return null;
157
+ const error = err instanceof Error ? err.message : String(err);
158
+ hookRunLog(`agent_access_window: ${error}`);
159
+ return { result: null, error };
100
160
  }
101
161
  finally {
102
162
  rmSync(dir, { recursive: true, force: true });
@@ -115,15 +175,40 @@ export function actOn(result) {
115
175
  if (result.action === 'open')
116
176
  openApp(result.bundle_id ?? '');
117
177
  }
118
- export function runAgentAccessWindow(agent, wordsPath = '') {
178
+ /**
179
+ * Show the window, act on the choice, and report into the prompt's story whether it was shown
180
+ * (the runner inherits the gate's story id). Awaited, so the report is sent before the runner exits.
181
+ */
182
+ export async function runAgentAccessWindow(agent, kind, wordsPath = '') {
183
+ const launched = process.env[JOB_LABEL_ENV] ? 'launchd' : 'spawn';
184
+ const rosetta = underRosetta();
185
+ const metadata = {
186
+ darwin: release(),
187
+ kind,
188
+ launched,
189
+ rosetta: rosetta ? 'yes' : 'no',
190
+ agent_version: agentVersion(agent),
191
+ };
192
+ // The server knows a fixed set of units; the window is part of the prompt gate's compliance.
193
+ // "started" first: a window process the agent stopped never reports more, and that shows.
194
+ const report = { unit: 'compliance', command: 'agent_access_window', metadata };
195
+ await reportFunctionalRun({ ...report, outcome: 'started', message: `${agent}: window process started (${launched})` });
119
196
  const words = wordsPath ? readWords(wordsPath) : syncedWindow(agent);
120
- const result = showWindow(words ?? undefined);
197
+ const { result, error } = drawWindow(words ?? undefined, rosetta);
121
198
  if (result)
122
199
  actOn(result);
200
+ await reportFunctionalRun({
201
+ ...report,
202
+ outcome: result ? 'ok' : 'failed',
203
+ message: (result ? `${agent}: shown, closed with ${result.action}` : `${agent}: not shown: ${error}`).slice(0, 500),
204
+ detail: error,
205
+ });
206
+ endLaunchdJob();
123
207
  }
124
208
  const isEntryPoint = process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1];
125
209
  if (isEntryPoint) {
126
- const [agent, wordsPath] = process.argv.slice(2);
127
- if (agent && AGENTS.includes(agent))
128
- runAgentAccessWindow(agent, wordsPath);
210
+ const [agent, kind, wordsPath] = process.argv.slice(2);
211
+ if (agent && AGENTS.includes(agent) && KINDS.includes(kind)) {
212
+ void runAgentAccessWindow(agent, kind, wordsPath);
213
+ }
129
214
  }
@@ -0,0 +1,93 @@
1
+ Copyright 2019 The Manrope Project Authors (https://github.com/sharanda/manrope)
2
+
3
+ This Font Software is licensed under the SIL Open Font License, Version 1.1.
4
+ This license is copied below, and is also available with a FAQ at:
5
+ http://scripts.sil.org/OFL
6
+
7
+
8
+ -----------------------------------------------------------
9
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10
+ -----------------------------------------------------------
11
+
12
+ PREAMBLE
13
+ The goals of the Open Font License (OFL) are to stimulate worldwide
14
+ development of collaborative font projects, to support the font creation
15
+ efforts of academic and linguistic communities, and to provide a free and
16
+ open framework in which fonts may be shared and improved in partnership
17
+ with others.
18
+
19
+ The OFL allows the licensed fonts to be used, studied, modified and
20
+ redistributed freely as long as they are not sold by themselves. The
21
+ fonts, including any derivative works, can be bundled, embedded,
22
+ redistributed and/or sold with any software provided that any reserved
23
+ names are not used by derivative works. The fonts and derivatives,
24
+ however, cannot be released under any other type of license. The
25
+ requirement for fonts to remain under this license does not apply
26
+ to any document created using the fonts or their derivatives.
27
+
28
+ DEFINITIONS
29
+ "Font Software" refers to the set of files released by the Copyright
30
+ Holder(s) under this license and clearly marked as such. This may
31
+ include source files, build scripts and documentation.
32
+
33
+ "Reserved Font Name" refers to any names specified as such after the
34
+ copyright statement(s).
35
+
36
+ "Original Version" refers to the collection of Font Software components as
37
+ distributed by the Copyright Holder(s).
38
+
39
+ "Modified Version" refers to any derivative made by adding to, deleting,
40
+ or substituting -- in part or in whole -- any of the components of the
41
+ Original Version, by changing formats or by porting the Font Software to a
42
+ new environment.
43
+
44
+ "Author" refers to any designer, engineer, programmer, technical
45
+ writer or other person who contributed to the Font Software.
46
+
47
+ PERMISSION & CONDITIONS
48
+ Permission is hereby granted, free of charge, to any person obtaining
49
+ a copy of the Font Software, to use, study, copy, merge, embed, modify,
50
+ redistribute, and sell modified and unmodified copies of the Font
51
+ Software, subject to the following conditions:
52
+
53
+ 1) Neither the Font Software nor any of its individual components,
54
+ in Original or Modified Versions, may be sold by itself.
55
+
56
+ 2) Original or Modified Versions of the Font Software may be bundled,
57
+ redistributed and/or sold with any software, provided that each copy
58
+ contains the above copyright notice and this license. These can be
59
+ included either as stand-alone text files, human-readable headers or
60
+ in the appropriate machine-readable metadata fields within text or
61
+ binary files as long as those fields can be easily viewed by the user.
62
+
63
+ 3) No Modified Version of the Font Software may use the Reserved Font
64
+ Name(s) unless explicit written permission is granted by the corresponding
65
+ Copyright Holder. This restriction only applies to the primary font name as
66
+ presented to the users.
67
+
68
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69
+ Software shall not be used to promote, endorse or advertise any
70
+ Modified Version, except to acknowledge the contribution(s) of the
71
+ Copyright Holder(s) and the Author(s) or with their explicit written
72
+ permission.
73
+
74
+ 5) The Font Software, modified or unmodified, in part or in whole,
75
+ must be distributed entirely under this license, and must not be
76
+ distributed under any other license. The requirement for fonts to
77
+ remain under this license does not apply to any document created
78
+ using the Font Software.
79
+
80
+ TERMINATION
81
+ This license becomes null and void if any of the above conditions are
82
+ not met.
83
+
84
+ DISCLAIMER
85
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93
+ OTHER DEALINGS IN THE FONT SOFTWARE.
@@ -334,7 +334,8 @@ function blockedByLogin(agent, ide) {
334
334
  // The blocked-agent window opens beside every block, in the background, so the hook answers at
335
335
  // once: an agent's own from the synced approvals, a login's from the words built here. Where it
336
336
  // could not start, Cursor's hook keeps its alert.
337
- const windowShown = launchAgentAccessWindow(approvalId(agent), block.window);
337
+ // A login block (tenant, email domain) brings its own words; the agent's own comes from the sync.
338
+ const windowShown = launchAgentAccessWindow(approvalId(agent), block.window ? 'login' : 'agent', block.window);
338
339
  console.log(agent === 'codex' ? codexBlockPayload(block.reason) : blockPayload(ide, block.reason, windowShown));
339
340
  return true;
340
341
  }
@@ -175,7 +175,7 @@ function openWindowOf(uuid) {
175
175
  if (!words)
176
176
  return false;
177
177
  windowsShown.add(uuid);
178
- return launchAgentAccessWindow('claude', words);
178
+ return launchAgentAccessWindow('claude', 'skill', words);
179
179
  }
180
180
  /** Open the window beside the block of the skill named `skill` (a stopped session, a typed command). */
181
181
  export function openSkillBlockWindow(skill) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optimuslabs/harness-map-staging",
3
- "version": "1.5.40",
3
+ "version": "1.5.42",
4
4
  "description": "CLI helpers for logging hardware UUIDs and posting startup payloads to Optimus Security.",
5
5
  "type": "module",
6
6
  "bin": {