synabun 2.2.0 → 2.2.1
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 +18 -0
- package/README.md +1 -1
- package/mcp-server/package-lock.json +2 -2
- package/mcp-server/package.json +1 -1
- package/neural-interface/i18n/en.json +3 -0
- package/neural-interface/i18n/pt-BR.json +3 -0
- package/neural-interface/lib/assistant-brains/codex.js +6 -1
- package/neural-interface/lib/assistant-dispatch.js +4 -1
- package/neural-interface/lib/mcp-external-servers.js +27 -3
- package/neural-interface/lib/native-loop-providers.js +3 -2
- package/neural-interface/lib/opencode-native-boundary.js +155 -21
- package/neural-interface/lib/peer-identity.js +49 -5
- package/neural-interface/package-lock.json +2 -2
- package/neural-interface/package.json +1 -1
- package/neural-interface/public/shared/assistant/asst-api.js +14 -2
- package/neural-interface/public/shared/assistant/asst-computer.js +26 -2
- package/neural-interface/public/shared/assistant/asst-motion-gate.js +80 -0
- package/neural-interface/public/shared/assistant/asst-panel.js +1 -1
- package/neural-interface/public/shared/assistant/asst-render.js +62 -7
- package/neural-interface/public/shared/assistant/asst-scene-styles.js +55 -23
- package/neural-interface/public/shared/assistant/asst-scenes.js +225 -79
- package/neural-interface/public/shared/assistant/asst-sidepanel-styles.js +4 -4
- package/neural-interface/public/shared/assistant/asst-style-cache.js +44 -0
- package/neural-interface/public/shared/assistant/asst-styles.js +24 -4
- package/neural-interface/public/shared/cp/cp-permissions.js +12 -0
- package/neural-interface/public/shared/loading-teardown.js +34 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +6 -1
- package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +1 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +10 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +6 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +88 -72
- package/neural-interface/public/shared/storage-retry.js +47 -0
- package/neural-interface/public/shared/storage.js +25 -4
- package/neural-interface/public/shared/styles.css +8 -1
- package/neural-interface/public/shared/synabun-mascot.js +27 -8
- package/neural-interface/public/shared/synabun-ticker.js +147 -12
- package/neural-interface/public/shared/ui-claude-panel.js +79 -61
- package/neural-interface/public/shared/ui-loading.js +5 -4
- package/neural-interface/server.js +87 -40
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readStoredToolListRecord, toolListRecordForUpdate } from './lib/tool-lists.js';
|
|
2
|
-
import { externalServerInventory, selectExternalServers, externalCatalog, EXTERNAL_SERVER_LIMITATIONS, scopeOpenCodeServers, retainExternalRefs, writePrivateMcpConfig, redactMcpValues,
|
|
2
|
+
import { externalServerInventory, selectExternalServers, externalCatalog, EXTERNAL_SERVER_LIMITATIONS, scopeOpenCodeServers, retainExternalRefs, writePrivateMcpConfig, redactMcpValues, mcpSecretValues } from './lib/mcp-external-servers.js';
|
|
3
3
|
import { openCodeWorkerData } from './lib/opencode-worker-data.js';
|
|
4
4
|
import { resumeSandboxPlan } from './lib/worker-resume.js';
|
|
5
5
|
import { waitForSynabunTools, synabunConfigured, invalidateSynabunTools, synabunToolsTimeoutText } from './lib/opencode-mcp-ready.js';
|
|
@@ -25,7 +25,7 @@ import { SANDBOX_EXEC, buildWorkerSandboxProfile, canonicalPath, sandboxExecArgv
|
|
|
25
25
|
import { createWorkerMcpChannel } from './lib/worker-mcp-channel.js';
|
|
26
26
|
import { createPageSession, PAGE_SECRET_PATHS, PAGE_SECRET_REFRESH_PATH, SERVICE_WORKER_PATH } from './lib/page-session.js';
|
|
27
27
|
import { normalizeViewUrl } from './public/shared/view-variant.js';
|
|
28
|
-
import { createPeerIdentity } from './lib/peer-identity.js';
|
|
28
|
+
import { createPeerIdentity, debugClientPolicy } from './lib/peer-identity.js';
|
|
29
29
|
import { createOwnerAuthority } from './lib/owner-authority.js';
|
|
30
30
|
import { collectRelayListenerPorts, localProxyPorts } from './lib/worker-relay-ports.js';
|
|
31
31
|
import { machineNames } from './lib/machine-names.js';
|
|
@@ -205,7 +205,7 @@ import {
|
|
|
205
205
|
} from './lib/native-loop-providers.js';
|
|
206
206
|
import { createOpenCodeActivityRelay } from './lib/opencode-worker-activity.js';
|
|
207
207
|
import { browserServePortDenial } from './lib/browser-tool-policy.js';
|
|
208
|
-
import { createNativeServeBoundary, nativeServeCapability } from './lib/opencode-native-boundary.js';
|
|
208
|
+
import { createNativeServeBoundary, nativeServeCapability, nativeServeReason, narrowOffenders } from './lib/opencode-native-boundary.js';
|
|
209
209
|
const nativeServeBoundary = createNativeServeBoundary();
|
|
210
210
|
import { setBrowserNetworkRule, usesNativeBrowserNetworkGuard, disposeBrowserNetworkGuard, recoverBrowserNetworkGuard, MONEY_PATTERNS, SERVE_PORT_PATTERNS } from './lib/browser-network-guard.js';
|
|
211
211
|
import { browserPolicyRoots } from './lib/browser-policy-roots.js';
|
|
@@ -447,6 +447,9 @@ try {
|
|
|
447
447
|
// allowed until a restart. What the environment itself gives is noted first and put back after the load; the
|
|
448
448
|
// file's own line is read when a request asks (publicWebOrigins).
|
|
449
449
|
const startedAllowedHosts = startedValueOf(process.env);
|
|
450
|
+
// The profiling exemption (lib/peer-identity.js debugClientPolicy) is read from the started environment only, before
|
|
451
|
+
// the data home's .env can add to it.
|
|
452
|
+
const startedDebugClient = debugClientPolicy(process.env);
|
|
450
453
|
dotenvConfig({ path: resolve(getDataHome(), '.env') });
|
|
451
454
|
startedAllowedHosts.restore();
|
|
452
455
|
|
|
@@ -556,7 +559,7 @@ app.use(requestGates.features);
|
|
|
556
559
|
// in front of every route (the page secret's pieces are made further down and read only when a request comes).
|
|
557
560
|
// This install's root (PACKAGE_ROOT, defined further down): SynaBun's own MCP server over HTTP is the one listening
|
|
558
561
|
// program the lookup knows, by its main script under it (mcp-server/dist/http.js, `npm run start:http`).
|
|
559
|
-
const _peerIdentity = createPeerIdentity({ log: (line) => console.warn(line), disabled: String(process.env.SYNABUN_PEER_IDENTITY || '').toLowerCase() === 'off', synabunRoots: [resolve(__dirname, '..')] });
|
|
562
|
+
const _peerIdentity = createPeerIdentity({ log: (line) => console.warn(line), disabled: String(process.env.SYNABUN_PEER_IDENTITY || '').toLowerCase() === 'off', synabunRoots: [resolve(__dirname, '..')], debugClient: startedDebugClient });
|
|
560
563
|
// The one decision for the owner's routes and sockets (lib/owner-authority.js): every write to /api but the hooks'
|
|
561
564
|
// and the agents' feature data, the two reads that hand out a secret, and the owner's sockets are refused to the
|
|
562
565
|
// automation browser (its marker or its process), relays, other SynaBun servers, remote logins and an agent's tree
|
|
@@ -3755,22 +3758,25 @@ function opencodeServeLaunch(port, { env = process.env, password = null, passwor
|
|
|
3755
3758
|
_noteCliLookup('opencode', found, 'OpenCode');
|
|
3756
3759
|
const line = configuredCommandLine('opencode');
|
|
3757
3760
|
const nativeWebSocketSafe = nativeServeCapability({ version: found.version, commandLine: !!line, passwordFrom });
|
|
3758
|
-
|
|
3761
|
+
// What the browser boundary names when this serve is the one that blocks a native (MoreLogin) context. A command
|
|
3762
|
+
// line from Settings runs whatever it names: the resolver's version is of another binary, so none is reported.
|
|
3763
|
+
const nativeServe = { version: line ? null : (found.version || null), path: line || found.launchPath || found.path || null, reason: nativeServeReason({ version: found.version, commandLine: !!line, passwordFrom }) };
|
|
3764
|
+
nativeServeBoundary.assertLaunch(nativeWebSocketSafe, { port, ...nativeServe });
|
|
3759
3765
|
const spec = serveSpecFor(port, { env, password, passwordFrom });
|
|
3760
3766
|
const words = spec.args.join(' ');
|
|
3761
3767
|
if (IS_WIN) {
|
|
3762
3768
|
const spawnEnv = withPathEnv(spec.env, getAugmentedPath());
|
|
3763
3769
|
if (!line && found.spec) {
|
|
3764
3770
|
const launch = spawnArgsFor(found.spec, spec.args, { env: spawnEnv });
|
|
3765
|
-
return { command: launch.command, args: launch.args, options: { ...launch.options, env: spawnEnv, windowsHide: true }, label: `${found.launchPath} ${words}`, password: spec.password, passwordFrom: spec.passwordFrom, nativeWebSocketSafe };
|
|
3771
|
+
return { command: launch.command, args: launch.args, options: { ...launch.options, env: spawnEnv, windowsHide: true }, label: `${found.launchPath} ${words}`, password: spec.password, passwordFrom: spec.passwordFrom, nativeWebSocketSafe, nativeServe };
|
|
3766
3772
|
}
|
|
3767
3773
|
const text = `${line || 'opencode'} ${words}`;
|
|
3768
|
-
return { command: DEFAULT_SHELL, args: ['/d', '/s', '/c', text], options: { env: spawnEnv, windowsHide: true }, label: text, password: spec.password, passwordFrom: spec.passwordFrom, nativeWebSocketSafe };
|
|
3774
|
+
return { command: DEFAULT_SHELL, args: ['/d', '/s', '/c', text], options: { env: spawnEnv, windowsHide: true }, label: text, password: spec.password, passwordFrom: spec.passwordFrom, nativeWebSocketSafe, nativeServe };
|
|
3769
3775
|
}
|
|
3770
3776
|
const cmd = line || (found.path ? shellSingleQuote(found.path) : 'opencode');
|
|
3771
3777
|
const unset = spec.passwordFrom === 'stdout' ? `${serveUnsetCommand()}; ` : '';
|
|
3772
3778
|
// A packaged application's runtime and npm after the user's own (the login shell adds the user's profile on top).
|
|
3773
|
-
return { command: DEFAULT_SHELL, args: ['-lc', `${unset}exec ${cmd} ${words}`], options: { env: withPackagedPathLast(spec.env) }, label: `${cmd} ${words}`, password: spec.password, passwordFrom: spec.passwordFrom, nativeWebSocketSafe };
|
|
3779
|
+
return { command: DEFAULT_SHELL, args: ['-lc', `${unset}exec ${cmd} ${words}`], options: { env: withPackagedPathLast(spec.env) }, label: `${cmd} ${words}`, password: spec.password, passwordFrom: spec.passwordFrom, nativeWebSocketSafe, nativeServe };
|
|
3774
3780
|
}
|
|
3775
3781
|
|
|
3776
3782
|
// Which OpenCode the binary SynaBun starts is: the resolver's `--version` of it
|
|
@@ -8261,8 +8267,8 @@ function noteOpencodeServePort(port, delta) {
|
|
|
8261
8267
|
if (next > 0) _opencodeServePortCounts.set(p, next); else _opencodeServePortCounts.delete(p);
|
|
8262
8268
|
}
|
|
8263
8269
|
|
|
8264
|
-
function trackOpencodeServe(proc, { kind, id = null, port, token, nativeWebSocketSafe = false }) {
|
|
8265
|
-
nativeServeBoundary.track(proc, { port, safe: nativeWebSocketSafe });
|
|
8270
|
+
function trackOpencodeServe(proc, { kind, id = null, port, token, nativeWebSocketSafe = false, nativeServe = null }) {
|
|
8271
|
+
nativeServeBoundary.track(proc, { ...(nativeServe || {}), port, safe: nativeWebSocketSafe });
|
|
8266
8272
|
const pid = Number(proc?.pid) || null;
|
|
8267
8273
|
const exited = () => !proc || proc.exitCode != null || proc.signalCode != null;
|
|
8268
8274
|
if (!exited()) {
|
|
@@ -8788,7 +8794,7 @@ async function ensureIsolatedServe(termId, { xdgRoot: requestedXdgRoot = null, c
|
|
|
8788
8794
|
entry.proc = proc;
|
|
8789
8795
|
entry.password = launch.password || null;
|
|
8790
8796
|
// On record from its spawn: a crash of SynaBun from here on does not leave it running for good.
|
|
8791
|
-
const tracked = trackOpencodeServe(proc, { kind: 'isolated', id: termId, port, token, nativeWebSocketSafe: launch.nativeWebSocketSafe });
|
|
8797
|
+
const tracked = trackOpencodeServe(proc, { kind: 'isolated', id: termId, port, token, nativeWebSocketSafe: launch.nativeWebSocketSafe, nativeServe: launch.nativeServe });
|
|
8792
8798
|
// While it boots, its start sees an exit itself (another server on the port is a collision); afterwards
|
|
8793
8799
|
// its exit ends the serve.
|
|
8794
8800
|
let booting = true;
|
|
@@ -9658,7 +9664,7 @@ async function ensureOpencodeServer({ allowDuringHistoryClear = false } = {}) {
|
|
|
9658
9664
|
});
|
|
9659
9665
|
_ocpProc = proc;
|
|
9660
9666
|
// On record from its spawn: a crash of SynaBun from here on does not leave it running for good.
|
|
9661
|
-
tracked = trackOpencodeServe(proc, { kind: 'shared', id: 'shared', port: _ocpPort, token, nativeWebSocketSafe: launch.nativeWebSocketSafe });
|
|
9667
|
+
tracked = trackOpencodeServe(proc, { kind: 'shared', id: 'shared', port: _ocpPort, token, nativeWebSocketSafe: launch.nativeWebSocketSafe, nativeServe: launch.nativeServe });
|
|
9662
9668
|
const readPassword = servePasswordReader();
|
|
9663
9669
|
const outLog = createServeLogRedactor(line => console.log(`[opencode:stdout] ${line.trim()}`));
|
|
9664
9670
|
const errLog = createServeLogRedactor(line => console.log(`[opencode:stderr] ${line.trim()}`));
|
|
@@ -14609,7 +14615,7 @@ app.post('/api/loop/launch', async (req, res) => {
|
|
|
14609
14615
|
const acq = await acquireLoopBrowserAndTab({ requestedBrowserSessionId, terminalSessionId });
|
|
14610
14616
|
if (acq.error) {
|
|
14611
14617
|
loopLog(terminalSessionId, 'launch:browser', 'ABORT: browser acquisition failed — returning 500', { error: acq.error });
|
|
14612
|
-
return res.status(500).json({ error: acq.error });
|
|
14618
|
+
return res.status(500).json({ error: acq.error, ...(acq.code ? { code: acq.code, offenders: acq.offenders } : {}) });
|
|
14613
14619
|
}
|
|
14614
14620
|
browserSessionId = acq.browserSessionId;
|
|
14615
14621
|
browserTabId = acq.browserTabId;
|
|
@@ -14935,7 +14941,7 @@ app.post('/api/loop/recover-browser', async (req, res) => {
|
|
|
14935
14941
|
const terminalSessionId = req.body?.terminalSessionId || randomBytes(16).toString('hex');
|
|
14936
14942
|
const acq = await acquireLoopBrowserAndTab({ terminalSessionId, logTag: 'recover:browser' });
|
|
14937
14943
|
if (acq.error) {
|
|
14938
|
-
return res.status(500).json({ error: acq.error });
|
|
14944
|
+
return res.status(500).json({ error: acq.error, ...(acq.code ? { code: acq.code, offenders: acq.offenders } : {}) });
|
|
14939
14945
|
}
|
|
14940
14946
|
// Persist the new pins onto the loop's state file when we can identify it,
|
|
14941
14947
|
// so resolve-from-ancestors and loop/stop operate on the live session/tab.
|
|
@@ -16934,7 +16940,7 @@ function spawnAgentProcess(agent, prompt, extraEnv = {}) {
|
|
|
16934
16940
|
const sessionId = agent._activeSessionId || agent.sessionId;
|
|
16935
16941
|
|
|
16936
16942
|
const serverMap = JSON.parse(agent._mcpConfig).mcpServers;
|
|
16937
|
-
const mcpValues =
|
|
16943
|
+
const mcpValues = mcpSecretValues(serverMap, { env: process.env });
|
|
16938
16944
|
const mcpConfigPath = writePrivateMcpConfig(resolve(DATA_HOME, 'data', 'agents', agent.id, 'mcp.json'), serverMap);
|
|
16939
16945
|
const args = [
|
|
16940
16946
|
'--print',
|
|
@@ -17415,7 +17421,7 @@ app.post('/api/agents/launch', async (req, res) => {
|
|
|
17415
17421
|
const acq = await acquireSharedBrowserTab({ ownerKey: `agent:${agentId}`, ownerType: 'interactive', logTag: 'agent:browser' });
|
|
17416
17422
|
if (acq.error) {
|
|
17417
17423
|
console.warn(`[agent] Browser tab acquire failed: ${acq.error}`);
|
|
17418
|
-
return res.status(500).json({ error: `Could not start browser session for this automation: ${acq.error}
|
|
17424
|
+
return res.status(500).json({ error: `Could not start browser session for this automation: ${acq.error}`, ...(acq.code ? { code: acq.code, offenders: acq.offenders } : {}) });
|
|
17419
17425
|
}
|
|
17420
17426
|
browserSessionId = acq.browserSessionId;
|
|
17421
17427
|
browserTabId = acq.browserTabId;
|
|
@@ -18668,7 +18674,7 @@ const editorCompletionServe = createOpencodeCompletionEngine({
|
|
|
18668
18674
|
const token = opencodeServeRecords.newToken();
|
|
18669
18675
|
const launch = opencodeServeLaunch(port, { env: { ...env, [SERVE_TOKEN_ENV]: token }, passwordFrom: 'stdout' });
|
|
18670
18676
|
const proc = spawn(launch.command, launch.args, { stdio: ['ignore', 'pipe', 'pipe'], ...launch.options, cwd, detached: false, shell: false, windowsHide: true });
|
|
18671
|
-
editorCompletionServeTracks.set(proc, trackOpencodeServe(proc, { kind: 'completion', id: 'editor-complete', port, token, nativeWebSocketSafe: launch.nativeWebSocketSafe }));
|
|
18677
|
+
editorCompletionServeTracks.set(proc, trackOpencodeServe(proc, { kind: 'completion', id: 'editor-complete', port, token, nativeWebSocketSafe: launch.nativeWebSocketSafe, nativeServe: launch.nativeServe }));
|
|
18672
18678
|
const outLog = createServeLogRedactor(line => console.log(`[editor-complete:out] ${line.trim().slice(0, 500)}`));
|
|
18673
18679
|
const errLog = createServeLogRedactor(line => console.log(`[editor-complete:err] ${line.trim().slice(0, 500)}`));
|
|
18674
18680
|
proc.stdout?.on('data', d => outLog.push(d));
|
|
@@ -25209,7 +25215,7 @@ function externalToolsCatalog(cwd = null) {
|
|
|
25209
25215
|
function applyOpenCodeExternalScope(xdgRoot, config, tools, cwd, { readProject = true } = {}) {
|
|
25210
25216
|
const scoped = scopeOpenCodeServers(config, tools, externalInventory('opencode', cwd || PACKAGE_ROOT, { readProject }));
|
|
25211
25217
|
Object.assign(config, scoped.config);
|
|
25212
|
-
_externalMcpLogValues.set(xdgRoot,
|
|
25218
|
+
_externalMcpLogValues.set(xdgRoot, mcpSecretValues(scoped.config.mcp.servers));
|
|
25213
25219
|
writeFileSync(resolve(xdgRoot, 'synabun-external-scope.json'), JSON.stringify(scoped.scope), { mode: 0o600 });
|
|
25214
25220
|
return scoped.scope;
|
|
25215
25221
|
}
|
|
@@ -30991,7 +30997,7 @@ async function acquireSharedBrowserTab({ requestedBrowserSessionId = null, owner
|
|
|
30991
30997
|
profileMode: result.profileMode, profileSource: result.profileSource,
|
|
30992
30998
|
});
|
|
30993
30999
|
} catch (err) {
|
|
30994
|
-
createError = err
|
|
31000
|
+
createError = browserStartFailure(err);
|
|
30995
31001
|
loopLog(ownerKey, logTag, 'strategy 2 FAILED: createBrowserSession threw', { err: err.message, stack: err.stack?.slice(0, 600) });
|
|
30996
31002
|
}
|
|
30997
31003
|
}
|
|
@@ -31001,8 +31007,9 @@ async function acquireSharedBrowserTab({ requestedBrowserSessionId = null, owner
|
|
|
31001
31007
|
// run's log, lastRunResult and the schedule:failed toast.
|
|
31002
31008
|
return {
|
|
31003
31009
|
error: createError
|
|
31004
|
-
? `Could not start the shared browser session: ${createError}`
|
|
31010
|
+
? `Could not start the shared browser session: ${createError.error}`
|
|
31005
31011
|
: 'Could not start the shared browser session. Open Browser Settings and verify the selected profile can launch.',
|
|
31012
|
+
...(createError?.code ? { code: createError.code, offenders: createError.offenders } : {}),
|
|
31006
31013
|
};
|
|
31007
31014
|
}
|
|
31008
31015
|
|
|
@@ -32167,34 +32174,72 @@ async function installMoneyGuardRoute(ctx) {
|
|
|
32167
32174
|
// still aborted. Managed contexts also block WS through Playwright. MoreLogin
|
|
32168
32175
|
// retains native WS only behind the verified server authentication boundary and
|
|
32169
32176
|
// credentials confined to this server, never an environment, page or log.
|
|
32170
|
-
|
|
32171
|
-
|
|
32172
|
-
|
|
32173
|
-
|
|
32174
|
-
|
|
32175
|
-
|
|
32177
|
+
// Records of serves this server did not start that may still run: each one could answer a page's WebSocket.
|
|
32178
|
+
function unverifiedServeRecords() {
|
|
32179
|
+
return opencodeServeRecords.list().filter(record =>
|
|
32180
|
+
!opencodeServeRecords.ownedHere(record) && opencodeServeRecords.verifySync(record) !== 'gone')
|
|
32181
|
+
.map(record => ({ ...record, claimable: (() => { try { return opencodeServeRecords.claimable(record); } catch { return false; } })() }));
|
|
32182
|
+
}
|
|
32183
|
+
const NATIVE_SERVE_WAIT_MS = Number(process.env.SYNABUN_NATIVE_SERVE_WAIT_MS) || 20_000;
|
|
32184
|
+
const NATIVE_SERVE_SWEEP_MS = Number(process.env.SYNABUN_NATIVE_SERVE_SWEEP_MS) || 10_000;
|
|
32185
|
+
// Left of a request's deadline for the answer itself (the MCP client gives a session create 70 s).
|
|
32186
|
+
const NATIVE_SERVE_REPLY_MARGIN_MS = 5_000;
|
|
32187
|
+
// Before refusing, repair what SynaBun can itself, bounded: stop the serves an ended SynaBun left running (the records'
|
|
32188
|
+
// own sweep, which signals only a provably-SynaBun process whose owner has ended; at most NATIVE_SERVE_SWEEP_MS, in the
|
|
32189
|
+
// background while the rest is read) and wait for SynaBun's own verified serves still starting (at most
|
|
32190
|
+
// NATIVE_SERVE_WAIT_MS), both inside the request's deadline. The context is held by the boundary from before the
|
|
32191
|
+
// first look (nativeServeBoundary.attach), so no unverified serve starts meanwhile. Nothing is cached: every attempt
|
|
32192
|
+
// reads the live state again. Authentication is never relaxed: what is left is named.
|
|
32193
|
+
async function protectNativeServeContext(ctx, { deadline = Infinity, signal = null } = {}) {
|
|
32194
|
+
try {
|
|
32195
|
+
return await nativeServeBoundary.attach(ctx, {
|
|
32196
|
+
state: () => ({ ports: [...liveOpencodeServePorts()], unverifiedRecords: unverifiedServeRecords() }),
|
|
32197
|
+
sweep: () => opencodeServeRecords.sweep(),
|
|
32198
|
+
waitMs: NATIVE_SERVE_WAIT_MS, sweepMs: NATIVE_SERVE_SWEEP_MS,
|
|
32199
|
+
deadline: Number.isFinite(deadline) ? deadline - NATIVE_SERVE_REPLY_MARGIN_MS : Infinity, signal,
|
|
32200
|
+
});
|
|
32201
|
+
} catch (error) {
|
|
32202
|
+
if (error?.offenders) console.warn(`[browser] native serve boundary refused: ${JSON.stringify(error.offenders)}`);
|
|
32203
|
+
throw error;
|
|
32204
|
+
}
|
|
32205
|
+
}
|
|
32206
|
+
|
|
32207
|
+
// The answer a failed browser start gives its caller: the sentence, and for the native serve boundary its code and
|
|
32208
|
+
// the serves it names (narrowed: never a token or a whole record).
|
|
32209
|
+
function browserStartFailure(err) {
|
|
32210
|
+
const out = { error: err?.message || String(err) };
|
|
32211
|
+
if (err?.code === 'NATIVE_SERVE_UNVERIFIED') { out.code = err.code; out.offenders = narrowOffenders(err.offenders); }
|
|
32212
|
+
return out;
|
|
32176
32213
|
}
|
|
32177
32214
|
|
|
32178
|
-
async function installServePortGuard(ctx) {
|
|
32215
|
+
async function installServePortGuard(ctx, { deadline = Infinity, signal = null } = {}) {
|
|
32179
32216
|
if (!ctx || ctx.__servePortGuardInstalled) return;
|
|
32180
32217
|
// routeWebSocket installs page bindings and replaces WebSocket on navigation,
|
|
32181
32218
|
// even with an empty matcher. Native contexts rely on the verified server's
|
|
32182
32219
|
// authentication; never silently route or drop protection for unknown serves.
|
|
32183
32220
|
const native = usesNativeBrowserNetworkGuard(ctx);
|
|
32184
|
-
const release = native ? protectNativeServeContext(ctx) : null;
|
|
32185
32221
|
const refused = (url) => { try { return !!browserServePortDenial(url?.href ?? url, liveOpencodeServePorts()); } catch { return false; } };
|
|
32222
|
+
const installRule = () => setBrowserNetworkRule(ctx, 'serve-port', {
|
|
32223
|
+
patterns: SERVE_PORT_PATTERNS,
|
|
32224
|
+
decide: request => refused(request.url) ? { abort: true } : null,
|
|
32225
|
+
});
|
|
32226
|
+
if (native) {
|
|
32227
|
+
// Held first (assertLaunch refuses an unverified start from here on), then the HTTP rule, then the bounded check.
|
|
32228
|
+
const release = nativeServeBoundary.reserve(ctx);
|
|
32229
|
+
try { await installRule(); } catch (error) {
|
|
32230
|
+
release();
|
|
32231
|
+
throw new Error(`Failed to install the OpenCode serve guard: ${error.message}`);
|
|
32232
|
+
}
|
|
32233
|
+
await protectNativeServeContext(ctx, { deadline, signal });
|
|
32234
|
+
ctx.__servePortGuardInstalled = true;
|
|
32235
|
+
return;
|
|
32236
|
+
}
|
|
32186
32237
|
try {
|
|
32187
|
-
await
|
|
32188
|
-
|
|
32189
|
-
|
|
32190
|
-
});
|
|
32191
|
-
if (!native) {
|
|
32192
|
-
if (typeof ctx.routeWebSocket !== 'function') throw new Error('WebSocket routing is unavailable');
|
|
32193
|
-
await ctx.routeWebSocket(refused, ws => Promise.resolve(ws.close({ code: 1008, reason: 'SynaBun: not an address the browser opens' })).catch(() => {}));
|
|
32194
|
-
}
|
|
32238
|
+
await installRule();
|
|
32239
|
+
if (typeof ctx.routeWebSocket !== 'function') throw new Error('WebSocket routing is unavailable');
|
|
32240
|
+
await ctx.routeWebSocket(refused, ws => Promise.resolve(ws.close({ code: 1008, reason: 'SynaBun: not an address the browser opens' })).catch(() => {}));
|
|
32195
32241
|
ctx.__servePortGuardInstalled = true;
|
|
32196
32242
|
} catch (error) {
|
|
32197
|
-
release?.();
|
|
32198
32243
|
throw new Error(`Failed to install the OpenCode serve guard: ${error.message}`);
|
|
32199
32244
|
}
|
|
32200
32245
|
}
|
|
@@ -32874,7 +32919,7 @@ async function createBrowserSession(options = {}) {
|
|
|
32874
32919
|
const startServeDenial = browserServePortDenial(startUrl, liveOpencodeServePorts());
|
|
32875
32920
|
if (startServeDenial) throw new Error(startServeDenial);
|
|
32876
32921
|
await installMoneyGuardRoute(context);
|
|
32877
|
-
await installServePortGuard(context);
|
|
32922
|
+
await installServePortGuard(context, { deadline: options.browserRequest?.deadline, signal: options.browserRequest?.signal });
|
|
32878
32923
|
if (typeof _pageSession !== 'undefined') {
|
|
32879
32924
|
const marked = await _pageSession.markWorkerBrowserContext(context, { port: PORT, hostNames: machineNames.names() });
|
|
32880
32925
|
if (!marked) throw new Error('Cannot protect browser owner authority: local worker marker failed');
|
|
@@ -33989,6 +34034,8 @@ app.post('/api/browser/sessions', async (req, res) => {
|
|
|
33989
34034
|
};
|
|
33990
34035
|
const result = await createBrowserSession({
|
|
33991
34036
|
url, width, height, screenWidth, screenHeight, deviceScaleFactor, timezone, _realHeaders,
|
|
34037
|
+
// Its deadline bounds the native serve check (installServePortGuard) inside the caller's timeout.
|
|
34038
|
+
browserRequest: req.browserRequest,
|
|
33992
34039
|
// Optional per-session override to target a specific MoreLogin env (multi-identity)
|
|
33993
34040
|
...(connectMode === 'morelogin' ? { connectMode } : {}),
|
|
33994
34041
|
...(moreloginEnvId ? { moreloginEnvId: String(moreloginEnvId) } : {}),
|
|
@@ -34015,7 +34062,7 @@ app.post('/api/browser/sessions', async (req, res) => {
|
|
|
34015
34062
|
});
|
|
34016
34063
|
} catch (err) {
|
|
34017
34064
|
console.error('Browser session create error:', err.message);
|
|
34018
|
-
res.status(500).json(
|
|
34065
|
+
res.status(500).json(browserStartFailure(err));
|
|
34019
34066
|
}
|
|
34020
34067
|
});
|
|
34021
34068
|
|
|
@@ -34040,7 +34087,7 @@ app.post('/api/browser/acquire', async (req, res) => {
|
|
|
34040
34087
|
const clientId = req.body?.clientId;
|
|
34041
34088
|
if (!clientId) return res.status(400).json({ error: 'clientId is required' });
|
|
34042
34089
|
const acq = await acquireSharedBrowserTab({ ownerKey: clientId, ownerType: 'interactive', logTag: 'acquire:interactive' });
|
|
34043
|
-
if (acq.error) return res.status(500).json({ error: acq.error });
|
|
34090
|
+
if (acq.error) return res.status(500).json({ error: acq.error, ...(acq.code ? { code: acq.code, offenders: acq.offenders } : {}) });
|
|
34044
34091
|
res.json({ ok: true, sessionId: acq.browserSessionId, tabId: acq.browserTabId || null });
|
|
34045
34092
|
} catch (err) {
|
|
34046
34093
|
res.status(500).json({ error: err.message });
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "synabun",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "synabun",
|
|
9
|
-
"version": "2.2.
|
|
9
|
+
"version": "2.2.1",
|
|
10
10
|
"hasInstallScript": true,
|
|
11
11
|
"license": "Apache-2.0",
|
|
12
12
|
"dependencies": {
|
package/package.json
CHANGED