@koda-sl/baker-bridge 0.124.0-dev.7e3937022 → 0.124.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/README.md +1 -2
- package/dist/broker/credential-cache.d.ts.map +1 -1
- package/dist/broker/credential-cache.js +8 -1
- package/dist/broker/credential-cache.js.map +1 -1
- package/dist/broker/credential-cache.test.js +16 -0
- package/dist/broker/credential-cache.test.js.map +1 -1
- package/dist/hono/avatar-accent.d.ts +5 -17
- package/dist/hono/avatar-accent.d.ts.map +1 -1
- package/dist/hono/avatar-accent.js +64 -31
- package/dist/hono/avatar-accent.js.map +1 -1
- package/dist/hono/avatar-accent.test.js +35 -46
- package/dist/hono/avatar-accent.test.js.map +1 -1
- package/dist/hono/avatar-face-question.d.ts +6 -23
- package/dist/hono/avatar-face-question.d.ts.map +1 -1
- package/dist/hono/avatar-face-question.js +59 -48
- package/dist/hono/avatar-face-question.js.map +1 -1
- package/dist/hono/avatar-face-question.test.js +101 -46
- package/dist/hono/avatar-face-question.test.js.map +1 -1
- package/dist/hono/bridge-job.d.ts +0 -4
- package/dist/hono/bridge-job.d.ts.map +1 -1
- package/dist/hono/bridge-job.js +6 -9
- package/dist/hono/bridge-job.js.map +1 -1
- package/dist/hono/bridge-job.test.js +0 -3
- package/dist/hono/bridge-job.test.js.map +1 -1
- package/dist/hono/credential-scrub.d.ts +2 -1
- package/dist/hono/credential-scrub.d.ts.map +1 -1
- package/dist/hono/credential-scrub.js +23 -2
- package/dist/hono/credential-scrub.js.map +1 -1
- package/dist/hono/credential-scrub.test.js +25 -0
- package/dist/hono/credential-scrub.test.js.map +1 -1
- package/dist/review/digest.d.ts +1 -8
- package/dist/review/digest.d.ts.map +1 -1
- package/dist/review/digest.js +43 -10
- package/dist/review/digest.js.map +1 -1
- package/dist/review/digest.test.js +6 -11
- package/dist/review/digest.test.js.map +1 -1
- package/package.json +1 -1
- package/dist/hono/jev.d.ts +0 -16
- package/dist/hono/jev.d.ts.map +0 -1
- package/dist/hono/jev.js +0 -60
- package/dist/hono/jev.js.map +0 -1
- package/dist/hono/jev.test.d.ts +0 -2
- package/dist/hono/jev.test.d.ts.map +0 -1
- package/dist/hono/jev.test.js +0 -15
- package/dist/hono/jev.test.js.map +0 -1
package/README.md
CHANGED
|
@@ -43,7 +43,6 @@ await server.start();
|
|
|
43
43
|
| `INTERNAL_SECRET_KEY` | AES key for encrypting flow `_data.json` config at rest |
|
|
44
44
|
| `ANTHROPIC_API_KEY` | Anthropic API key (required by the Agent SDK) |
|
|
45
45
|
| `ANTHROPIC_BASE_URL` / `ANTHROPIC_AUTH_TOKEN` | OpenRouter routing (base URL + per-org key) injected into the sandbox. Forwarded to the Agent SDK subprocess so the agent can reach its model |
|
|
46
|
-
| `OPENROUTER_API_KEY` | The same per-org key under its own name. The bridge reads the user's and the agent's words with it before letting two kinds of call through: a question asking the user to pick an avatar's face (the casting picker shows real portraits), and an avatar accent in a language nobody asked for. Unset or unreachable, both calls go through unchecked |
|
|
47
46
|
| `CLAUDE_CODE_EXTRA_BODY` | Optional JSON merged by the CLI into every model request body. The bridge sets/extends it per turn with a per-thread `session_id` so OpenRouter sticky-routes the whole conversation to one provider instance and its instance-scoped prompt cache is re-read across the turn's many tool-call requests (see [Prompt-cache pinning](#prompt-cache-pinning)). It also defaults `provider` to Bedrock first (see [Bedrock is the preferred provider](#bedrock-is-the-preferred-provider)). Any inherited value is merged; our `session_id` and the `web`-plugin opt-out win (see [OpenRouter web plugin is forced off](#openrouter-web-plugin-is-forced-off)). An inherited `provider` replaces the Bedrock default |
|
|
48
47
|
| `BAKER_CHAT_ID` | Present only when running inside a chat sandbox. When set, the bridge relocates Claude Code auto-memory into the cloned repo (`.claude/memory`) — see [Session journal](#session-journal-agent-memory) |
|
|
49
48
|
| `BAKER_ACTING_USER_ID` | Injected into the Agent SDK subprocess env per turn (when the turn's sender is known) so in-sandbox `baker` CLI calls can scope work to the acting user — e.g. `baker mcp add --scope user` |
|
|
@@ -71,7 +70,7 @@ A second bin, run as root in the sandbox before the agent starts, so the agent c
|
|
|
71
70
|
|
|
72
71
|
The agent's processes get `agentProxyEnv()`: `HTTP(S)_PROXY` pointing at the broker, `NO_PROXY` for loopback plus the model gateway, `NODE_USE_ENV_PROXY=1`, `NODE_EXTRA_CA_CERTS` set to the broker's CA, and `REQUESTS_CA_BUNDLE` / `SSL_CERT_FILE` / `CURL_CA_BUNDLE` set to the system bundle.
|
|
73
72
|
|
|
74
|
-
Security model: a value is only substituted over https, and only for a host the user allowed for that credential. A placeholder in the URL or a header that is not allowed for the host, or that names no stored credential, is refused (403 / 404, or 503 while credentials can't be fetched) before anything leaves the sandbox. A placeholder that appears only in the body is left as plain text unless it names a credential allowed for that host. Response headers and text bodies up to 10 MB are scrubbed back to placeholders; those responses arrive buffered rather than streamed. The socket never returns a value.
|
|
73
|
+
Security model: a value is only substituted over https, and only for a host the user allowed for that credential. A placeholder in the URL or a header that is not allowed for the host, or that names no stored credential, is refused (403 / 404, or 503 while credentials can't be fetched) before anything leaves the sandbox. A placeholder that appears only in the body is left as plain text unless it names a credential allowed for that host. Response headers and text bodies up to 10 MB are scrubbed back to placeholders; those responses arrive buffered rather than streamed. The socket never returns a value. Once one fetch has succeeded, `/scrub` answers from the last good table while a refresh is in flight; the bridge retries a failed `/scrub` three times (0.25 s, 1 s, 3 s) before withholding a tool result.
|
|
75
74
|
|
|
76
75
|
## Session surface
|
|
77
76
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credential-cache.d.ts","sourceRoot":"","sources":["../../src/broker/credential-cache.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"credential-cache.d.ts","sourceRoot":"","sources":["../../src/broker/credential-cache.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE/D,eAAO,MAAM,iBAAiB,QAAS,CAAC;AACxC,6GAA6G;AAC7G,eAAO,MAAM,uBAAuB,OAAQ,CAAC;AAE7C,MAAM,MAAM,gBAAgB,GAAG,MAAM,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAC;AAEnE,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,IAAI,OAAO,CAAC,kBAAkB,EAAE,GAAG,IAAI,CAAC,CAAC;IAC5C,+EAA+E;IAC/E,UAAU,IAAI,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAC;IAC5C;;;OAGG;IACH,OAAO,IAAI,OAAO,CAAC;IACnB,yEAAyE;IACzE,IAAI,IAAI,MAAM,CAAC;CAChB,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,OAAO,EAAE;IAC7C,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC,GAAG,eAAe,CA0DlB"}
|
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
* has succeeded there is no table (null), which callers must treat as
|
|
7
7
|
* "credentials unavailable", not "no credentials".
|
|
8
8
|
*
|
|
9
|
+
* A scrub uses the last good table while a refresh is in flight, so a slow
|
|
10
|
+
* Baker never holds a tool result back once one fetch has succeeded.
|
|
11
|
+
*
|
|
9
12
|
* A value that leaves the table — refused or removed — may still be on its
|
|
10
13
|
* way back in a response to a request that carried it, so `scrubbable()`
|
|
11
14
|
* keeps scrubbing it for the broker's lifetime. It is never sent again.
|
|
@@ -52,7 +55,11 @@ export function createCredentialCache(options) {
|
|
|
52
55
|
return {
|
|
53
56
|
get,
|
|
54
57
|
async scrubbable() {
|
|
55
|
-
|
|
58
|
+
const pending = get();
|
|
59
|
+
// A scrub never waits on a refresh it can do without: the last good table
|
|
60
|
+
// still covers every value the proxy could have sent.
|
|
61
|
+
const table = lastGood ?? (await pending);
|
|
62
|
+
return [...(table ?? []), ...retired.values()];
|
|
56
63
|
},
|
|
57
64
|
refresh() {
|
|
58
65
|
if (now() - lastRefreshAt < MIN_REFRESH_INTERVAL_MS)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credential-cache.js","sourceRoot":"","sources":["../../src/broker/credential-cache.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"credential-cache.js","sourceRoot":"","sources":["../../src/broker/credential-cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -92,6 +92,22 @@ describe("createCredentialCache", () => {
|
|
|
92
92
|
expect(afterRefusal.sort()).toEqual(["ads-secret-value", "shop-secret-value"]);
|
|
93
93
|
expect(await cache.get()).toBeNull();
|
|
94
94
|
});
|
|
95
|
+
it("scrubs with the last good table while a slow refresh is still in flight", async () => {
|
|
96
|
+
let clock = 0;
|
|
97
|
+
let hang = false;
|
|
98
|
+
const cache = createCredentialCache({
|
|
99
|
+
fetchCredentials: () => hang
|
|
100
|
+
? new Promise(() => {
|
|
101
|
+
// Baker never answers.
|
|
102
|
+
})
|
|
103
|
+
: Promise.resolve([{ name: "shop", value: "secret-value", allowedHosts: ["api.example.com"] }]),
|
|
104
|
+
now: () => clock,
|
|
105
|
+
});
|
|
106
|
+
await cache.get();
|
|
107
|
+
hang = true;
|
|
108
|
+
clock = 10_000;
|
|
109
|
+
expect((await cache.scrubbable()).map((credential) => credential.name)).toEqual(["shop"]);
|
|
110
|
+
});
|
|
95
111
|
it("fetches again on the next read after a refresh", async () => {
|
|
96
112
|
const source = fetcher();
|
|
97
113
|
const cache = createCredentialCache({ fetchCredentials: source.fetchCredentials, now: () => 0 });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credential-cache.test.js","sourceRoot":"","sources":["../../src/broker/credential-cache.test.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"credential-cache.test.js","sourceRoot":"","sources":["../../src/broker/credential-cache.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -13,23 +13,11 @@
|
|
|
13
13
|
* own description, in the creation hint and in the family doc by now, so this is the
|
|
14
14
|
* layer left.
|
|
15
15
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
16
|
+
* Narrow by construction — it only fires when the user is clearly writing Spanish AND
|
|
17
|
+
* the accent clearly names another language AND nothing the user said asks for that
|
|
18
|
+
* language or market. An ad a Spaniard genuinely wants in English says so, and then
|
|
19
|
+
* this stays out of the way.
|
|
20
20
|
*/
|
|
21
|
-
import type { Judge } from "./jev.ts";
|
|
22
|
-
/**
|
|
23
|
-
* Three judgments, each its own Noul in one request: an accent fits when the user writes in
|
|
24
|
-
* its language, or asked for it, or it names no language at all. Asked as one question, a
|
|
25
|
-
* face ("eher südländischer Typ") read as a request for Italian (0.79). Re-run
|
|
26
|
-
* `scripts/avatar-guard-evals` after changing any of them.
|
|
27
|
-
*/
|
|
28
|
-
export declare const ACCENT_WRITTEN_IN_QUESTION: string;
|
|
29
|
-
export declare const ACCENT_ASKED_FOR_QUESTION: string;
|
|
30
|
-
export declare const ACCENT_NAMES_LANGUAGE_QUESTION = "Does `accent` name a language, a country or a regional accent?";
|
|
31
|
-
/** Each answer is a yes at or above this. Measured in `scripts/avatar-guard-evals`. */
|
|
32
|
-
export declare const ACCENT_HELD = 0.5;
|
|
33
21
|
/** The `--accent` value in a `baker avatars create|update` command, if it has one. */
|
|
34
22
|
export declare function accentFrom(command: string): string | null;
|
|
35
23
|
/** The refusal, or null when the accent is consistent with what the user asked for. */
|
|
@@ -42,5 +30,5 @@ export declare function accentContradictsUser(command: string | undefined, userT
|
|
|
42
30
|
* the UK, in English" — and writes the next in Spanish had their own decision refused,
|
|
43
31
|
* with nothing they could type to get past it short of repeating themselves every time.
|
|
44
32
|
*/
|
|
45
|
-
priorUserText
|
|
33
|
+
priorUserText?: string): string | null;
|
|
46
34
|
//# sourceMappingURL=avatar-accent.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"avatar-accent.d.ts","sourceRoot":"","sources":["../../src/hono/avatar-accent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;
|
|
1
|
+
{"version":3,"file":"avatar-accent.d.ts","sourceRoot":"","sources":["../../src/hono/avatar-accent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAmCH,sFAAsF;AACtF,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAIzD;AAED,uFAAuF;AACvF,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,QAAQ,EAAE,MAAM,GAAG,SAAS;AAC5B;;;;;;;GAOG;AACH,aAAa,CAAC,EAAE,MAAM,GACrB,MAAM,GAAG,IAAI,CAkBf"}
|
|
@@ -1,19 +1,51 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Refuses an avatar cast to speak a language the user never asked for.
|
|
3
|
+
*
|
|
4
|
+
* `--accent` decides the LANGUAGE of every clip that avatar appears in, and the agent
|
|
5
|
+
* keeps reading it off the person's looks instead of off the conversation. Three runs in
|
|
6
|
+
* a row, each from the same Spanish sentence asking for "aspecto europeo" — a face:
|
|
7
|
+
*
|
|
8
|
+
* --accent "Dutch, neutral European accent"
|
|
9
|
+
* --accent "German, clear and authoritative"
|
|
10
|
+
* --accent "English from UK, Southern England accent"
|
|
11
|
+
*
|
|
12
|
+
* The first shipped: a Spanish client's ad, spoken in German. The rule is in the flag's
|
|
13
|
+
* own description, in the creation hint and in the family doc by now, so this is the
|
|
14
|
+
* layer left.
|
|
15
|
+
*
|
|
16
|
+
* Narrow by construction — it only fires when the user is clearly writing Spanish AND
|
|
17
|
+
* the accent clearly names another language AND nothing the user said asks for that
|
|
18
|
+
* language or market. An ad a Spaniard genuinely wants in English says so, and then
|
|
19
|
+
* this stays out of the way.
|
|
6
20
|
*/
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
21
|
+
/** Words that are common in Spanish and rare-to-absent in English/German/Dutch. */
|
|
22
|
+
const SPANISH_MARKERS = /\b(que|para|con|una?|los|las|del|por|pero|como|cuando|donde|porque|nuestro|nuestra|quiero|hacer|tiene|está|más|también|anuncio|vídeo|video|cliente|empresa)\b/gi;
|
|
23
|
+
/** Languages an accent can name, with the words that would justify asking for one. */
|
|
24
|
+
const FOREIGN_LANGUAGES = [
|
|
25
|
+
{
|
|
26
|
+
name: "English",
|
|
27
|
+
accent: /\b(english|british|american|uk|usa|australian|irish|scottish)\b/i,
|
|
28
|
+
justification: /\b(ingl[ée]s|inglesa|uk|reino unido|brit[áa]nic|estados unidos|usa|americano|irlanda|australia)\b/i,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: "German",
|
|
32
|
+
accent: /\b(german|austrian|deutsch)\b/i,
|
|
33
|
+
justification: /\b(alem[áa]n|alemana|alemania|austria)\b/i,
|
|
34
|
+
},
|
|
35
|
+
{ name: "French", accent: /\b(french|parisian)\b/i, justification: /\b(franc[ée]s|francesa|francia)\b/i },
|
|
36
|
+
{
|
|
37
|
+
name: "Dutch",
|
|
38
|
+
accent: /\b(dutch|netherlands|flemish)\b/i,
|
|
39
|
+
justification: /\b(holand[ée]s|neerland[ée]s|pa[íi]ses bajos|holanda|b[ée]lgica)\b/i,
|
|
40
|
+
},
|
|
41
|
+
{ name: "Italian", accent: /\b(italian)\b/i, justification: /\b(italiano|italiana|italia)\b/i },
|
|
42
|
+
{ name: "Portuguese", accent: /\b(portuguese|brazilian)\b/i, justification: /\b(portugu[ée]s|portugal|brasil)\b/i },
|
|
43
|
+
];
|
|
44
|
+
/** Enough Spanish function words that the user is plainly writing Spanish. */
|
|
45
|
+
const SPANISH_CONFIDENCE = 3;
|
|
46
|
+
function writesSpanish(text) {
|
|
47
|
+
return (text.match(SPANISH_MARKERS) ?? []).length >= SPANISH_CONFIDENCE;
|
|
48
|
+
}
|
|
17
49
|
/** The `--accent` value in a `baker avatars create|update` command, if it has one. */
|
|
18
50
|
export function accentFrom(command) {
|
|
19
51
|
if (!/\bbaker\s+avatars\s+(create|update)\b/.test(command))
|
|
@@ -22,7 +54,7 @@ export function accentFrom(command) {
|
|
|
22
54
|
return match ? (match[2] ?? match[3] ?? match[4] ?? null) : null;
|
|
23
55
|
}
|
|
24
56
|
/** The refusal, or null when the accent is consistent with what the user asked for. */
|
|
25
|
-
export
|
|
57
|
+
export function accentContradictsUser(command, userText,
|
|
26
58
|
/**
|
|
27
59
|
* What the user said EARLIER in the same conversation.
|
|
28
60
|
*
|
|
@@ -31,23 +63,24 @@ export async function accentContradictsUser(command, userText,
|
|
|
31
63
|
* the UK, in English" — and writes the next in Spanish had their own decision refused,
|
|
32
64
|
* with nothing they could type to get past it short of repeating themselves every time.
|
|
33
65
|
*/
|
|
34
|
-
priorUserText
|
|
35
|
-
const said = [priorUserText ?? "", userText ?? ""].join("
|
|
36
|
-
|
|
37
|
-
if (!accent || !said)
|
|
38
|
-
return null;
|
|
39
|
-
const answers = await judge([ACCENT_WRITTEN_IN_QUESTION, ACCENT_ASKED_FOR_QUESTION, ACCENT_NAMES_LANGUAGE_QUESTION].map((question) => ({
|
|
40
|
-
instructions: { accent, conversation: said, question },
|
|
41
|
-
})));
|
|
42
|
-
const [writtenIn, askedFor, namesLanguage] = answers ?? [];
|
|
43
|
-
if (writtenIn === undefined || askedFor === undefined || namesLanguage === undefined)
|
|
66
|
+
priorUserText) {
|
|
67
|
+
const said = [priorUserText ?? "", userText ?? ""].join(" ").trim();
|
|
68
|
+
if (!command || !said || !writesSpanish(said))
|
|
44
69
|
return null;
|
|
45
|
-
|
|
70
|
+
const accent = accentFrom(command);
|
|
71
|
+
if (!accent)
|
|
46
72
|
return null;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
73
|
+
for (const language of FOREIGN_LANGUAGES) {
|
|
74
|
+
if (!language.accent.test(accent))
|
|
75
|
+
continue;
|
|
76
|
+
if (language.justification.test(said))
|
|
77
|
+
return null;
|
|
78
|
+
return (`Blocked: --accent "${accent}" makes this avatar speak ${language.name}, and the user is writing to you in ` +
|
|
79
|
+
`Spanish without asking for ${language.name} anywhere. --accent sets the LANGUAGE of every clip, not the ` +
|
|
80
|
+
`person's looks — "aspecto europeo", "Nordic" and "Mediterranean" describe a FACE and belong in --subject. ` +
|
|
81
|
+
`Use the user's own language (e.g. --accent "Spanish from Spain, Madrid accent"). If the ad really is for a ` +
|
|
82
|
+
`${language.name}-speaking market, ask them first and say so in the same turn.`);
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
52
85
|
}
|
|
53
86
|
//# sourceMappingURL=avatar-accent.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"avatar-accent.js","sourceRoot":"","sources":["../../src/hono/avatar-accent.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"avatar-accent.js","sourceRoot":"","sources":["../../src/hono/avatar-accent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,mFAAmF;AACnF,MAAM,eAAe,GACnB,iKAAiK,CAAC;AAEpK,sFAAsF;AACtF,MAAM,iBAAiB,GAA8D;IACnF;QACE,IAAI,EAAE,SAAS;QACf,MAAM,EAAE,kEAAkE;QAC1E,aAAa,EAAE,oGAAoG;KACpH;IACD;QACE,IAAI,EAAE,QAAQ;QACd,MAAM,EAAE,gCAAgC;QACxC,aAAa,EAAE,2CAA2C;KAC3D;IACD,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,wBAAwB,EAAE,aAAa,EAAE,oCAAoC,EAAE;IACzG;QACE,IAAI,EAAE,OAAO;QACb,MAAM,EAAE,kCAAkC;QAC1C,aAAa,EAAE,qEAAqE;KACrF;IACD,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,gBAAgB,EAAE,aAAa,EAAE,iCAAiC,EAAE;IAC/F,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,6BAA6B,EAAE,aAAa,EAAE,qCAAqC,EAAE;CACpH,CAAC;AAEF,8EAA8E;AAC9E,MAAM,kBAAkB,GAAG,CAAC,CAAC;AAE7B,SAAS,aAAa,CAAC,IAAY;IACjC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,IAAI,kBAAkB,CAAC;AAC1E,CAAC;AAED,sFAAsF;AACtF,MAAM,UAAU,UAAU,CAAC,OAAe;IACxC,IAAI,CAAC,uCAAuC,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IACxE,MAAM,KAAK,GAAG,8CAA8C,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC3E,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACnE,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,qBAAqB,CACnC,OAA2B,EAC3B,QAA4B;AAC5B;;;;;;;GAOG;AACH,aAAsB;IAEtB,MAAM,IAAI,GAAG,CAAC,aAAa,IAAI,EAAE,EAAE,QAAQ,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IACpE,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3D,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;IACnC,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAEzB,KAAK,MAAM,QAAQ,IAAI,iBAAiB,EAAE,CAAC;QACzC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC;YAAE,SAAS;QAC5C,IAAI,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;QACnD,OAAO,CACL,sBAAsB,MAAM,6BAA6B,QAAQ,CAAC,IAAI,sCAAsC;YAC5G,8BAA8B,QAAQ,CAAC,IAAI,+DAA+D;YAC1G,4GAA4G;YAC5G,6GAA6G;YAC7G,GAAG,QAAQ,CAAC,IAAI,+DAA+D,CAChF,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -1,59 +1,48 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
if (instructions.question === ACCENT_ASKED_FOR_QUESTION) {
|
|
14
|
-
return instructions.conversation?.includes(marker) ? 0.9 : 0.1;
|
|
2
|
+
import { accentContradictsUser, accentFrom } from "./avatar-accent.js";
|
|
3
|
+
const ASK = "Crea un avatar para un anuncio de TikTok en el que vendamos la instalación de placas solares para ahorrar dinero, con aspecto europeo.";
|
|
4
|
+
describe("accentContradictsUser", () => {
|
|
5
|
+
it("refuses each of the three accents that shipped from this exact sentence", () => {
|
|
6
|
+
for (const accent of [
|
|
7
|
+
"Dutch, neutral European accent",
|
|
8
|
+
"German, clear and authoritative",
|
|
9
|
+
"English from UK, Southern England accent",
|
|
10
|
+
]) {
|
|
11
|
+
const refusal = accentContradictsUser(`baker avatars create --name "X" --accent "${accent}"`, ASK);
|
|
12
|
+
expect(refusal).toContain("--accent sets the LANGUAGE");
|
|
15
13
|
}
|
|
16
|
-
return 0.05;
|
|
17
14
|
});
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
it("refuses an accent the judge reads as a language nobody asked for, in any language the user writes", async () => {
|
|
21
|
-
const refusal = await accentContradictsUser(CREATE("Swedish, neutral"), "Zrób awatara do reklamy na TikToka o panelach słonecznych, ma wyglądać skandynawsko.", undefined, fitsWhen("po szwedzku"));
|
|
22
|
-
expect(refusal).toContain("--accent sets the LANGUAGE");
|
|
15
|
+
it("allows the user's own language", () => {
|
|
16
|
+
expect(accentContradictsUser('baker avatars create --name "X" --accent "Spanish from Spain, Madrid accent"', ASK)).toBeNull();
|
|
23
17
|
});
|
|
24
|
-
it("allows
|
|
25
|
-
|
|
26
|
-
expect(
|
|
18
|
+
it("allows a foreign language the user actually asked for", () => {
|
|
19
|
+
// A Spanish speaker selling into the UK is a real case and must not be blocked.
|
|
20
|
+
expect(accentContradictsUser('baker avatars create --name "X" --accent "English from UK"', "Quiero un anuncio para el mercado de UK, en inglés, sobre placas solares")).toBeNull();
|
|
27
21
|
});
|
|
28
|
-
it("
|
|
29
|
-
|
|
30
|
-
// own decision refused, with nothing they could type short of repeating it every time.
|
|
31
|
-
const refusal = await accentContradictsUser(CREATE("English from UK, Southern England accent"), "Crea el avatar y empieza con el primer clip.", "La campaña es para UK y el anuncio va en inglés.", fitsWhen("para UK"));
|
|
32
|
-
expect(refusal).toBeNull();
|
|
33
|
-
});
|
|
34
|
-
it("allows the language the user writes in, which nobody has to ask for", async () => {
|
|
35
|
-
const writesIt = async (questions) => questions.map(({ instructions }) => (instructions.question === ACCENT_ASKED_FOR_QUESTION ? 0.1 : 0.95));
|
|
36
|
-
const refusal = await accentContradictsUser(CREATE("Polish, Warsaw"), "Zrób awatara do reklamy.", undefined, writesIt);
|
|
37
|
-
expect(refusal).toBeNull();
|
|
22
|
+
it("stays out of the way when the user is not writing Spanish", () => {
|
|
23
|
+
expect(accentContradictsUser('baker avatars create --name "X" --accent "German"', "Create an avatar for a TikTok ad about solar panels")).toBeNull();
|
|
38
24
|
});
|
|
39
|
-
it("
|
|
40
|
-
|
|
41
|
-
expect(
|
|
25
|
+
it("ignores commands that are not creating an avatar", () => {
|
|
26
|
+
expect(accentContradictsUser("grep -rn -- --accent docs/", ASK)).toBeNull();
|
|
27
|
+
expect(accentContradictsUser(undefined, ASK)).toBeNull();
|
|
42
28
|
});
|
|
43
|
-
it("lets the command through when the judge cannot answer, rather than refuse on a guess", async () => {
|
|
44
|
-
expect(await accentContradictsUser(CREATE("German"), "Hazme un avatar", undefined, async () => null)).toBeNull();
|
|
45
|
-
});
|
|
46
|
-
it("ignores commands that set no accent, without asking the judge", async () => {
|
|
47
|
-
const unreachable = () => Promise.reject(new Error("the judge should not be asked"));
|
|
48
|
-
expect(await accentContradictsUser("grep -rn -- --accent docs/", "Hazme un avatar", undefined, unreachable)).toBeNull();
|
|
49
|
-
expect(await accentContradictsUser('baker avatars create --name "X"', "Hazme un avatar", undefined, unreachable)).toBeNull();
|
|
50
|
-
expect(await accentContradictsUser(undefined, "Hazme un avatar", undefined, unreachable)).toBeNull();
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
describe("accentFrom", () => {
|
|
54
29
|
it("reads the accent however it was quoted", () => {
|
|
55
30
|
expect(accentFrom('baker avatars create --accent "German, firm"')).toBe("German, firm");
|
|
56
31
|
expect(accentFrom("baker avatars update marta --accent='British English'")).toBe("British English");
|
|
57
32
|
});
|
|
58
33
|
});
|
|
34
|
+
describe("what the user said in an earlier turn", () => {
|
|
35
|
+
it("accepts a foreign accent the conversation already established", () => {
|
|
36
|
+
// The gate reads the turn in front of it. A user who settles the market in one turn —
|
|
37
|
+
// "the campaign is for the UK, in English" — and then writes the next one in Spanish
|
|
38
|
+
// got their own decision refused, with nothing they could type to get past it short of
|
|
39
|
+
// repeating themselves in every message.
|
|
40
|
+
const refusal = accentContradictsUser('baker avatars create --name "X" --accent "English from UK, Southern England accent"', "Crea el avatar y empieza con el primer clip.", "La campaña es para UK y el anuncio va en inglés.");
|
|
41
|
+
expect(refusal).toBeNull();
|
|
42
|
+
});
|
|
43
|
+
it("still refuses when nothing in the conversation asked for it", () => {
|
|
44
|
+
const refusal = accentContradictsUser('baker avatars create --name "X" --accent "English from UK, Southern England accent"', "Crea el avatar y empieza con el primer clip.", "Quiero un anuncio de placas solares para el mercado local.");
|
|
45
|
+
expect(refusal).toContain("--accent sets the LANGUAGE");
|
|
46
|
+
});
|
|
47
|
+
});
|
|
59
48
|
//# sourceMappingURL=avatar-accent.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"avatar-accent.test.js","sourceRoot":"","sources":["../../src/hono/avatar-accent.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,
|
|
1
|
+
{"version":3,"file":"avatar-accent.test.js","sourceRoot":"","sources":["../../src/hono/avatar-accent.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,qBAAqB,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAEvE,MAAM,GAAG,GACP,wIAAwI,CAAC;AAE3I,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACrC,EAAE,CAAC,yEAAyE,EAAE,GAAG,EAAE;QACjF,KAAK,MAAM,MAAM,IAAI;YACnB,gCAAgC;YAChC,iCAAiC;YACjC,0CAA0C;SAC3C,EAAE,CAAC;YACF,MAAM,OAAO,GAAG,qBAAqB,CAAC,6CAA6C,MAAM,GAAG,EAAE,GAAG,CAAC,CAAC;YACnG,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,4BAA4B,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gCAAgC,EAAE,GAAG,EAAE;QACxC,MAAM,CACJ,qBAAqB,CAAC,8EAA8E,EAAE,GAAG,CAAC,CAC3G,CAAC,QAAQ,EAAE,CAAC;IACf,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAC/D,gFAAgF;QAChF,MAAM,CACJ,qBAAqB,CACnB,4DAA4D,EAC5D,0EAA0E,CAC3E,CACF,CAAC,QAAQ,EAAE,CAAC;IACf,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2DAA2D,EAAE,GAAG,EAAE;QACnE,MAAM,CACJ,qBAAqB,CACnB,mDAAmD,EACnD,qDAAqD,CACtD,CACF,CAAC,QAAQ,EAAE,CAAC;IACf,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,CAAC,qBAAqB,CAAC,4BAA4B,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC5E,MAAM,CAAC,qBAAqB,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC3D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAChD,MAAM,CAAC,UAAU,CAAC,8CAA8C,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QACxF,MAAM,CAAC,UAAU,CAAC,uDAAuD,CAAC,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;IACtG,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,uCAAuC,EAAE,GAAG,EAAE;IACrD,EAAE,CAAC,+DAA+D,EAAE,GAAG,EAAE;QACvE,sFAAsF;QACtF,qFAAqF;QACrF,uFAAuF;QACvF,yCAAyC;QACzC,MAAM,OAAO,GAAG,qBAAqB,CACnC,qFAAqF,EACrF,8CAA8C,EAC9C,kDAAkD,CACnD,CAAC;QAEF,MAAM,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC7B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6DAA6D,EAAE,GAAG,EAAE;QACrE,MAAM,OAAO,GAAG,qBAAqB,CACnC,qFAAqF,EACrF,8CAA8C,EAC9C,4DAA4D,CAC7D,CAAC;QAEF,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,4BAA4B,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -11,32 +11,15 @@
|
|
|
11
11
|
* before asking anyway — three times, in both casting modes. A rule that survives being
|
|
12
12
|
* read needs a gate, and `routeToolUse` is the one path every question travels.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* tested without the network or the bridge's environment.
|
|
14
|
+
* Its own module so it can be tested as the pure function it is, without the bridge's
|
|
15
|
+
* environment having to exist.
|
|
17
16
|
*/
|
|
18
|
-
import type { Judge } from "./jev.ts";
|
|
19
|
-
/**
|
|
20
|
-
* The two questions Jev answers about each question the agent asks, as the user would read
|
|
21
|
-
* it: is it a face pick, and is it about a page. A presenter section on a landing is a photo
|
|
22
|
-
* the user already has, which the casting picker cannot offer, and asked in one question it
|
|
23
|
-
* read as a face pick (0.81). Kept word for word in step with the scaffold's
|
|
24
|
-
* guard-avatar-face-question hook, the same rule one layer in. Re-run
|
|
25
|
-
* `scripts/avatar-guard-evals` after changing either.
|
|
26
|
-
*/
|
|
27
|
-
export declare const FACE_PICK_QUESTION: string;
|
|
28
|
-
export declare const ON_A_PAGE_QUESTION = "Does `asked` mention a website, a landing page or a section of a web page?";
|
|
29
|
-
/** At or above, a face pick; below, not about a page. Measured in `scripts/avatar-guard-evals`. */
|
|
30
|
-
export declare const FACE_PICK_HELD = 0.5;
|
|
31
|
-
export declare const ON_A_PAGE_HELD = 0.5;
|
|
32
17
|
/** What the agent is told when this is refused — names both ways out. */
|
|
33
18
|
export declare const FACE_QUESTION_MESSAGE: string;
|
|
34
|
-
/** One question as the user would read it: header, question, then each option. Pure. */
|
|
35
|
-
export declare function questionText(question: unknown): string;
|
|
36
19
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
20
|
+
* Deliberately narrow, because a wrong refusal costs the user a real question: it fires
|
|
21
|
+
* only when the text is about a face AND about an avatar or a casting. An avatar's
|
|
22
|
+
* accent, its wardrobe, whether to use one at all — all still askable.
|
|
40
23
|
*/
|
|
41
|
-
export declare function asksForAvatarFace(input: Record<string, unknown> | undefined
|
|
24
|
+
export declare function asksForAvatarFace(input: Record<string, unknown> | undefined): boolean;
|
|
42
25
|
//# sourceMappingURL=avatar-face-question.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"avatar-face-question.d.ts","sourceRoot":"","sources":["../../src/hono/avatar-face-question.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"avatar-face-question.d.ts","sourceRoot":"","sources":["../../src/hono/avatar-face-question.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAwCH,yEAAyE;AACzE,eAAO,MAAM,qBAAqB,QAK4B,CAAC;AAE/D;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAAG,OAAO,CAqBrF"}
|
|
@@ -11,67 +11,78 @@
|
|
|
11
11
|
* before asking anyway — three times, in both casting modes. A rule that survives being
|
|
12
12
|
* read needs a gate, and `routeToolUse` is the one path every question travels.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* tested without the network or the bridge's environment.
|
|
14
|
+
* Its own module so it can be tested as the pure function it is, without the bridge's
|
|
15
|
+
* environment having to exist.
|
|
17
16
|
*/
|
|
17
|
+
/** A face, in the languages the product is used in. `typeface` must not match. */
|
|
18
|
+
const FACE_WORD = /(?<!type)\b(faces?|rostros?|caras?|retratos?|portraits?)\b/i;
|
|
18
19
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* the user already has, which the casting picker cannot offer, and asked in one question it
|
|
22
|
-
* read as a face pick (0.81). Kept word for word in step with the scaffold's
|
|
23
|
-
* guard-avatar-face-question hook, the same rule one layer in. Re-run
|
|
24
|
-
* `scripts/avatar-guard-evals` after changing either.
|
|
20
|
+
* `face` the VERB, which describes what the avatar does rather than whose it is.
|
|
21
|
+
* "Should Marta face the camera or look away?" is a direction, not a casting call.
|
|
25
22
|
*/
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
23
|
+
const FACE_VERB = /\bfaces?\s+(?:the\s+|el\s+|la\s+|a\s+la\s+)?(?:camera|c[áa]mara|lens|viewer|audience|forward|away|left|right|towards?|hacia)\b/gi;
|
|
24
|
+
/**
|
|
25
|
+
* What makes a face an avatar's face rather than anything else.
|
|
26
|
+
*
|
|
27
|
+
* Two tiers, because they are not equally certain. `avatar` and `casting` name the
|
|
28
|
+
* thing the picker exists for and nothing else. `presenter`/`presentadora` names a
|
|
29
|
+
* ROLE, and a client's landing page has one of those too — a real person, in a photo
|
|
30
|
+
* they already own, that `request_avatar_casting` cannot offer and cannot replace.
|
|
31
|
+
*/
|
|
32
|
+
const CASTING_WORD = /\b(avatars?|casting)\b/i;
|
|
33
|
+
const ROLE_WORD = /\b(presentador[ao]s?|presenters?)\b/i;
|
|
34
|
+
/**
|
|
35
|
+
* An asset the user ALREADY has, which is never a face waiting to be cast. Only
|
|
36
|
+
* consulted for the weak `ROLE_WORD` tier: under an explicit avatar or casting
|
|
37
|
+
* header the picker is still the answer however the question is worded.
|
|
38
|
+
*/
|
|
39
|
+
const EXISTING_ASSET_WORD = /\b(landings?|p[áa]ginas?|pages?|secci[óo]n|seccion|sections?|hero|fotos?|photos?|im[áa]genes|imagen|images?)\b/i;
|
|
40
|
+
/**
|
|
41
|
+
* "Portrait" the ORIENTATION, which is not a face at all.
|
|
42
|
+
*
|
|
43
|
+
* An aspect ratio beside it, or a sibling that names a frame shape, says the question
|
|
44
|
+
* is about the shape of the frame. Under an "Avatar" header that question read as a
|
|
45
|
+
* face picker and was refused — and a wrong refusal costs the user a real question
|
|
46
|
+
* this tool is exactly right for. `vertical` is in the list because it is the
|
|
47
|
+
* commonest phrasing of it and the only one Spanish uses; without it the exemption
|
|
48
|
+
* only ever fired on a question that happened to offer landscape as the alternative.
|
|
49
|
+
*/
|
|
50
|
+
const ORIENTATION_WORD = /\b(\d{1,2}:\d{1,2}|landscape|square|horizontal|vertical|apaisado|cuadrado|aspect ratio|relaci[óo]n de aspecto)\b/i;
|
|
35
51
|
/** What the agent is told when this is refused — names both ways out. */
|
|
36
52
|
export const FACE_QUESTION_MESSAGE = "Blocked: this tool renders text, so it cannot show a face — the user would be choosing between descriptions of " +
|
|
37
53
|
"people they cannot see. If they should choose, invoke the request_avatar_casting TOOL instead — as a tool call with " +
|
|
38
54
|
"avatarHandle, never as a shell command — it renders " +
|
|
39
55
|
"the real portraits and blocks until they pick. If nobody is there to ask, create the avatar with --face auto, ask " +
|
|
40
56
|
"nothing, and do not describe the resulting face as chosen.";
|
|
41
|
-
/** One question as the user would read it: header, question, then each option. Pure. */
|
|
42
|
-
export function questionText(question) {
|
|
43
|
-
if (typeof question !== "object" || question === null)
|
|
44
|
-
return "";
|
|
45
|
-
const { header, question: asked, options } = question;
|
|
46
|
-
const lines = [[header, asked].filter((part) => typeof part === "string" && part.trim()).join(" — ")];
|
|
47
|
-
for (const option of Array.isArray(options) ? options : []) {
|
|
48
|
-
if (typeof option !== "object" || option === null)
|
|
49
|
-
continue;
|
|
50
|
-
const { label, description } = option;
|
|
51
|
-
const parts = [label, description].filter((part) => typeof part === "string" && part.trim());
|
|
52
|
-
if (parts.length > 0)
|
|
53
|
-
lines.push(`- ${parts.join(": ")}`);
|
|
54
|
-
}
|
|
55
|
-
return lines.join("\n").trim();
|
|
56
|
-
}
|
|
57
57
|
/**
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
58
|
+
* Deliberately narrow, because a wrong refusal costs the user a real question: it fires
|
|
59
|
+
* only when the text is about a face AND about an avatar or a casting. An avatar's
|
|
60
|
+
* accent, its wardrobe, whether to use one at all — all still askable.
|
|
61
61
|
*/
|
|
62
|
-
export
|
|
62
|
+
export function asksForAvatarFace(input) {
|
|
63
63
|
const questions = input?.questions;
|
|
64
64
|
if (!Array.isArray(questions))
|
|
65
65
|
return false;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
66
|
+
return questions.some((question) => {
|
|
67
|
+
const q = question;
|
|
68
|
+
const options = Array.isArray(q.options)
|
|
69
|
+
? q.options
|
|
70
|
+
.map((option) => {
|
|
71
|
+
const o = option;
|
|
72
|
+
return `${String(o?.label ?? "")} ${String(o?.description ?? "")}`;
|
|
73
|
+
})
|
|
74
|
+
.join(" ")
|
|
75
|
+
: "";
|
|
76
|
+
// The verb is dropped before anything else looks for a face, so a question that
|
|
77
|
+
// only ever used `face` that way has no face in it at all.
|
|
78
|
+
const text = `${String(q.header ?? "")} ${String(q.question ?? "")} ${options}`.replace(FACE_VERB, " ");
|
|
79
|
+
if (ORIENTATION_WORD.test(text) && !/(faces?|rostros?|caras?)\b/i.test(text))
|
|
80
|
+
return false;
|
|
81
|
+
if (!FACE_WORD.test(text))
|
|
82
|
+
return false;
|
|
83
|
+
if (CASTING_WORD.test(text))
|
|
84
|
+
return true;
|
|
85
|
+
return ROLE_WORD.test(text) && !EXISTING_ASSET_WORD.test(text);
|
|
86
|
+
});
|
|
76
87
|
}
|
|
77
88
|
//# sourceMappingURL=avatar-face-question.js.map
|