@dench.com/cli 3.8.6-staging.409 → 3.9.0-staging.417
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/app-sdk.d.ts +788 -26
- package/campaign.ts +94 -27
- package/crm.ts +157 -41
- package/dench.ts +4 -0
- package/lib/api-schemas.ts +126 -16
- package/lib/app-sdk-catalog.generated.ts +5 -5
- package/lib/campaign-agent.ts +26 -14
- package/lib/campaign-reference.ts +39 -0
- package/lib/command-registry.ts +10 -6
- package/lib/references.generated.json +14 -14
- package/lib/workspace-references.ts +205 -0
- package/package.json +1 -1
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/** Explicit, portable navigation metadata. Never derive identities from prose. */
|
|
2
|
+
type ReferencePresentation = {
|
|
3
|
+
label: string;
|
|
4
|
+
href: string;
|
|
5
|
+
url?: string;
|
|
6
|
+
imageUrl?: string;
|
|
7
|
+
objectIcon?: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type WorkspaceReference = ReferencePresentation &
|
|
11
|
+
(
|
|
12
|
+
| { kind: "crm-entry"; objectName: string; entryId: string }
|
|
13
|
+
| { kind: "crm-object"; objectName: string; objectId?: string }
|
|
14
|
+
| { kind: "campaign"; sequenceId: string }
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
export type WorkspaceReferenceCoverage = {
|
|
18
|
+
returned: number;
|
|
19
|
+
/** References available in this result, never the total matching dataset. */
|
|
20
|
+
available: number;
|
|
21
|
+
truncated: boolean;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type WorkspaceReferences = {
|
|
25
|
+
references: WorkspaceReference[];
|
|
26
|
+
referenceCoverage: WorkspaceReferenceCoverage;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const WORKSPACE_REFERENCE_LIMIT = 100;
|
|
30
|
+
export const WORKSPACE_REFERENCE_BYTE_LIMIT = 16 * 1024;
|
|
31
|
+
|
|
32
|
+
export function workspaceReferenceKey(reference: WorkspaceReference): string {
|
|
33
|
+
if (reference.kind === "campaign") return `campaign:${reference.sequenceId}`;
|
|
34
|
+
if (reference.kind === "crm-object")
|
|
35
|
+
return `crm-object:${reference.objectName}`;
|
|
36
|
+
return `crm-entry:${reference.objectName}:${reference.entryId}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function workspaceReferenceHref(
|
|
40
|
+
reference:
|
|
41
|
+
| { kind: "crm-entry"; objectName: string; entryId: string }
|
|
42
|
+
| { kind: "crm-object"; objectName: string }
|
|
43
|
+
| { kind: "campaign"; sequenceId: string },
|
|
44
|
+
): string {
|
|
45
|
+
if (reference.kind === "campaign")
|
|
46
|
+
return `/?path=${encodeURIComponent(`~outreach/sequences/${reference.sequenceId}`)}`;
|
|
47
|
+
const object = encodeURIComponent(reference.objectName);
|
|
48
|
+
return reference.kind === "crm-object"
|
|
49
|
+
? `/?path=${object}`
|
|
50
|
+
: `/?path=${object}&entry=${object}:${encodeURIComponent(reference.entryId)}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function withWorkspaceReferenceUrls(
|
|
54
|
+
reference: WorkspaceReference,
|
|
55
|
+
context: { host?: string; workspaceSlug?: string },
|
|
56
|
+
): WorkspaceReference {
|
|
57
|
+
const href = workspaceReferenceHref(reference);
|
|
58
|
+
const { url: _oldUrl, ...rest } = reference;
|
|
59
|
+
const slug = context.workspaceSlug?.trim();
|
|
60
|
+
const host = context.host?.trim();
|
|
61
|
+
if (!host || !slug) return { ...rest, href };
|
|
62
|
+
try {
|
|
63
|
+
const origin = new URL(
|
|
64
|
+
/^https?:\/\//i.test(host) ? host : `https://${host}`,
|
|
65
|
+
);
|
|
66
|
+
if (!["http:", "https:"].includes(origin.protocol))
|
|
67
|
+
return { ...rest, href };
|
|
68
|
+
return {
|
|
69
|
+
...rest,
|
|
70
|
+
href,
|
|
71
|
+
url: `${origin.origin}/${encodeURIComponent(slug)}${href.slice(1)}`,
|
|
72
|
+
};
|
|
73
|
+
} catch {
|
|
74
|
+
return { ...rest, href };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const text = (value: unknown, max: number): value is string =>
|
|
79
|
+
typeof value === "string" && value.trim().length > 0 && value.length <= max;
|
|
80
|
+
|
|
81
|
+
/** Validate typed metadata at the display boundary; ignore arbitrary ID fields. */
|
|
82
|
+
export function parseWorkspaceReference(
|
|
83
|
+
value: unknown,
|
|
84
|
+
): WorkspaceReference | null {
|
|
85
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
86
|
+
const row = value as Record<string, unknown>;
|
|
87
|
+
if (!text(row.label, 2048) || !text(row.href, 4096)) return null;
|
|
88
|
+
const presentation: ReferencePresentation = {
|
|
89
|
+
label: row.label,
|
|
90
|
+
href: row.href,
|
|
91
|
+
};
|
|
92
|
+
if (text(row.objectIcon, 128)) presentation.objectIcon = row.objectIcon;
|
|
93
|
+
for (const key of ["imageUrl", "url"] as const) {
|
|
94
|
+
if (!text(row[key], 4096)) continue;
|
|
95
|
+
try {
|
|
96
|
+
const url = new URL(row[key]);
|
|
97
|
+
if (["http:", "https:"].includes(url.protocol))
|
|
98
|
+
presentation[key] = row[key];
|
|
99
|
+
} catch {
|
|
100
|
+
/* optional invalid URLs do not make an identity valid */
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
let reference: WorkspaceReference;
|
|
104
|
+
if (row.kind === "campaign" && text(row.sequenceId, 256)) {
|
|
105
|
+
reference = {
|
|
106
|
+
...presentation,
|
|
107
|
+
kind: "campaign",
|
|
108
|
+
sequenceId: row.sequenceId,
|
|
109
|
+
};
|
|
110
|
+
} else if (
|
|
111
|
+
row.kind === "crm-entry" &&
|
|
112
|
+
text(row.objectName, 256) &&
|
|
113
|
+
text(row.entryId, 256)
|
|
114
|
+
) {
|
|
115
|
+
reference = {
|
|
116
|
+
...presentation,
|
|
117
|
+
kind: "crm-entry",
|
|
118
|
+
objectName: row.objectName,
|
|
119
|
+
entryId: row.entryId,
|
|
120
|
+
};
|
|
121
|
+
} else if (row.kind === "crm-object" && text(row.objectName, 256)) {
|
|
122
|
+
reference = {
|
|
123
|
+
...presentation,
|
|
124
|
+
kind: "crm-object",
|
|
125
|
+
objectName: row.objectName,
|
|
126
|
+
...(text(row.objectId, 256) ? { objectId: row.objectId } : {}),
|
|
127
|
+
};
|
|
128
|
+
} else return null;
|
|
129
|
+
return reference.href === workspaceReferenceHref(reference)
|
|
130
|
+
? reference
|
|
131
|
+
: null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Bound complete serialized metadata, including its coverage, in UTF-8 bytes. */
|
|
135
|
+
export function boundWorkspaceReferences(
|
|
136
|
+
candidates: readonly WorkspaceReference[],
|
|
137
|
+
available?: number,
|
|
138
|
+
): WorkspaceReferences {
|
|
139
|
+
const unique = new Map<string, WorkspaceReference>();
|
|
140
|
+
for (const candidate of candidates) {
|
|
141
|
+
const reference = parseWorkspaceReference(candidate);
|
|
142
|
+
if (reference) unique.set(workspaceReferenceKey(reference), reference);
|
|
143
|
+
}
|
|
144
|
+
const count = Math.max(
|
|
145
|
+
unique.size,
|
|
146
|
+
typeof available === "number" && Number.isFinite(available)
|
|
147
|
+
? Math.max(0, Math.trunc(available))
|
|
148
|
+
: unique.size,
|
|
149
|
+
);
|
|
150
|
+
const result: WorkspaceReferences = {
|
|
151
|
+
references: [],
|
|
152
|
+
referenceCoverage: { returned: 0, available: count, truncated: count > 0 },
|
|
153
|
+
};
|
|
154
|
+
const encoder = new TextEncoder();
|
|
155
|
+
for (const reference of unique.values()) {
|
|
156
|
+
if (result.references.length >= WORKSPACE_REFERENCE_LIMIT) break;
|
|
157
|
+
result.references.push(reference);
|
|
158
|
+
result.referenceCoverage.returned = result.references.length;
|
|
159
|
+
result.referenceCoverage.truncated = result.references.length < count;
|
|
160
|
+
if (
|
|
161
|
+
encoder.encode(JSON.stringify(result)).byteLength >
|
|
162
|
+
WORKSPACE_REFERENCE_BYTE_LIMIT
|
|
163
|
+
) {
|
|
164
|
+
result.references.pop();
|
|
165
|
+
result.referenceCoverage.returned = result.references.length;
|
|
166
|
+
result.referenceCoverage.truncated = result.references.length < count;
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** Add shareable links only to the explicit envelope, never inside user data. */
|
|
174
|
+
export function withWorkspaceReferenceEnvelopeUrls(
|
|
175
|
+
value: unknown,
|
|
176
|
+
context: { host?: string; workspaceSlug?: string },
|
|
177
|
+
): unknown {
|
|
178
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return value;
|
|
179
|
+
const record = value as Record<string, unknown>;
|
|
180
|
+
if (!Array.isArray(record.references)) return value;
|
|
181
|
+
const coverage = record.referenceCoverage as
|
|
182
|
+
| Partial<WorkspaceReferenceCoverage>
|
|
183
|
+
| undefined;
|
|
184
|
+
if (
|
|
185
|
+
!coverage ||
|
|
186
|
+
typeof coverage !== "object" ||
|
|
187
|
+
Array.isArray(coverage) ||
|
|
188
|
+
!Number.isSafeInteger(coverage.returned) ||
|
|
189
|
+
coverage.returned !== record.references.length ||
|
|
190
|
+
typeof coverage.available !== "number" ||
|
|
191
|
+
!Number.isSafeInteger(coverage.available) ||
|
|
192
|
+
coverage.available < coverage.returned ||
|
|
193
|
+
typeof coverage.truncated !== "boolean" ||
|
|
194
|
+
coverage.truncated !== coverage.available > coverage.returned
|
|
195
|
+
)
|
|
196
|
+
return value;
|
|
197
|
+
const refs: WorkspaceReference[] = [];
|
|
198
|
+
for (const raw of record.references) {
|
|
199
|
+
const reference = parseWorkspaceReference(raw);
|
|
200
|
+
if (!reference) return value;
|
|
201
|
+
refs.push(withWorkspaceReferenceUrls(reference, context));
|
|
202
|
+
}
|
|
203
|
+
const metadata = boundWorkspaceReferences(refs, coverage.available);
|
|
204
|
+
return { ...record, ...metadata };
|
|
205
|
+
}
|