@miller-tech/uap 1.164.0 → 1.165.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/dist/.tsbuildinfo +1 -1
- package/dist/browser/web-browser.d.ts +7 -0
- package/dist/browser/web-browser.d.ts.map +1 -1
- package/dist/browser/web-browser.js +15 -0
- package/dist/browser/web-browser.js.map +1 -1
- package/dist/cli/deliver.d.ts +27 -0
- package/dist/cli/deliver.d.ts.map +1 -1
- package/dist/cli/deliver.js +103 -12
- package/dist/cli/deliver.js.map +1 -1
- package/dist/cli/verify.d.ts +6 -0
- package/dist/cli/verify.d.ts.map +1 -1
- package/dist/cli/verify.js +64 -0
- package/dist/cli/verify.js.map +1 -1
- package/dist/delivery/agentic-executor.d.ts +15 -0
- package/dist/delivery/agentic-executor.d.ts.map +1 -1
- package/dist/delivery/agentic-executor.js +64 -3
- package/dist/delivery/agentic-executor.js.map +1 -1
- package/dist/delivery/convergence-loop.d.ts +39 -1
- package/dist/delivery/convergence-loop.d.ts.map +1 -1
- package/dist/delivery/convergence-loop.js +22 -4
- package/dist/delivery/convergence-loop.js.map +1 -1
- package/dist/delivery/execution-gate.d.ts +4 -0
- package/dist/delivery/execution-gate.d.ts.map +1 -1
- package/dist/delivery/execution-gate.js +194 -2
- package/dist/delivery/execution-gate.js.map +1 -1
- package/dist/delivery/mission-acceptance.d.ts.map +1 -1
- package/dist/delivery/mission-acceptance.js +69 -21
- package/dist/delivery/mission-acceptance.js.map +1 -1
- package/dist/delivery/user-paths.d.ts +42 -0
- package/dist/delivery/user-paths.d.ts.map +1 -1
- package/dist/delivery/user-paths.js +254 -6
- package/dist/delivery/user-paths.js.map +1 -1
- package/dist/delivery/user-validation.d.ts.map +1 -1
- package/dist/delivery/user-validation.js +21 -1
- package/dist/delivery/user-validation.js.map +1 -1
- package/dist/delivery/vision-judge.d.ts.map +1 -1
- package/dist/delivery/vision-judge.js +8 -1
- package/dist/delivery/vision-judge.js.map +1 -1
- package/dist/models/openai-compat-client.d.ts +26 -0
- package/dist/models/openai-compat-client.d.ts.map +1 -1
- package/dist/models/openai-compat-client.js +60 -8
- package/dist/models/openai-compat-client.js.map +1 -1
- package/docs/guides/DROIDS_AND_SKILLS.md +2 -1
- package/package.json +1 -1
- package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
- package/src/policies/enforcers/iac_plan_destruction_check.py +32 -4
- package/templates/hooks/__pycache__/deliver_autoroute.cpython-312.pyc +0 -0
- package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
|
@@ -14,6 +14,32 @@ export interface OpenAICompatClientOptions {
|
|
|
14
14
|
/** Abort timeout in ms (default: UAP_MODEL_HTTP_TIMEOUT_MS, 30 min) */
|
|
15
15
|
timeoutMs?: number;
|
|
16
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Is this endpoint on the local machine or a private network?
|
|
19
|
+
*
|
|
20
|
+
* Exported so every credential-bearing caller shares ONE definition. The
|
|
21
|
+
* PROXY_AUTH_TOKEN fallback means a request can now carry a secret even when the
|
|
22
|
+
* model preset declares no apiKeyEnvVar, so any code path that attaches an
|
|
23
|
+
* Authorization header has to make the same local-only judgement — duplicating
|
|
24
|
+
* the regexes is how the two drift apart and one of them starts leaking.
|
|
25
|
+
*/
|
|
26
|
+
export declare function isLocalEndpoint(url: URL): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* The credential to send, or undefined.
|
|
29
|
+
*
|
|
30
|
+
* PROXY_AUTH_TOKEN is a secret for ONE local process — the anthropic proxy. There
|
|
31
|
+
* is no arrangement in which handing it to a third party is correct, so it is
|
|
32
|
+
* scoped to local endpoints by construction rather than merely being refused over
|
|
33
|
+
* cleartext: an operator pointing UAP at a hosted OpenAI-compatible provider over
|
|
34
|
+
* https would otherwise have shipped their proxy token (and with it, use of their
|
|
35
|
+
* Anthropic passthrough) to that vendor in a header the vendor logs.
|
|
36
|
+
*
|
|
37
|
+
* An explicitly-named provider key keeps the older, looser rule — it belongs to
|
|
38
|
+
* the endpoint it is being sent to — but still may not travel in cleartext.
|
|
39
|
+
*/
|
|
40
|
+
export declare function resolveRequestCredential(model: {
|
|
41
|
+
apiKeyEnvVar?: string;
|
|
42
|
+
}, url: URL): string | undefined;
|
|
17
43
|
export declare class OpenAICompatClient implements ModelClient {
|
|
18
44
|
private readonly defaultEndpoint;
|
|
19
45
|
private readonly timeoutMs;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openai-compat-client.d.ts","sourceRoot":"","sources":["../../src/models/openai-compat-client.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD,MAAM,WAAW,yBAAyB;IACxC,oHAAoH;IACpH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,uEAAuE;IACvE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;
|
|
1
|
+
{"version":3,"file":"openai-compat-client.d.ts","sourceRoot":"","sources":["../../src/models/openai-compat-client.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD,MAAM,WAAW,yBAAyB;IACxC,oHAAoH;IACpH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,uEAAuE;IACvE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA6BD;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAEjD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE;IAAE,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,EAChC,GAAG,EAAE,GAAG,GACP,MAAM,GAAG,SAAS,CAYpB;AASD,qBAAa,kBAAmB,YAAW,WAAW;IACpD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEvB,OAAO,GAAE,yBAA8B;IAQnD;;;;;;OAMG;IACG,QAAQ,CACZ,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,OAAO,CAAA;KAAE,GAC/F,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;YAgBnF,QAAQ;CAyEvB"}
|
|
@@ -9,9 +9,62 @@
|
|
|
9
9
|
import { fetchModelWithRetry, modelHttpTimeoutMs } from './long-fetch.js';
|
|
10
10
|
const DEFAULT_TIMEOUT_MS = modelHttpTimeoutMs();
|
|
11
11
|
const LOOPBACK_RE = /^(localhost|127\.\d+\.\d+\.\d+|\[?::1\]?)$/i;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
// MUST be a full IPv4 literal, not a prefix. The old pattern was unanchored and
|
|
13
|
+
// shape-free, so any DNS name beginning with those digits — `10.evil.com`,
|
|
14
|
+
// `192.168.attacker.net` — was classified as private and the local-only
|
|
15
|
+
// credential rule waved it through to a public host in cleartext. RFC 1123
|
|
16
|
+
// permits labels to start with digits, so this is registerable, not theoretical.
|
|
17
|
+
const IPV4_RE = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
|
|
18
|
+
function isPrivateIpv4(hostname) {
|
|
19
|
+
const m = IPV4_RE.exec(hostname);
|
|
20
|
+
if (!m)
|
|
21
|
+
return false;
|
|
22
|
+
const [a, b] = [Number(m[1]), Number(m[2])];
|
|
23
|
+
if ([a, b, Number(m[3]), Number(m[4])].some((n) => n > 255))
|
|
24
|
+
return false;
|
|
25
|
+
if (a === 10)
|
|
26
|
+
return true;
|
|
27
|
+
if (a === 192 && b === 168)
|
|
28
|
+
return true;
|
|
29
|
+
if (a === 172 && b >= 16 && b <= 31)
|
|
30
|
+
return true;
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Is this endpoint on the local machine or a private network?
|
|
35
|
+
*
|
|
36
|
+
* Exported so every credential-bearing caller shares ONE definition. The
|
|
37
|
+
* PROXY_AUTH_TOKEN fallback means a request can now carry a secret even when the
|
|
38
|
+
* model preset declares no apiKeyEnvVar, so any code path that attaches an
|
|
39
|
+
* Authorization header has to make the same local-only judgement — duplicating
|
|
40
|
+
* the regexes is how the two drift apart and one of them starts leaking.
|
|
41
|
+
*/
|
|
42
|
+
export function isLocalEndpoint(url) {
|
|
43
|
+
return LOOPBACK_RE.test(url.hostname) || isPrivateIpv4(url.hostname);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The credential to send, or undefined.
|
|
47
|
+
*
|
|
48
|
+
* PROXY_AUTH_TOKEN is a secret for ONE local process — the anthropic proxy. There
|
|
49
|
+
* is no arrangement in which handing it to a third party is correct, so it is
|
|
50
|
+
* scoped to local endpoints by construction rather than merely being refused over
|
|
51
|
+
* cleartext: an operator pointing UAP at a hosted OpenAI-compatible provider over
|
|
52
|
+
* https would otherwise have shipped their proxy token (and with it, use of their
|
|
53
|
+
* Anthropic passthrough) to that vendor in a header the vendor logs.
|
|
54
|
+
*
|
|
55
|
+
* An explicitly-named provider key keeps the older, looser rule — it belongs to
|
|
56
|
+
* the endpoint it is being sent to — but still may not travel in cleartext.
|
|
57
|
+
*/
|
|
58
|
+
export function resolveRequestCredential(model, url) {
|
|
59
|
+
const explicit = model.apiKeyEnvVar ? process.env[model.apiKeyEnvVar] : undefined;
|
|
60
|
+
if (explicit) {
|
|
61
|
+
if (url.protocol !== 'https:' && !isLocalEndpoint(url)) {
|
|
62
|
+
throw new Error(`Refusing to send ${model.apiKeyEnvVar} over ${url.protocol}// to non-local host ${url.hostname} — use https.`);
|
|
63
|
+
}
|
|
64
|
+
return explicit;
|
|
65
|
+
}
|
|
66
|
+
// The local-proxy fallback: local endpoints only, regardless of scheme.
|
|
67
|
+
return isLocalEndpoint(url) ? process.env.PROXY_AUTH_TOKEN || undefined : undefined;
|
|
15
68
|
}
|
|
16
69
|
import { withModelSlot, recordModelSuccess, recordModelExhaustion, isExhaustionError, } from '../utils/model-slot-lease.js';
|
|
17
70
|
export class OpenAICompatClient {
|
|
@@ -50,13 +103,12 @@ export class OpenAICompatClient {
|
|
|
50
103
|
}
|
|
51
104
|
async _request(model, prompt, options) {
|
|
52
105
|
const endpoint = (model.endpoint ?? this.defaultEndpoint).replace(/\/$/, '');
|
|
53
|
-
const apiKey = model.apiKeyEnvVar ? process.env[model.apiKeyEnvVar] : undefined;
|
|
54
106
|
const timeout = options?.timeout ?? this.timeoutMs;
|
|
55
107
|
const url = new URL(`${endpoint}/chat/completions`);
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
108
|
+
// One shared rule for what may be sent where (throws on a cleartext leak of
|
|
109
|
+
// an explicit provider key; silently withholds the local proxy token from any
|
|
110
|
+
// non-local endpoint).
|
|
111
|
+
const apiKey = resolveRequestCredential(model, url);
|
|
60
112
|
const start = Date.now();
|
|
61
113
|
const controller = new AbortController();
|
|
62
114
|
const timer = setTimeout(() => controller.abort(), timeout);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openai-compat-client.js","sourceRoot":"","sources":["../../src/models/openai-compat-client.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAiB1E,MAAM,kBAAkB,GAAG,kBAAkB,EAAE,CAAC;AAEhD,MAAM,WAAW,GAAG,6CAA6C,CAAC;AAClE,MAAM,
|
|
1
|
+
{"version":3,"file":"openai-compat-client.js","sourceRoot":"","sources":["../../src/models/openai-compat-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -156,7 +156,7 @@ A skill is a reusable workflow. Skills live in directories under
|
|
|
156
156
|
[`.factory/skills/`](../../.factory/skills/), each containing a `SKILL.md` file
|
|
157
157
|
with frontmatter (`name`, `version`, `compatibility`) and the workflow body.
|
|
158
158
|
|
|
159
|
-
UAP ships **
|
|
159
|
+
UAP ships **37 skills** in `.factory/skills/`, including:
|
|
160
160
|
|
|
161
161
|
- **Coordination & workflow** — `uap-coordination`, `uap-tasks`,
|
|
162
162
|
`uap-worktree`, `worktree-workflow`, `parallel-expert-review`, `batch-review`
|
|
@@ -168,6 +168,7 @@ UAP ships **36 skills** in `.factory/skills/`, including:
|
|
|
168
168
|
`llama-cpp-worker`, `infra-worker`, `service-config`
|
|
169
169
|
- **Iteration & benchmarking** — `near-miss`, `near-miss-iteration`,
|
|
170
170
|
`adversarial`, `terminal-bench`, `terminal-bench-strategies`
|
|
171
|
+
- **Product & demos** — `record-walkthroughs`, `design-system`
|
|
171
172
|
- **Hooks** — `hooks-session-start`, `hooks-pre-compact`, `scripts-tool-router`
|
|
172
173
|
|
|
173
174
|
### `uap skill` CLI
|
package/package.json
CHANGED
|
Binary file
|
|
@@ -50,6 +50,28 @@ def branch_touches_iac() -> bool:
|
|
|
50
50
|
return rc == 0 and "infra/terraform/" in out
|
|
51
51
|
|
|
52
52
|
|
|
53
|
+
def repo_selector(cmd: str) -> list[str]:
|
|
54
|
+
"""The `--repo` args to reuse when looking up the PR, mirroring whatever
|
|
55
|
+
repo the merge command itself targets.
|
|
56
|
+
|
|
57
|
+
`gh pr merge <N> --repo owner/name` merges in THAT repo, but a bare
|
|
58
|
+
`gh pr view <N>` resolves against the local checkout. Without mirroring the
|
|
59
|
+
selector, a cross-repo merge looks the number up in the wrong repo; that
|
|
60
|
+
miss then falls back to branch_touches_iac(), which scans a local tree with
|
|
61
|
+
nothing to do with the PR — so an unrelated untracked infra/terraform tf
|
|
62
|
+
file in the current checkout blocks a merge that touches no infrastructure
|
|
63
|
+
at all. Observed blocking every cross-repo merge from pay2u.
|
|
64
|
+
"""
|
|
65
|
+
m = re.search(r"(?:--repo|-R)[=\s]+([\w.-]+/[\w.-]+)", cmd)
|
|
66
|
+
if m:
|
|
67
|
+
return ["--repo", m.group(1)]
|
|
68
|
+
# A `gh api` merge call carries owner/name in its path instead of a flag.
|
|
69
|
+
m = re.search(r"repos/([\w.-]+/[\w.-]+)/pulls/\d+/merge", cmd)
|
|
70
|
+
if m:
|
|
71
|
+
return ["--repo", m.group(1)]
|
|
72
|
+
return []
|
|
73
|
+
|
|
74
|
+
|
|
53
75
|
def merge_touches_iac(cmd: str) -> bool:
|
|
54
76
|
"""Decide whether a `gh pr merge`/`gh api .../merge` command touches IaC by
|
|
55
77
|
inspecting the FILES OF THE PR BEING MERGED — not the local working tree.
|
|
@@ -61,13 +83,16 @@ def merge_touches_iac(cmd: str) -> bool:
|
|
|
61
83
|
own file list instead, and only fall back to the local check when no PR
|
|
62
84
|
number can be parsed or the lookup fails.
|
|
63
85
|
"""
|
|
64
|
-
m = re.search(r"gh\s+pr\s+merge\s+(\d+)", cmd) or re.search(
|
|
86
|
+
m = re.search(r"gh\s+pr\s+merge\s+(\d+)", cmd, re.I) or re.search(
|
|
87
|
+
r"/pulls/(\d+)/merge", cmd
|
|
88
|
+
)
|
|
65
89
|
if not m:
|
|
66
90
|
# e.g. `gh pr merge` on the current branch with no number — use local diff.
|
|
67
91
|
return branch_touches_iac()
|
|
68
92
|
pr = m.group(1)
|
|
69
93
|
rc, out, _ = run(
|
|
70
|
-
["gh", "pr", "view", pr, "--json", "files", "-q", ".files[].path"],
|
|
94
|
+
["gh", "pr", "view", pr, *repo_selector(cmd), "--json", "files", "-q", ".files[].path"],
|
|
95
|
+
timeout=20,
|
|
71
96
|
)
|
|
72
97
|
if rc != 0:
|
|
73
98
|
# Lookup failed (offline / auth) — fail safe by keeping the gate on.
|
|
@@ -91,7 +116,10 @@ def main() -> None:
|
|
|
91
116
|
op, args = parse_cli()
|
|
92
117
|
if op not in ("Bash", "bash"):
|
|
93
118
|
emit(True, "not a bash op")
|
|
94
|
-
|
|
119
|
+
raw_cmd = args.get("command") or ""
|
|
120
|
+
# Markers are matched case-insensitively, but repo_selector() needs the
|
|
121
|
+
# original case of an owner/name argument — so keep both forms.
|
|
122
|
+
cmd = raw_cmd.lower()
|
|
95
123
|
|
|
96
124
|
is_tf_apply = any(m in cmd for m in APPLY_MARKERS)
|
|
97
125
|
is_wf_apply = any(m in cmd for m in WORKFLOW_APPLY) and "iac-terraform" in cmd and "apply" in cmd
|
|
@@ -104,7 +132,7 @@ def main() -> None:
|
|
|
104
132
|
# Merges/pushes only gated when IaC is actually touched. For `gh pr merge`
|
|
105
133
|
# inspect the PR's own files; for local pushes use the working-tree diff.
|
|
106
134
|
if (is_merge or is_push_main) and not (is_tf_apply or is_wf_apply):
|
|
107
|
-
touches = merge_touches_iac(
|
|
135
|
+
touches = merge_touches_iac(raw_cmd) if is_merge else branch_touches_iac()
|
|
108
136
|
if not touches:
|
|
109
137
|
emit(True, "merge/push does not touch infra/terraform")
|
|
110
138
|
|
|
Binary file
|