@llamaventures/cli 2.0.2 → 2.0.3
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 +16 -0
- package/README.md +3 -2
- package/bin/llama-mcp.mjs +72 -34
- package/bin/llama.mjs +39 -6
- package/lib/build-manifest.json +2 -2
- package/lib/client.mjs +30 -1
- package/lib/deal-actions.mjs +26 -58
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.0.3] - 2026-08-31
|
|
4
|
+
|
|
5
|
+
- Show Core validation codes and field paths instead of collapsing malformed
|
|
6
|
+
Deal writes into `Invalid Occam command`.
|
|
7
|
+
- Publish `write_deal` as four operation-specific MCP command shapes while
|
|
8
|
+
retaining one mutation tool.
|
|
9
|
+
- Add copyable write examples to `llama help deal` and omit the duplicate
|
|
10
|
+
rendered briefing from structured bootstrap output.
|
|
11
|
+
- Let Core's private Page contract classify prose and enums instead of keeping
|
|
12
|
+
a second, drifting public-CLI field whitelist.
|
|
13
|
+
- Return a compact `page.patch` revision receipt with exact read-back fields
|
|
14
|
+
instead of echoing the full Live Deal Page after every write.
|
|
15
|
+
- Turn retired top-level commands such as `llama brief`, `fact`, `post`,
|
|
16
|
+
`timeline`, `html`, `memo`, and `pipeline` into self-correcting errors that
|
|
17
|
+
name the CLI 2 replacement and live contract entrypoint.
|
|
18
|
+
|
|
3
19
|
## [2.0.2] - 2026-08-31
|
|
4
20
|
|
|
5
21
|
- Correct the authenticated runtime model: Brain has exactly two private
|
package/README.md
CHANGED
|
@@ -90,8 +90,9 @@ When Page prose is human-visible, write one bilingual field value as
|
|
|
90
90
|
`{"en":"natural English","zh":"自然中文"}`. The Web language switch selects
|
|
91
91
|
the reader's version from the same Page revision. Keep language-neutral names,
|
|
92
92
|
enums, URLs, numbers, dates, and source IDs scalar. Information and raw Input
|
|
93
|
-
stay in their original language with provenance.
|
|
94
|
-
|
|
93
|
+
stay in their original language with provenance. Core rejects new scalar Page
|
|
94
|
+
prose according to its private Page contract; the public CLI catches malformed
|
|
95
|
+
half-localized pairs without maintaining a second field whitelist. Historical
|
|
95
96
|
scalar prose remains readable.
|
|
96
97
|
|
|
97
98
|
Raw user input example:
|
package/bin/llama-mcp.mjs
CHANGED
|
@@ -5,10 +5,12 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
5
5
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
import {
|
|
8
|
+
formatErrorForDisplay,
|
|
8
9
|
getAuthHeaders,
|
|
9
10
|
readBriefing,
|
|
10
11
|
request,
|
|
11
12
|
setClientRuntime,
|
|
13
|
+
structuredBootstrapManifest,
|
|
12
14
|
} from "../lib/client.mjs";
|
|
13
15
|
import {
|
|
14
16
|
clearExternalSession,
|
|
@@ -20,6 +22,7 @@ import {
|
|
|
20
22
|
import {
|
|
21
23
|
buildDealReadPath,
|
|
22
24
|
buildDealSearchPath,
|
|
25
|
+
compactDealWriteResult,
|
|
23
26
|
prepareDealCommand,
|
|
24
27
|
} from "../lib/deal-actions.mjs";
|
|
25
28
|
|
|
@@ -36,12 +39,12 @@ function textResult(text, isError = false) {
|
|
|
36
39
|
};
|
|
37
40
|
}
|
|
38
41
|
|
|
39
|
-
async function callApi(method, path, body) {
|
|
42
|
+
async function callApi(method, path, body, transform = (value) => value) {
|
|
40
43
|
try {
|
|
41
|
-
const result = await request(method, path, body);
|
|
44
|
+
const result = transform(await request(method, path, body));
|
|
42
45
|
return textResult(typeof result === "string" ? result : JSON.stringify(result, null, 2));
|
|
43
46
|
} catch (error) {
|
|
44
|
-
return textResult(
|
|
47
|
+
return textResult(formatErrorForDisplay(error), true);
|
|
45
48
|
}
|
|
46
49
|
}
|
|
47
50
|
|
|
@@ -51,6 +54,54 @@ const originSchema = z.object({
|
|
|
51
54
|
originatingChatRecordId: z.string().uuid().optional(),
|
|
52
55
|
});
|
|
53
56
|
|
|
57
|
+
const writeDealCommandSchema = z.discriminatedUnion("operation", [
|
|
58
|
+
z.object({
|
|
59
|
+
operation: z.literal("input.submit"),
|
|
60
|
+
dealId: z.string().uuid(),
|
|
61
|
+
format: z.string().min(1).max(120).optional(),
|
|
62
|
+
content: z.any(),
|
|
63
|
+
source: z.record(z.string(), z.any()).optional(),
|
|
64
|
+
origin: originSchema,
|
|
65
|
+
idempotencyKey: z.string().min(1).max(240).optional(),
|
|
66
|
+
}).strict(),
|
|
67
|
+
z.object({
|
|
68
|
+
operation: z.literal("information.put"),
|
|
69
|
+
dealId: z.string().uuid(),
|
|
70
|
+
informationId: z.string().uuid().optional(),
|
|
71
|
+
type: z.string().min(1).max(120),
|
|
72
|
+
labels: z.array(z.string()).optional(),
|
|
73
|
+
subject: z.record(z.string(), z.any()).optional(),
|
|
74
|
+
value: z.any(),
|
|
75
|
+
expectedVersion: z.number().int().positive().optional(),
|
|
76
|
+
origin: originSchema,
|
|
77
|
+
idempotencyKey: z.string().min(1).max(240).optional(),
|
|
78
|
+
}).strict(),
|
|
79
|
+
z.object({
|
|
80
|
+
operation: z.literal("page.patch"),
|
|
81
|
+
dealId: z.string().uuid(),
|
|
82
|
+
patch: z.record(z.string(), z.any()),
|
|
83
|
+
expectedRevision: z.number().int().nonnegative().optional(),
|
|
84
|
+
origin: originSchema,
|
|
85
|
+
idempotencyKey: z.string().min(1).max(240).optional(),
|
|
86
|
+
}).strict(),
|
|
87
|
+
z.object({
|
|
88
|
+
operation: z.literal("artifact.put"),
|
|
89
|
+
dealId: z.string().uuid(),
|
|
90
|
+
artifactId: z.string().uuid().optional(),
|
|
91
|
+
kind: z.string().min(1).max(120),
|
|
92
|
+
title: z.string().min(1).max(500),
|
|
93
|
+
mimeType: z.string().min(1).max(240),
|
|
94
|
+
contentBase64: z.string().min(1).optional(),
|
|
95
|
+
storageKey: z.string().min(1).optional(),
|
|
96
|
+
storageUrl: z.string().url().optional(),
|
|
97
|
+
byteSize: z.number().int().nonnegative().optional(),
|
|
98
|
+
sha256: z.string().regex(/^[0-9a-f]{64}$/).optional(),
|
|
99
|
+
metadata: z.record(z.string(), z.any()).optional(),
|
|
100
|
+
origin: originSchema,
|
|
101
|
+
idempotencyKey: z.string().min(1).max(240).optional(),
|
|
102
|
+
}).strict(),
|
|
103
|
+
]);
|
|
104
|
+
|
|
54
105
|
// Deal is deliberately a closed four-tool surface. Do not add split resource
|
|
55
106
|
// tools here: one more tool is one more branch every agent must reason about.
|
|
56
107
|
server.registerTool(
|
|
@@ -70,7 +121,7 @@ server.registerTool(
|
|
|
70
121
|
server.registerTool(
|
|
71
122
|
"read_deal",
|
|
72
123
|
{
|
|
73
|
-
description: "Read one Deal. Live Page
|
|
124
|
+
description: "Read one Deal. Live Page is current human-visible state; Information is memory and never updates Page automatically. Page field semantics come from agent_bootstrap, not every Deal read.",
|
|
74
125
|
inputSchema: {
|
|
75
126
|
dealId: z.string().min(1),
|
|
76
127
|
detail: z.enum(["overview", "memory", "files", "conversation", "history", "all"]).optional(),
|
|
@@ -99,36 +150,18 @@ server.registerTool(
|
|
|
99
150
|
server.registerTool(
|
|
100
151
|
"write_deal",
|
|
101
152
|
{
|
|
102
|
-
description: "The only Deal mutation tool: input.submit, information.put, page.patch, or artifact.put. All Page content fields are Agent-writable; author controls attribution, not permission. Use the
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
expectedRevision: z.number().int().nonnegative().optional(),
|
|
108
|
-
informationId: z.string().uuid().optional(),
|
|
109
|
-
type: z.string().min(1).max(120).optional(),
|
|
110
|
-
labels: z.array(z.string()).optional(),
|
|
111
|
-
subject: z.record(z.string(), z.any()).optional(),
|
|
112
|
-
value: z.any().optional(),
|
|
113
|
-
expectedVersion: z.number().int().positive().optional(),
|
|
114
|
-
format: z.string().min(1).max(120).optional(),
|
|
115
|
-
content: z.any().optional(),
|
|
116
|
-
source: z.record(z.string(), z.any()).optional(),
|
|
117
|
-
artifactId: z.string().uuid().optional(),
|
|
118
|
-
kind: z.string().min(1).max(120).optional(),
|
|
119
|
-
title: z.string().min(1).max(500).optional(),
|
|
120
|
-
mimeType: z.string().min(1).max(240).optional(),
|
|
121
|
-
contentBase64: z.string().min(1).optional(),
|
|
122
|
-
storageKey: z.string().min(1).optional(),
|
|
123
|
-
storageUrl: z.string().url().optional(),
|
|
124
|
-
byteSize: z.number().int().nonnegative().optional(),
|
|
125
|
-
sha256: z.string().regex(/^[0-9a-f]{64}$/).optional(),
|
|
126
|
-
metadata: z.record(z.string(), z.any()).optional(),
|
|
127
|
-
origin: originSchema,
|
|
128
|
-
idempotencyKey: z.string().min(1).max(240).optional(),
|
|
129
|
-
},
|
|
153
|
+
description: "The only Deal mutation tool. Pass one command: input.submit, information.put, page.patch, or artifact.put. All Page content fields are Agent-writable; author controls attribution, not permission. Use the bootstrap field contract and bilingual Page prose. page.patch is JSON Merge Patch: arrays replace whole arrays, so read-modify-write and preserve sibling slots.",
|
|
154
|
+
// MCP SDK 1.30 only publishes object schemas at the tool root. Nesting one
|
|
155
|
+
// command preserves the real discriminated union instead of flattening four
|
|
156
|
+
// incompatible operations into false affordances.
|
|
157
|
+
inputSchema: { command: writeDealCommandSchema },
|
|
130
158
|
},
|
|
131
|
-
async (
|
|
159
|
+
async ({ command }) => callApi(
|
|
160
|
+
"POST",
|
|
161
|
+
"/api/occam/deals/commands",
|
|
162
|
+
prepareDealCommand("write", command),
|
|
163
|
+
(result) => compactDealWriteResult(command, result),
|
|
164
|
+
),
|
|
132
165
|
);
|
|
133
166
|
|
|
134
167
|
server.registerTool(
|
|
@@ -158,7 +191,12 @@ server.registerTool(
|
|
|
158
191
|
async ({ limit } = {}) => {
|
|
159
192
|
const params = new URLSearchParams({ clientVersion: PKG_VERSION });
|
|
160
193
|
if (limit) params.set("limit", String(limit));
|
|
161
|
-
|
|
194
|
+
try {
|
|
195
|
+
const result = await request("GET", `/api/agent/manifest?${params}`);
|
|
196
|
+
return textResult(JSON.stringify(structuredBootstrapManifest(result), null, 2));
|
|
197
|
+
} catch (error) {
|
|
198
|
+
return textResult(formatErrorForDisplay(error), true);
|
|
199
|
+
}
|
|
162
200
|
},
|
|
163
201
|
);
|
|
164
202
|
|
package/bin/llama.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { createRequire } from "node:module";
|
|
|
4
4
|
import { readFile } from "node:fs/promises";
|
|
5
5
|
import readline from "node:readline";
|
|
6
6
|
import {
|
|
7
|
+
formatErrorForDisplay,
|
|
7
8
|
getAuthHeaders,
|
|
8
9
|
getBaseUrl,
|
|
9
10
|
getToken,
|
|
@@ -12,6 +13,7 @@ import {
|
|
|
12
13
|
readCanonicalToken,
|
|
13
14
|
readLegacyConfig,
|
|
14
15
|
request,
|
|
16
|
+
structuredBootstrapManifest,
|
|
15
17
|
tryGcloudIdentityToken,
|
|
16
18
|
writeCanonicalToken,
|
|
17
19
|
writeLegacyConfig,
|
|
@@ -33,6 +35,7 @@ import {
|
|
|
33
35
|
DEAL_ACTIONS,
|
|
34
36
|
buildDealReadPath,
|
|
35
37
|
buildDealSearchPath,
|
|
38
|
+
compactDealWriteResult,
|
|
36
39
|
prepareDealCommand,
|
|
37
40
|
readJsonInput,
|
|
38
41
|
} from "../lib/deal-actions.mjs";
|
|
@@ -82,8 +85,14 @@ read-modify-write complete Page arrays and preserve sibling slots.
|
|
|
82
85
|
create JSON:
|
|
83
86
|
{"companyName":"Acme","page":{},"information":[],"origin":{"kind":"user","originalUserUtterance":"..."}}
|
|
84
87
|
|
|
85
|
-
write
|
|
86
|
-
input.submit
|
|
88
|
+
write JSON (CLI derives idempotencyKey when omitted):
|
|
89
|
+
input.submit: {"operation":"input.submit","dealId":"<uuid>","content":"<original input>","format":"text","source":{},"origin":{"kind":"user","originalUserUtterance":"..."}}
|
|
90
|
+
information.put:{"operation":"information.put","dealId":"<uuid>","type":"founder_claim","labels":["founder-stated"],"subject":{"topic":"traction"},"value":{"content":"...","source":{"kind":"first_meeting_transcript"}},"origin":{"kind":"user","originalUserUtterance":"..."}}
|
|
91
|
+
page.patch: {"operation":"page.patch","dealId":"<uuid>","patch":{"description":{"en":"...","zh":"..."}},"origin":{"kind":"agent"}}
|
|
92
|
+
artifact.put: {"operation":"artifact.put","dealId":"<uuid>","kind":"deck","title":"deck.pdf","mimeType":"application/pdf","storageKey":"<drive-file-id>","storageUrl":"https://drive.google.com/...","byteSize":123,"sha256":"<64 hex>","origin":{"kind":"user","originalUserUtterance":"..."}}
|
|
93
|
+
|
|
94
|
+
For Information, origin records who caused the write and preserves user words;
|
|
95
|
+
value.source records where the evidence came from.
|
|
87
96
|
|
|
88
97
|
Core owns Chat Records, append-only Deal Events, Drive provisioning, audit,
|
|
89
98
|
and idempotency. User-originated work must preserve exact wording in
|
|
@@ -127,6 +136,28 @@ function assertDealAction(area, action) {
|
|
|
127
136
|
}
|
|
128
137
|
}
|
|
129
138
|
|
|
139
|
+
const RETIRED_TOP_LEVEL_COMMANDS = Object.freeze({
|
|
140
|
+
brief: "llama deal write --json (operation: page.patch)",
|
|
141
|
+
fact: "llama deal write --json (operation: information.put)",
|
|
142
|
+
post: "llama deal write --json (operation: input.submit)",
|
|
143
|
+
timeline: "llama deal read <dealId> --detail history",
|
|
144
|
+
html: "llama deal write --json (operation: artifact.put)",
|
|
145
|
+
memo: "llama deal write --json (operation: artifact.put)",
|
|
146
|
+
pipeline: "llama deal search|read|create|write",
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
function unknownCommandError(area, action) {
|
|
150
|
+
const replacement = RETIRED_TOP_LEVEL_COMMANDS[area];
|
|
151
|
+
if (replacement) {
|
|
152
|
+
return new Error(
|
|
153
|
+
`\`llama ${area}\` was retired in CLI 2.0.\n` +
|
|
154
|
+
` → Use: ${replacement}\n` +
|
|
155
|
+
" → Contract: llama agent bootstrap",
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
return new Error(`Unknown command: llama ${[area, action].filter(Boolean).join(" ")}`);
|
|
159
|
+
}
|
|
160
|
+
|
|
130
161
|
function onboardingNoAuth() {
|
|
131
162
|
return `Llama team onboarding requires credentials.
|
|
132
163
|
|
|
@@ -441,7 +472,7 @@ async function main() {
|
|
|
441
472
|
const params = new URLSearchParams({ clientVersion: PKG_VERSION });
|
|
442
473
|
if (flags.limit && flags.limit !== true) params.set("limit", String(flags.limit));
|
|
443
474
|
const manifest = await request("GET", `/api/agent/manifest?${params}`);
|
|
444
|
-
if (flags.json) print(manifest);
|
|
475
|
+
if (flags.json) print(structuredBootstrapManifest(manifest));
|
|
445
476
|
else process.stdout.write(`${manifest.briefing || JSON.stringify(manifest, null, 2)}\n`);
|
|
446
477
|
return;
|
|
447
478
|
}
|
|
@@ -539,7 +570,9 @@ async function main() {
|
|
|
539
570
|
if (area === "deal" && ["create", "write"].includes(action)) {
|
|
540
571
|
const { flags } = parseFlags(rest, ["json"]);
|
|
541
572
|
const input = await readJsonInput(flags.json);
|
|
542
|
-
|
|
573
|
+
const command = prepareDealCommand(action, input);
|
|
574
|
+
const response = await request("POST", "/api/occam/deals/commands", command);
|
|
575
|
+
print(compactDealWriteResult(command, response));
|
|
543
576
|
return;
|
|
544
577
|
}
|
|
545
578
|
|
|
@@ -565,12 +598,12 @@ async function main() {
|
|
|
565
598
|
return;
|
|
566
599
|
}
|
|
567
600
|
|
|
568
|
-
throw
|
|
601
|
+
throw unknownCommandError(area, action);
|
|
569
602
|
}
|
|
570
603
|
|
|
571
604
|
main()
|
|
572
605
|
.then(() => maybeNudgeUpdate())
|
|
573
606
|
.catch((error) => {
|
|
574
|
-
console.error(
|
|
607
|
+
console.error(formatErrorForDisplay(error));
|
|
575
608
|
process.exit(1);
|
|
576
609
|
});
|
package/lib/build-manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"format": "llama.cli-build.v1",
|
|
3
3
|
"packageName": "@llamaventures/cli",
|
|
4
|
-
"packageVersion": "2.0.
|
|
5
|
-
"sourceSha": "
|
|
4
|
+
"packageVersion": "2.0.3",
|
|
5
|
+
"sourceSha": "dc1618ab65b6cb2e73b107e7d56315ce2076744e",
|
|
6
6
|
"sourceKind": "github",
|
|
7
7
|
"sourceDirty": false,
|
|
8
8
|
"coreApiContract": {
|
package/lib/client.mjs
CHANGED
|
@@ -534,7 +534,21 @@ function apiResponseError(data, status) {
|
|
|
534
534
|
return error;
|
|
535
535
|
}
|
|
536
536
|
if (data?.code !== "MENTION_APPROVAL_REQUIRED") {
|
|
537
|
-
const
|
|
537
|
+
const issues = Array.isArray(data?.issues)
|
|
538
|
+
? data.issues.map((issue) => {
|
|
539
|
+
const path = Array.isArray(issue?.path) && issue.path.length
|
|
540
|
+
? issue.path.reduce(
|
|
541
|
+
(result, part) => typeof part === "number"
|
|
542
|
+
? `${result}[${part}]`
|
|
543
|
+
: result ? `${result}.${part}` : String(part),
|
|
544
|
+
"",
|
|
545
|
+
)
|
|
546
|
+
: "$";
|
|
547
|
+
const detail = issue?.message || issue?.code || "Invalid value";
|
|
548
|
+
return `- ${path}: ${detail}`;
|
|
549
|
+
})
|
|
550
|
+
: [];
|
|
551
|
+
const error = new Error([message, ...issues].join("\n"));
|
|
538
552
|
if (typeof data?.code === "string") error.code = data.code;
|
|
539
553
|
error.details = data;
|
|
540
554
|
error.status = status;
|
|
@@ -563,6 +577,21 @@ function apiResponseError(data, status) {
|
|
|
563
577
|
return error;
|
|
564
578
|
}
|
|
565
579
|
|
|
580
|
+
export function formatErrorForDisplay(error) {
|
|
581
|
+
const message = error?.message ?? String(error);
|
|
582
|
+
if (/^Error(?:\[[^\]]+\])?:/u.test(message)) return message;
|
|
583
|
+
const code = typeof error?.code === "string" ? `[${error.code}]` : "";
|
|
584
|
+
return `Error${code}: ${message}`;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
export function structuredBootstrapManifest(manifest) {
|
|
588
|
+
if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
|
|
589
|
+
return manifest;
|
|
590
|
+
}
|
|
591
|
+
const { briefing: _renderedDuplicate, ...structured } = manifest;
|
|
592
|
+
return structured;
|
|
593
|
+
}
|
|
594
|
+
|
|
566
595
|
async function requestWithRetry(method, endpoint, body, opts, allowRetry) {
|
|
567
596
|
const authHeaders = await getAuthHeaders();
|
|
568
597
|
if (Object.keys(authHeaders).length === 0) throw noAuthError();
|
package/lib/deal-actions.mjs
CHANGED
|
@@ -72,62 +72,6 @@ function validateLocalizedTextPairs(value, path = "page") {
|
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
const LANGUAGE_NEUTRAL_PAGE_KEYS = new Set([
|
|
76
|
-
"companyName", "companyKey", "state", "pipelineStatus", "stage",
|
|
77
|
-
"theirStage", "ownerUserId", "sourceUserId", "website", "dealOwner",
|
|
78
|
-
"source", "sourceDirection", "proposedAmount", "roundSize", "valuation",
|
|
79
|
-
"founders", "deckLink", "location", "foundedYear", "leadInvestor",
|
|
80
|
-
"dateAdded", "tab", "sourcePerson", "createdByName", "createdByLabel",
|
|
81
|
-
"createdVia", "id", "name", "key", "slug", "url", "href", "email",
|
|
82
|
-
"linkedin", "handle", "status", "kind", "type", "tier", "confidence",
|
|
83
|
-
"currency", "unit", "format", "mimeType", "sha256", "timestamp",
|
|
84
|
-
"date", "year",
|
|
85
|
-
]);
|
|
86
|
-
const LANGUAGE_NEUTRAL_PAGE_CONTAINERS = new Set([
|
|
87
|
-
"manualTags", "investors", "founderEmails", "labels", "tags",
|
|
88
|
-
]);
|
|
89
|
-
const OPAQUE_PAGE_CONTAINERS = new Set([
|
|
90
|
-
"migration", "extra", "fieldProvenance",
|
|
91
|
-
]);
|
|
92
|
-
|
|
93
|
-
function pathKeys(path) {
|
|
94
|
-
return path.match(/[^.[\]]+/g)?.filter((part) => !/^\d+$/u.test(part)) ?? [];
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function isLanguageNeutralPagePath(path) {
|
|
98
|
-
const keys = pathKeys(path);
|
|
99
|
-
if (keys.some((key) => OPAQUE_PAGE_CONTAINERS.has(key))) return true;
|
|
100
|
-
if (keys.some((key) => LANGUAGE_NEUTRAL_PAGE_CONTAINERS.has(key))) return true;
|
|
101
|
-
const key = keys.at(-1) ?? "";
|
|
102
|
-
if (LANGUAGE_NEUTRAL_PAGE_KEYS.has(key)) return true;
|
|
103
|
-
return /(?:Id|Ids|URL|Url|URI|Uri|Link|Email|Emails|At|Date|Year|Key|Slug|Hash)$/u.test(key);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function validateAgentAuthoredPageProse(value, path = "page") {
|
|
107
|
-
if (typeof value === "string") {
|
|
108
|
-
if (!isLanguageNeutralPagePath(path)) {
|
|
109
|
-
throw new Error(
|
|
110
|
-
`${path} agent-authored prose must be one value with non-empty en and zh`,
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
if (Array.isArray(value)) {
|
|
116
|
-
value.forEach((item, index) => validateAgentAuthoredPageProse(item, `${path}[${index}]`));
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
if (!value || typeof value !== "object") return;
|
|
120
|
-
if (
|
|
121
|
-
Object.prototype.hasOwnProperty.call(value, "en") ||
|
|
122
|
-
Object.prototype.hasOwnProperty.call(value, "zh")
|
|
123
|
-
) {
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
for (const [key, child] of Object.entries(value)) {
|
|
127
|
-
validateAgentAuthoredPageProse(child, `${path}.${key}`);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
75
|
export async function readJsonInput(path, stdin = process.stdin) {
|
|
132
76
|
if (!path || path === true) throw new Error("--json <file|-> is required");
|
|
133
77
|
const text = path === "-"
|
|
@@ -170,6 +114,32 @@ export function buildDealReadPath(dealId, detail = "overview") {
|
|
|
170
114
|
return `/api/occam/deals/${encodeURIComponent(dealId)}${expansion ? `?expand=${expansion}` : ""}`;
|
|
171
115
|
}
|
|
172
116
|
|
|
117
|
+
/**
|
|
118
|
+
* page.patch previously echoed the entire Live Deal Page after every write.
|
|
119
|
+
* Keep the authoritative revision receipt and tell the Agent exactly what to
|
|
120
|
+
* read back, without paying another full-page context cost before verification.
|
|
121
|
+
*/
|
|
122
|
+
export function compactDealWriteResult(command, response) {
|
|
123
|
+
if (command?.operation !== "page.patch" || !response?.result?.page) return response;
|
|
124
|
+
const page = response.result.page;
|
|
125
|
+
return {
|
|
126
|
+
...response,
|
|
127
|
+
result: {
|
|
128
|
+
idempotent: Boolean(response.result.idempotent),
|
|
129
|
+
page: {
|
|
130
|
+
id: page.id,
|
|
131
|
+
revision: page.revision,
|
|
132
|
+
updatedAt: page.updatedAt,
|
|
133
|
+
},
|
|
134
|
+
verify: {
|
|
135
|
+
dealId: command.dealId,
|
|
136
|
+
fields: Object.keys(command.patch ?? {}),
|
|
137
|
+
command: `llama deal read ${command.dealId}`,
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
173
143
|
export function prepareDealCommand(action, input) {
|
|
174
144
|
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
175
145
|
throw new Error("Mutation JSON must be an object");
|
|
@@ -183,11 +153,9 @@ export function prepareDealCommand(action, input) {
|
|
|
183
153
|
}
|
|
184
154
|
if (action === "create") {
|
|
185
155
|
validateLocalizedTextPairs(command.page ?? {});
|
|
186
|
-
validateAgentAuthoredPageProse(command.page ?? {});
|
|
187
156
|
}
|
|
188
157
|
if (action === "write" && command.operation === "page.patch") {
|
|
189
158
|
validateLocalizedTextPairs(command.patch ?? {});
|
|
190
|
-
validateAgentAuthoredPageProse(command.patch ?? {});
|
|
191
159
|
}
|
|
192
160
|
return ensureIdempotencyKey(validateCommandOrigin(command));
|
|
193
161
|
}
|