@miller-tech/uap 1.119.3 → 1.119.4
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/.tsbuildinfo +1 -1
- package/dist/cli/sandbox.d.ts +8 -0
- package/dist/cli/sandbox.d.ts.map +1 -1
- package/dist/cli/sandbox.js +25 -1
- package/dist/cli/sandbox.js.map +1 -1
- package/package.json +1 -1
- package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
- package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
- package/tools/agents/scripts/anthropic_proxy.py +66 -2
- package/tools/agents/tests/test_sandbox_tool_strip.py +36 -0
package/dist/cli/sandbox.d.ts
CHANGED
|
@@ -14,5 +14,13 @@
|
|
|
14
14
|
* UAP_SANDBOX_ALLOW extra writable paths, colon-separated
|
|
15
15
|
* UAP_SANDBOX_OFF=1 bypass the sandbox entirely (run the command as-is)
|
|
16
16
|
*/
|
|
17
|
+
/**
|
|
18
|
+
* Build the ANTHROPIC_CUSTOM_HEADERS value for a sandboxed session: append the
|
|
19
|
+
* `X-Uap-Sandbox: 1` marker (which the proxy uses to strip sandbox-unreachable
|
|
20
|
+
* MCP tools) to any pre-existing headers rather than clobbering them. Claude
|
|
21
|
+
* Code forwards this env var verbatim to its endpoint as HTTP headers
|
|
22
|
+
* (newline-separated "Name: Value" pairs).
|
|
23
|
+
*/
|
|
24
|
+
export declare function sandboxCustomHeaders(existing: string | undefined): string;
|
|
17
25
|
export declare function sandboxCommand(command: string[]): Promise<void>;
|
|
18
26
|
//# sourceMappingURL=sandbox.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../../src/cli/sandbox.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;
|
|
1
|
+
{"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../../src/cli/sandbox.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAOH;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CASzE;AAYD,wBAAsB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuErE"}
|
package/dist/cli/sandbox.js
CHANGED
|
@@ -18,6 +18,24 @@ import { spawnSync } from 'child_process';
|
|
|
18
18
|
import { existsSync, realpathSync } from 'fs';
|
|
19
19
|
import { homedir } from 'os';
|
|
20
20
|
import { join } from 'path';
|
|
21
|
+
/**
|
|
22
|
+
* Build the ANTHROPIC_CUSTOM_HEADERS value for a sandboxed session: append the
|
|
23
|
+
* `X-Uap-Sandbox: 1` marker (which the proxy uses to strip sandbox-unreachable
|
|
24
|
+
* MCP tools) to any pre-existing headers rather than clobbering them. Claude
|
|
25
|
+
* Code forwards this env var verbatim to its endpoint as HTTP headers
|
|
26
|
+
* (newline-separated "Name: Value" pairs).
|
|
27
|
+
*/
|
|
28
|
+
export function sandboxCustomHeaders(existing) {
|
|
29
|
+
const marker = 'X-Uap-Sandbox: 1';
|
|
30
|
+
const prior = existing?.trim();
|
|
31
|
+
if (!prior)
|
|
32
|
+
return marker;
|
|
33
|
+
// Idempotent: don't duplicate the marker (nested sandbox, or user pre-set it).
|
|
34
|
+
const already = prior
|
|
35
|
+
.split('\n')
|
|
36
|
+
.some((h) => h.trim().toLowerCase() === marker.toLowerCase());
|
|
37
|
+
return already ? prior : `${prior}\n${marker}`;
|
|
38
|
+
}
|
|
21
39
|
function fail(msg, code = 2) {
|
|
22
40
|
console.error(`uap sandbox: ${msg}`);
|
|
23
41
|
process.exit(code);
|
|
@@ -78,7 +96,13 @@ export async function sandboxCommand(command) {
|
|
|
78
96
|
if (p && existsSync(p))
|
|
79
97
|
args.push('--bind', p, p);
|
|
80
98
|
}
|
|
81
|
-
|
|
99
|
+
// Tell the proxy this session is sandboxed so it can strip MCP tools the
|
|
100
|
+
// bubblewrap sandbox can't reach (the claude-in-chrome browser extension
|
|
101
|
+
// socket is unreachable here — offering browser_batch guarantees dead-end
|
|
102
|
+
// loops). Claude Code forwards ANTHROPIC_CUSTOM_HEADERS verbatim to its
|
|
103
|
+
// endpoint; append rather than clobber any pre-existing value.
|
|
104
|
+
const customHeaders = sandboxCustomHeaders(process.env.ANTHROPIC_CUSTOM_HEADERS);
|
|
105
|
+
args.push('--setenv', 'HOME', home, '--setenv', 'UAP_SANDBOX_ACTIVE', '1', '--setenv', 'ANTHROPIC_CUSTOM_HEADERS', customHeaders, '--chdir', workdir);
|
|
82
106
|
const r = spawnSync('bwrap', [...args, ...command], { stdio: 'inherit' });
|
|
83
107
|
if (r.error)
|
|
84
108
|
fail(`failed to launch bwrap: ${r.error.message}`, 127);
|
package/dist/cli/sandbox.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sandbox.js","sourceRoot":"","sources":["../../src/cli/sandbox.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sandbox.js","sourceRoot":"","sources":["../../src/cli/sandbox.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,QAA4B;IAC/D,MAAM,MAAM,GAAG,kBAAkB,CAAC;IAClC,MAAM,KAAK,GAAG,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC/B,IAAI,CAAC,KAAK;QAAE,OAAO,MAAM,CAAC;IAC1B,+EAA+E;IAC/E,MAAM,OAAO,GAAG,KAAK;SAClB,KAAK,CAAC,IAAI,CAAC;SACX,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC;IAChE,OAAO,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,KAAK,MAAM,EAAE,CAAC;AACjD,CAAC;AAED,SAAS,IAAI,CAAC,GAAW,EAAE,IAAI,GAAG,CAAC;IACjC,OAAO,CAAC,KAAK,CAAC,gBAAgB,GAAG,EAAE,CAAC,CAAC;IACrC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACrB,CAAC;AAED,SAAS,QAAQ;IACf,MAAM,CAAC,GAAG,SAAS,CAAC,OAAO,EAAE,CAAC,WAAW,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;IACjE,OAAO,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC;AACpC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,OAAiB;IACpD,iEAAiE;IACjE,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,IAAI;QAAE,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAEpD,IAAI,OAAO,CAAC,GAAG,CAAC,eAAe,KAAK,GAAG,EAAE,CAAC;QACxC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,IAAI,CAAC,kBAAkB,CAAC,CAAC;QACnD,MAAM,CAAC,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;QACxE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAChD,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,IAAI,CAAC,6DAA6D,CAAC,CAAC;IACtE,CAAC;IACD,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC;QAChB,IAAI,CACF,8DAA8D;YAC5D,2DAA2D,EAC7D,GAAG,CACJ,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,OAAO,EAAE,CAAC;IAC3C,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAC3E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAC7D,CAAC;IAED,8EAA8E;IAC9E,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,KAAK,GAAG,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;QAC/D,IAAI,CAAC,qCAAqC,OAAO,eAAe,CAAC,CAAC;IACpE,CAAC;IAED,wCAAwC;IACxC,MAAM,IAAI,GAAa;QACrB,WAAW,EAAE,GAAG,EAAE,GAAG;QACrB,OAAO,EAAE,MAAM;QACf,QAAQ,EAAE,OAAO;QACjB,SAAS,EAAE,MAAM;QACjB,mBAAmB;QACnB,eAAe;QACf,eAAe;QACf,kBAAkB;QAClB,QAAQ,EAAE,OAAO,EAAE,OAAO;QAC1B,QAAQ,EAAE,MAAM,EAAE,MAAM;QACxB,SAAS,EAAE,UAAU;KACtB,CAAC;IACF,KAAK,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC;QAChD,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAC1B,IAAI,UAAU,CAAC,CAAC,CAAC;YAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAC/C,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QACjE,IAAI,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC;YAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IACpD,CAAC;IACD,yEAAyE;IACzE,yEAAyE;IACzE,0EAA0E;IAC1E,wEAAwE;IACxE,+DAA+D;IAC/D,MAAM,aAAa,GAAG,oBAAoB,CAAC,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;IACjF,IAAI,CAAC,IAAI,CACP,UAAU,EAAE,MAAM,EAAE,IAAI,EACxB,UAAU,EAAE,oBAAoB,EAAE,GAAG,EACrC,UAAU,EAAE,0BAA0B,EAAE,aAAa,EACrD,SAAS,EAAE,OAAO,CACnB,CAAC;IAEF,MAAM,CAAC,GAAG,SAAS,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,OAAO,CAAC,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;IAC1E,IAAI,CAAC,CAAC,KAAK;QAAE,IAAI,CAAC,2BAA2B,CAAC,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,GAAG,CAAC,CAAC;IACrE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;AAC9B,CAAC"}
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
@@ -1275,6 +1275,7 @@ class SessionMonitor:
|
|
|
1275
1275
|
and enable proactive context management before overflow occurs."""
|
|
1276
1276
|
|
|
1277
1277
|
context_window: int = 0 # Auto-detected or configured
|
|
1278
|
+
sandboxed: bool = False # True when the client is a `uap sandbox` (bwrap) session (X-Uap-Sandbox header)
|
|
1278
1279
|
total_requests: int = 0
|
|
1279
1280
|
last_input_tokens: int = 0 # Estimated input tokens of last request
|
|
1280
1281
|
last_output_tokens: int = 0 # Actual output tokens of last response
|
|
@@ -4366,6 +4367,46 @@ def _writes_are_gated(openai_body: dict) -> bool:
|
|
|
4366
4367
|
return False
|
|
4367
4368
|
|
|
4368
4369
|
|
|
4370
|
+
# MCP tool prefixes a bubblewrap sandbox (`uap sandbox`) cannot reach: the
|
|
4371
|
+
# claude-in-chrome browser extension talks over a socket that is not bound into
|
|
4372
|
+
# the sandbox mount namespace, so every browser_batch call dead-ends. Offering
|
|
4373
|
+
# these tools to a sandboxed session guarantees a fetch/tool-selection loop.
|
|
4374
|
+
# Env-configurable (colon-separated) so a deployment that DOES bind the browser
|
|
4375
|
+
# extension socket into the sandbox can disable stripping (set empty), or extend
|
|
4376
|
+
# it to other sandbox-unreachable MCP servers. Default: the claude-in-chrome
|
|
4377
|
+
# browser bridge, empirically unreachable in the bwrap namespace (observed live:
|
|
4378
|
+
# browser_batch dead-ended repeatedly and the model reported it unavailable).
|
|
4379
|
+
_SANDBOX_UNREACHABLE_TOOL_PREFIXES = tuple(
|
|
4380
|
+
p
|
|
4381
|
+
for p in os.environ.get(
|
|
4382
|
+
"PROXY_SANDBOX_UNREACHABLE_PREFIXES", "mcp__claude-in-chrome__"
|
|
4383
|
+
).split(":")
|
|
4384
|
+
if p
|
|
4385
|
+
)
|
|
4386
|
+
|
|
4387
|
+
|
|
4388
|
+
def _strip_sandbox_unreachable_tools(body: dict) -> int:
|
|
4389
|
+
"""Drop browser/extension MCP tools a bwrap sandbox can't reach from the
|
|
4390
|
+
Anthropic-format tool list in ``body['tools']``. Returns the count removed
|
|
4391
|
+
so the caller can log it. The model then falls back to WebFetch / local
|
|
4392
|
+
file reads instead of looping on an unreachable browser_batch."""
|
|
4393
|
+
tools = body.get("tools")
|
|
4394
|
+
if not isinstance(tools, list) or not _SANDBOX_UNREACHABLE_TOOL_PREFIXES:
|
|
4395
|
+
return 0
|
|
4396
|
+
kept = [
|
|
4397
|
+
t
|
|
4398
|
+
for t in tools
|
|
4399
|
+
if not (
|
|
4400
|
+
isinstance(t, dict)
|
|
4401
|
+
and str(t.get("name", "")).startswith(_SANDBOX_UNREACHABLE_TOOL_PREFIXES)
|
|
4402
|
+
)
|
|
4403
|
+
]
|
|
4404
|
+
removed = len(tools) - len(kept)
|
|
4405
|
+
if removed:
|
|
4406
|
+
body["tools"] = kept
|
|
4407
|
+
return removed
|
|
4408
|
+
|
|
4409
|
+
|
|
4369
4410
|
def _maybe_inject_stuck_break(openai_body: dict, monitor: "SessionMonitor") -> None:
|
|
4370
4411
|
"""Force a terminal turn when the model is looping self-awarely or hammering
|
|
4371
4412
|
a rate-limited API. Unlike the cycle-breaker (which narrows tools), this
|
|
@@ -4379,11 +4420,18 @@ def _maybe_inject_stuck_break(openai_body: dict, monitor: "SessionMonitor") -> N
|
|
|
4379
4420
|
# Release the tool-choice coercion so a plain text turn is allowed.
|
|
4380
4421
|
if openai_body.get("tool_choice") == "required":
|
|
4381
4422
|
openai_body["tool_choice"] = "auto"
|
|
4423
|
+
# Sandboxed sessions can't reach the browser extension, so don't steer them
|
|
4424
|
+
# to it (that is the very dead-end this guard is trying to break).
|
|
4425
|
+
channel_hint = (
|
|
4426
|
+
"use `git clone` (git protocol) or WebFetch"
|
|
4427
|
+
if monitor.sandboxed
|
|
4428
|
+
else "use the browser tool or `git clone` (git protocol)"
|
|
4429
|
+
)
|
|
4382
4430
|
directive = (
|
|
4383
4431
|
"\n\nSTOP — you are repeating a failing action (" + reason + "). Do NOT "
|
|
4384
4432
|
"retry the same tool or fetch again. If a resource is unreachable (e.g. a "
|
|
4385
|
-
"rate-limited GitHub REST API), switch channel:
|
|
4386
|
-
"
|
|
4433
|
+
"rate-limited GitHub REST API), switch channel: " + channel_hint + ", NOT "
|
|
4434
|
+
"api.github.com. If it is still "
|
|
4387
4435
|
"unavailable, proceed WITHOUT it using what you already have, or ask the "
|
|
4388
4436
|
"operator the single blocking question in one sentence. Take a DIFFERENT "
|
|
4389
4437
|
"action now."
|
|
@@ -9278,6 +9326,21 @@ async def messages(request: Request):
|
|
|
9278
9326
|
if request.headers.get("x-uap-json-response"):
|
|
9279
9327
|
body["_uap_json_response"] = True
|
|
9280
9328
|
|
|
9329
|
+
# Sandboxed sessions (`uap sandbox` / bwrap) set X-Uap-Sandbox:1 via
|
|
9330
|
+
# ANTHROPIC_CUSTOM_HEADERS. The sandbox can't reach the claude-in-chrome
|
|
9331
|
+
# browser extension, so strip its MCP tools here (before passthrough, so it
|
|
9332
|
+
# applies to local AND cloud sessions) — the model then uses WebFetch or
|
|
9333
|
+
# local file reads instead of looping on an unreachable browser_batch.
|
|
9334
|
+
sandboxed = (request.headers.get("x-uap-sandbox") or "").strip() == "1"
|
|
9335
|
+
if sandboxed:
|
|
9336
|
+
_stripped = _strip_sandbox_unreachable_tools(body)
|
|
9337
|
+
if _stripped:
|
|
9338
|
+
logger.warning(
|
|
9339
|
+
"SANDBOX: stripped %d unreachable browser MCP tool(s) "
|
|
9340
|
+
"(bwrap cannot reach the claude-in-chrome extension)",
|
|
9341
|
+
_stripped,
|
|
9342
|
+
)
|
|
9343
|
+
|
|
9281
9344
|
# Periodically re-detect context window from upstream (handles server restarts)
|
|
9282
9345
|
await _maybe_recheck_context_window()
|
|
9283
9346
|
|
|
@@ -9286,6 +9349,7 @@ async def messages(request: Request):
|
|
|
9286
9349
|
return await _passthrough_anthropic_request(request, body, is_stream)
|
|
9287
9350
|
session_id = resolve_session_id(request, body)
|
|
9288
9351
|
monitor = get_session_monitor(session_id)
|
|
9352
|
+
monitor.sandboxed = sandboxed
|
|
9289
9353
|
# Per-turn flag: only the turn whose breaker strips tools suppresses the
|
|
9290
9354
|
# response-side prose->tool_call resurrection. Clear it at request entry so a
|
|
9291
9355
|
# prior finalize turn never bleeds into the next turn's normal extraction.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Tests for sandbox browser-tool stripping (bwrap can't reach the extension)."""
|
|
2
|
+
import importlib.util
|
|
3
|
+
import unittest
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
proxy_path = Path(__file__).resolve().parents[3] / "tools" / "agents" / "scripts" / "anthropic_proxy.py"
|
|
7
|
+
spec = importlib.util.spec_from_file_location("anthropic_proxy", proxy_path)
|
|
8
|
+
ap = importlib.util.module_from_spec(spec)
|
|
9
|
+
spec.loader.exec_module(ap)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class StripSandboxToolsTest(unittest.TestCase):
|
|
13
|
+
def test_strips_browser_mcp_tools_keeps_the_rest(self):
|
|
14
|
+
body = {"tools": [
|
|
15
|
+
{"name": "Bash"},
|
|
16
|
+
{"name": "mcp__claude-in-chrome__browser_batch"},
|
|
17
|
+
{"name": "mcp__claude-in-chrome__navigate"},
|
|
18
|
+
{"name": "WebFetch"},
|
|
19
|
+
]}
|
|
20
|
+
removed = ap._strip_sandbox_unreachable_tools(body)
|
|
21
|
+
self.assertEqual(removed, 2)
|
|
22
|
+
self.assertEqual([t["name"] for t in body["tools"]], ["Bash", "WebFetch"])
|
|
23
|
+
|
|
24
|
+
def test_keeps_all_when_no_browser_tools(self):
|
|
25
|
+
body = {"tools": [{"name": "Bash"}, {"name": "WebFetch"}]}
|
|
26
|
+
self.assertEqual(ap._strip_sandbox_unreachable_tools(body), 0)
|
|
27
|
+
self.assertEqual(len(body["tools"]), 2)
|
|
28
|
+
|
|
29
|
+
def test_missing_or_null_tools_is_safe(self):
|
|
30
|
+
self.assertEqual(ap._strip_sandbox_unreachable_tools({}), 0)
|
|
31
|
+
self.assertEqual(ap._strip_sandbox_unreachable_tools({"tools": None}), 0)
|
|
32
|
+
self.assertEqual(ap._strip_sandbox_unreachable_tools({"tools": []}), 0)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
if __name__ == "__main__":
|
|
36
|
+
unittest.main()
|