@company-semantics/contracts 58.0.0 → 58.2.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/package.json +4 -4
- package/src/__tests__/resource-keys.test.ts +30 -0
- package/src/api/generated-spec-hash.ts +2 -2
- package/src/api/generated.ts +33 -1
- package/src/chat/README.md +15 -4
- package/src/chat/__tests__/proactive-kind.test.ts +51 -0
- package/src/chat/index.ts +9 -0
- package/src/chat/proactive-kind.ts +51 -0
- package/src/chat/schemas.ts +92 -1
- package/src/chat/types.ts +19 -1
- package/src/index.ts +110 -0
- package/src/message-parts/README.md +5 -0
- package/src/message-parts/__tests__/suggested-replies.test.ts +52 -0
- package/src/message-parts/__tests__/wire.test.ts +48 -0
- package/src/message-parts/index.ts +8 -0
- package/src/message-parts/suggested-replies.ts +48 -0
- package/src/message-parts/types.ts +7 -1
- package/src/message-parts/wire.ts +26 -0
- package/src/notifications/__tests__/__snapshots__/monospace-budget.test.ts.snap +1 -0
- package/src/notifications/__tests__/__snapshots__/registry.test.ts.snap +1 -0
- package/src/notifications/__tests__/__snapshots__/render-snapshot.test.ts.snap +207 -0
- package/src/notifications/__tests__/fixtures.ts +9 -0
- package/src/notifications/__tests__/org-invite.test.ts +75 -0
- package/src/notifications/__tests__/render-snapshot.test.ts +8 -0
- package/src/notifications/kinds/org-invite.ts +27 -12
- package/src/notifications/payloads.ts +7 -0
- package/src/org/README.md +38 -0
- package/src/org/__tests__/canonical-facts.test.ts +118 -0
- package/src/org/__tests__/structure-inference.test.ts +392 -0
- package/src/org/__tests__/structure-provenance.test.ts +187 -0
- package/src/org/canonical-facts.ts +94 -1
- package/src/org/index.ts +54 -0
- package/src/org/schemas.ts +23 -0
- package/src/org/structure-inference.ts +521 -0
- package/src/proactive/README.md +125 -0
- package/src/proactive/__tests__/README.md +56 -0
- package/src/proactive/__tests__/chat-templates.test.ts +167 -0
- package/src/proactive/__tests__/compile-fixtures.ts +110 -0
- package/src/proactive/__tests__/vocabulary.test.ts +279 -0
- package/src/proactive/classes.ts +125 -0
- package/src/proactive/composer.ts +104 -0
- package/src/proactive/facts.ts +87 -0
- package/src/proactive/index.ts +52 -0
- package/src/proactive/kinds.ts +127 -0
- package/src/proactive/plan.ts +79 -0
- package/src/proactive/registry.ts +71 -0
- package/src/proactive/surfaces.ts +59 -0
- package/src/proactive/templates/README.md +58 -0
- package/src/proactive/templates/index.ts +32 -0
- package/src/proactive/templates/morning-brief.ts +77 -0
- package/src/proactive/templates/org-became-shared.ts +54 -0
- package/src/resource-key-types.ts +9 -0
- package/src/resource-keys.ts +2 -0
- package/src/user-notifications/README.md +10 -0
- package/src/user-notifications/kinds.ts +28 -0
package/src/index.ts
CHANGED
|
@@ -324,6 +324,49 @@ export type {
|
|
|
324
324
|
UserNotificationKind,
|
|
325
325
|
} from "./user-notifications/index";
|
|
326
326
|
|
|
327
|
+
// Proactive delivery — how the system speaks first (ADR-CONTRACTS-142). A
|
|
328
|
+
// COMPOSER above the three notification vocabularies, not a fourth peer: the
|
|
329
|
+
// presentation class is a TOTAL FUNCTION from class to surfaces
|
|
330
|
+
// (CLASS_SURFACES, checked in both directions by the compiler), and a kind
|
|
331
|
+
// carries its class plus the IDS each surface needs — which banner, which chat
|
|
332
|
+
// template, which inbox kind — never the surface booleans. The born-read rule
|
|
333
|
+
// is stated once (bornRead) so the backend imports it rather than re-deriving
|
|
334
|
+
// it per call site. Pushed-chat prose lives here too (PROACTIVE_CHAT_COMPOSERS,
|
|
335
|
+
// total over the templates): the backend hands a composer facts and writes what
|
|
336
|
+
// comes back, FROZEN into the transcript — so a composer may only interpolate
|
|
337
|
+
// facts the recipient was entitled to at compose time. See src/proactive/README.md.
|
|
338
|
+
export {
|
|
339
|
+
PROACTIVE_PRESENTATION_CLASSES,
|
|
340
|
+
CLASS_SURFACES,
|
|
341
|
+
bornRead,
|
|
342
|
+
ORG_SYSTEM_EVENT_TYPES,
|
|
343
|
+
PROACTIVE_CHAT_TEMPLATES,
|
|
344
|
+
PROACTIVE_EVENT_KIND_IDS,
|
|
345
|
+
PROACTIVE_RECIPIENT_REASONS,
|
|
346
|
+
PROACTIVE_PROJECTIONS,
|
|
347
|
+
PROACTIVE_EVENT_KINDS,
|
|
348
|
+
PROACTIVE_CHAT_MAX_REPLIES,
|
|
349
|
+
PROACTIVE_CHAT_COMPOSERS,
|
|
350
|
+
} from "./proactive/index";
|
|
351
|
+
|
|
352
|
+
export type {
|
|
353
|
+
ProactivePresentationClass,
|
|
354
|
+
BadgeCarrier,
|
|
355
|
+
ClassSurfacePlan,
|
|
356
|
+
OrgSystemEventType,
|
|
357
|
+
ProactiveChatTemplate,
|
|
358
|
+
ProactiveEventKind,
|
|
359
|
+
ProactiveEventId,
|
|
360
|
+
ProactiveRecipientReason,
|
|
361
|
+
ProactiveProjection,
|
|
362
|
+
ProactiveEventDefinition,
|
|
363
|
+
OrgBecameSharedFacts,
|
|
364
|
+
MorningBriefFacts,
|
|
365
|
+
ProactiveChatFacts,
|
|
366
|
+
ProactiveChatComposer,
|
|
367
|
+
ProactiveChatMessage,
|
|
368
|
+
} from "./proactive/index";
|
|
369
|
+
|
|
327
370
|
// Comment wire contract — where a thread hangs (the anchor union), what a
|
|
328
371
|
// thread and its comments look like on the wire, and who a comment may name.
|
|
329
372
|
// The anchor is the single most load-bearing shape in this package: the backend
|
|
@@ -481,11 +524,21 @@ export type {
|
|
|
481
524
|
// Runtime profile types (PRD-00229)
|
|
482
525
|
ChatRuntimeProfile,
|
|
483
526
|
ChatRuntimeProfileInfo,
|
|
527
|
+
// Proactive chat wire vocabulary (ADR-CONTRACTS-142): how a chat was born,
|
|
528
|
+
// the occurrence-to-chat resolution states, and the recognized form of the
|
|
529
|
+
// wire-lenient `proactiveKind` string
|
|
530
|
+
ChatOrigin,
|
|
531
|
+
ProactiveChatResolution,
|
|
532
|
+
RecognizedProactiveKind,
|
|
484
533
|
} from "./chat/index";
|
|
485
534
|
|
|
486
535
|
export {
|
|
487
536
|
CHAT_RUNTIME_PROFILES,
|
|
488
537
|
DEFAULT_CHAT_RUNTIME_PROFILE,
|
|
538
|
+
// `proactiveKind` is a bare string on the wire so a new kind cannot make an
|
|
539
|
+
// older client reject the whole chat list; this is the ONE place it becomes
|
|
540
|
+
// a ProactiveEventKind. See src/chat/proactive-kind.ts.
|
|
541
|
+
recognizeProactiveKind,
|
|
489
542
|
} from "./chat/index";
|
|
490
543
|
|
|
491
544
|
// Organization domain types
|
|
@@ -798,20 +851,72 @@ export type {
|
|
|
798
851
|
export {
|
|
799
852
|
CanonicalPersonSchema,
|
|
800
853
|
CanonicalOrgUnitSchema,
|
|
854
|
+
CanonicalPositionStatusSchema,
|
|
801
855
|
CanonicalPositionSchema,
|
|
802
856
|
CanonicalOccupancySchema,
|
|
803
857
|
CanonicalReportingEdgeSchema,
|
|
858
|
+
CanonicalUnresolvedManagerResolutionSchema,
|
|
859
|
+
CanonicalUnresolvedManagerSchema,
|
|
804
860
|
CanonicalFactsSchema,
|
|
805
861
|
} from "./org/index";
|
|
806
862
|
export type {
|
|
807
863
|
CanonicalPerson,
|
|
808
864
|
CanonicalOrgUnit,
|
|
865
|
+
CanonicalPositionStatus,
|
|
809
866
|
CanonicalPosition,
|
|
810
867
|
CanonicalOccupancy,
|
|
811
868
|
CanonicalReportingEdge,
|
|
869
|
+
CanonicalUnresolvedManagerResolution,
|
|
870
|
+
CanonicalUnresolvedManager,
|
|
812
871
|
CanonicalFacts,
|
|
813
872
|
} from "./org/index";
|
|
814
873
|
|
|
874
|
+
// Org-structure inference: a StructureInferenceSnapshot of the persisted graph
|
|
875
|
+
// goes in, a StructureProposal — a proposed desired state, never database
|
|
876
|
+
// commands — comes out. Typed evidence, targetUnitId for durable identity, and
|
|
877
|
+
// exactly one PersonStructureOutcome per person in the snapshot. (PRD-00955)
|
|
878
|
+
//
|
|
879
|
+
// Acceptance attaches StructureProvenance — proposalOrigin, acceptedBy and
|
|
880
|
+
// authority as three ORTHOGONAL axes, so accepting an AI proposal never rewrites
|
|
881
|
+
// its origin as manual. mayOverrideAuthority is the single definition of
|
|
882
|
+
// precedence, and it consults `authority` only. (PRD-00956)
|
|
883
|
+
export {
|
|
884
|
+
StructureEvidenceSchema,
|
|
885
|
+
TOPOLOGY_ONLY_EVIDENCE_KINDS,
|
|
886
|
+
StructureUnitAuthoritySchema,
|
|
887
|
+
StructureProposalOriginSchema,
|
|
888
|
+
StructureAcceptedBySchema,
|
|
889
|
+
StructureAuthoritySchema,
|
|
890
|
+
StructureProvenanceSchema,
|
|
891
|
+
mayOverrideAuthority,
|
|
892
|
+
ProposedOrgUnitSchema,
|
|
893
|
+
PersonStructureOutcomeSchema,
|
|
894
|
+
StructureReviewItemSchema,
|
|
895
|
+
StructureProposalSchema,
|
|
896
|
+
StructurePersonFactSchema,
|
|
897
|
+
StructureReportingFactSchema,
|
|
898
|
+
ExistingUnitFactSchema,
|
|
899
|
+
ExistingPlacementFactSchema,
|
|
900
|
+
StructureInferenceSnapshotSchema,
|
|
901
|
+
} from "./org/index";
|
|
902
|
+
export type {
|
|
903
|
+
StructureEvidence,
|
|
904
|
+
StructureUnitAuthority,
|
|
905
|
+
StructureProposalOrigin,
|
|
906
|
+
StructureAcceptedBy,
|
|
907
|
+
StructureAuthority,
|
|
908
|
+
StructureProvenance,
|
|
909
|
+
ProposedOrgUnit,
|
|
910
|
+
PersonStructureOutcome,
|
|
911
|
+
StructureReviewItem,
|
|
912
|
+
StructureProposal,
|
|
913
|
+
StructurePersonFact,
|
|
914
|
+
StructureReportingFact,
|
|
915
|
+
ExistingUnitFact,
|
|
916
|
+
ExistingPlacementFact,
|
|
917
|
+
StructureInferenceSnapshot,
|
|
918
|
+
} from "./org/index";
|
|
919
|
+
|
|
815
920
|
// Authority & Delegation vocabulary
|
|
816
921
|
export {
|
|
817
922
|
AuthoritySourceSchema,
|
|
@@ -909,6 +1014,11 @@ export type {
|
|
|
909
1014
|
InteractiveTaskData,
|
|
910
1015
|
InteractiveTaskPart,
|
|
911
1016
|
InteractiveTaskDataPart,
|
|
1017
|
+
// Suggested replies surface types (non-governed chips, PRD-00958)
|
|
1018
|
+
SuggestedReply,
|
|
1019
|
+
SuggestedRepliesData,
|
|
1020
|
+
SuggestedRepliesPart,
|
|
1021
|
+
SuggestedRepliesDataPart,
|
|
912
1022
|
// Execution result types (Phase 5)
|
|
913
1023
|
ExecutionArtifactStatus,
|
|
914
1024
|
ExecutionResultSummary,
|
|
@@ -14,6 +14,7 @@ Canonical vocabulary for structured assistant message output. Defines the type s
|
|
|
14
14
|
- Confirmation is deterministic — no natural language inference ("yes" is invalid)
|
|
15
15
|
- Execution results carry `state` resolved from ExecutionState (single authority, no redundant status)
|
|
16
16
|
- Undo creates append-only audit rows — original execution is never mutated
|
|
17
|
+
- `SuggestedRepliesPart` is a surface but NOT a governed one: a chip fires an ordinary user turn (no `executionId`, no `submitEndpoint`), is stateless (clicking never consumes it) and advisory (the composer stays enabled) — so it is outside the at-most-one-governed-surface-per-turn rule
|
|
17
18
|
|
|
18
19
|
<!-- BEGIN GENERATED: readme-public-api — derived from code by `pnpm readme-api`. Do not edit. -->
|
|
19
20
|
|
|
@@ -65,6 +66,10 @@ Canonical vocabulary for structured assistant message output. Defines the type s
|
|
|
65
66
|
- `StatusPanelEntry` _(type)_ — Single entry in a status panel.
|
|
66
67
|
- `StatusPanelPart` _(type)_ — Status panel surface part.
|
|
67
68
|
- `StreamPhase` _(type)_ — Derived stream phase for UI state management.
|
|
69
|
+
- `SuggestedRepliesData` _(type)_ — Suggested replies surface data payload.
|
|
70
|
+
- `SuggestedRepliesDataPart` _(type)_ — Wire form, as persisted in `chat_messages.parts`.
|
|
71
|
+
- `SuggestedRepliesPart` _(type)_ — Normalized form, as rendered.
|
|
72
|
+
- `SuggestedReply` _(type)_ — Suggested follow-up chips beneath an assistant turn.
|
|
68
73
|
- `SurfacePart` _(type)_ — Surface parts are rendered atomically (never streamed).
|
|
69
74
|
- `TablePart` _(type)_ — Table surface part.
|
|
70
75
|
- `TextPart` _(type)_ — Text content part.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { isSurfacePart, isTextPart } from "../types.js";
|
|
3
|
+
import type { AssistantMessagePart, SurfacePart } from "../types.js";
|
|
4
|
+
import { createPartBuilder, addSurface, addText } from "../builder.js";
|
|
5
|
+
import type { SuggestedRepliesPart } from "../suggested-replies.js";
|
|
6
|
+
|
|
7
|
+
// The semantic part is what the app renders after normalizing the wire part.
|
|
8
|
+
// Typing the fixture as SuggestedRepliesPart and then assigning it to
|
|
9
|
+
// SurfacePart is the compile-time proof that the union gained the member.
|
|
10
|
+
const chips: SuggestedRepliesPart = {
|
|
11
|
+
type: "suggested-replies",
|
|
12
|
+
data: {
|
|
13
|
+
replies: [
|
|
14
|
+
{ id: "r1", label: "What moved?" },
|
|
15
|
+
{ id: "r2", label: "Undo", prompt: "Undo the last change" },
|
|
16
|
+
],
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
describe("SuggestedRepliesPart in the SurfacePart union", () => {
|
|
21
|
+
it("is assignable to SurfacePart and AssistantMessagePart", () => {
|
|
22
|
+
const asSurface: SurfacePart = chips;
|
|
23
|
+
const asPart: AssistantMessagePart = chips;
|
|
24
|
+
expect(asSurface.type).toBe("suggested-replies");
|
|
25
|
+
expect(asPart.type).toBe("suggested-replies");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("is classified as a surface part, not a text part", () => {
|
|
29
|
+
expect(isSurfacePart(chips)).toBe(true);
|
|
30
|
+
expect(isTextPart(chips)).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// The builder's narrative-before-surface state machine applies to chips like
|
|
34
|
+
// any other surface: they are emitted after the prose, never streamed.
|
|
35
|
+
it("is accepted by addSurface after narrative text", () => {
|
|
36
|
+
let state = createPartBuilder();
|
|
37
|
+
state = addText(state, "Five settings sections moved.").state;
|
|
38
|
+
const result = addSurface(state, chips);
|
|
39
|
+
expect(result.accepted).toBe(true);
|
|
40
|
+
expect(result.state.parts).toEqual([
|
|
41
|
+
{ type: "text", text: "Five settings sections moved." },
|
|
42
|
+
chips,
|
|
43
|
+
]);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("does not consume: the same part can be added to two builders unchanged", () => {
|
|
47
|
+
const a = addSurface(createPartBuilder(), chips).state.parts[0];
|
|
48
|
+
const b = addSurface(createPartBuilder(), chips).state.parts[0];
|
|
49
|
+
expect(a).toEqual(b);
|
|
50
|
+
expect(a).toEqual(chips);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -3,6 +3,7 @@ import { WireSurfaceBuilder } from "../wire.js";
|
|
|
3
3
|
import type { MCPToolDescriptor } from "../../mcp/index.js";
|
|
4
4
|
import type { ExecutionResultData } from "../execution.js";
|
|
5
5
|
import type { UndoResultData } from "../execution.js";
|
|
6
|
+
import type { SuggestedRepliesData } from "../suggested-replies.js";
|
|
6
7
|
|
|
7
8
|
// Deliberately partial fixtures: toolList is a pure wrapper that passes `tools`
|
|
8
9
|
// straight through, so these assert the wrapping, not descriptor validity. Cast
|
|
@@ -90,6 +91,53 @@ describe("WireSurfaceBuilder.undoResult", () => {
|
|
|
90
91
|
});
|
|
91
92
|
});
|
|
92
93
|
|
|
94
|
+
describe("WireSurfaceBuilder.suggestedReplies", () => {
|
|
95
|
+
const data: SuggestedRepliesData = {
|
|
96
|
+
replies: [
|
|
97
|
+
{ id: "r1", label: "Show me what moved" },
|
|
98
|
+
{
|
|
99
|
+
id: "r2",
|
|
100
|
+
label: "Why?",
|
|
101
|
+
prompt: "Why did these settings move out of My settings?",
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
it("returns data part with type data-suggested-replies", () => {
|
|
107
|
+
const result = WireSurfaceBuilder.suggestedReplies(data);
|
|
108
|
+
expect(result.type).toBe("data-suggested-replies");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("data matches input SuggestedRepliesData exactly", () => {
|
|
112
|
+
const result = WireSurfaceBuilder.suggestedReplies(data);
|
|
113
|
+
expect(result.data).toEqual(data);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// A chip is an ordinary user turn: the part carries nothing that would make
|
|
117
|
+
// it a governed surface. Pinning the key set catches an accidental
|
|
118
|
+
// executionId/submitEndpoint creeping onto the payload.
|
|
119
|
+
it("carries no governed-surface fields (no executionId, no submitEndpoint)", () => {
|
|
120
|
+
const result = WireSurfaceBuilder.suggestedReplies(data);
|
|
121
|
+
expect(Object.keys(result.data)).toEqual(["replies"]);
|
|
122
|
+
expect(result.data).not.toHaveProperty("executionId");
|
|
123
|
+
expect(result.data).not.toHaveProperty("submitEndpoint");
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Stateless: building twice from the same facts yields identical output, so
|
|
127
|
+
// a historical message re-rendered later cannot differ from the original.
|
|
128
|
+
it("is deterministic across repeated builds", () => {
|
|
129
|
+
expect(WireSurfaceBuilder.suggestedReplies(data)).toEqual(
|
|
130
|
+
WireSurfaceBuilder.suggestedReplies(data),
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("works with an empty replies array", () => {
|
|
135
|
+
const result = WireSurfaceBuilder.suggestedReplies({ replies: [] });
|
|
136
|
+
expect(result.type).toBe("data-suggested-replies");
|
|
137
|
+
expect(result.data.replies).toEqual([]);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
93
141
|
describe("WireSurfaceBuilder.messageStart", () => {
|
|
94
142
|
it("returns data part with type data-message-start", () => {
|
|
95
143
|
const result = WireSurfaceBuilder.messageStart("msg-123");
|
|
@@ -56,6 +56,14 @@ export type {
|
|
|
56
56
|
InteractiveTaskDataPart,
|
|
57
57
|
} from "./interactive";
|
|
58
58
|
|
|
59
|
+
// Suggested replies surface types (non-governed chips)
|
|
60
|
+
export type {
|
|
61
|
+
SuggestedReply,
|
|
62
|
+
SuggestedRepliesData,
|
|
63
|
+
SuggestedRepliesPart,
|
|
64
|
+
SuggestedRepliesDataPart,
|
|
65
|
+
} from "./suggested-replies";
|
|
66
|
+
|
|
59
67
|
// Execution result types
|
|
60
68
|
export type {
|
|
61
69
|
ExecutionArtifactStatus,
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Suggested follow-up chips beneath an assistant turn.
|
|
3
|
+
*
|
|
4
|
+
* INVARIANTS — these are what keep this OUT of the governed
|
|
5
|
+
* preview/confirmation/interactive triad:
|
|
6
|
+
*
|
|
7
|
+
* - A chip fires an ORDINARY USER TURN. No `executionId`, no `submitEndpoint`,
|
|
8
|
+
* no side effect of its own. That is precisely why it is not a governed
|
|
9
|
+
* surface and is not subject to the at-most-one-governed-surface-per-turn
|
|
10
|
+
* rule.
|
|
11
|
+
* - STATELESS. Clicking does not consume it; the historical message renders
|
|
12
|
+
* identically forever, because the turn it produced is already in the
|
|
13
|
+
* transcript below it.
|
|
14
|
+
* - ADVISORY and non-exhaustive. The composer box stays enabled.
|
|
15
|
+
*
|
|
16
|
+
* Purely additive: an app that predates this part normalizes unknown part
|
|
17
|
+
* types to null, so it drops the chips and still renders the prose.
|
|
18
|
+
*
|
|
19
|
+
* @see decisions/ADR-CONT-026.md for the message-parts design
|
|
20
|
+
* @see decisions/ADR-CONTRACTS-142-proactive-vocabulary-composes-the-three-notification-unions.md
|
|
21
|
+
*/
|
|
22
|
+
export interface SuggestedReply {
|
|
23
|
+
/** Stable within the part. Telemetry key and React key; never a server id. */
|
|
24
|
+
readonly id: string;
|
|
25
|
+
/** What the chip SAYS. */
|
|
26
|
+
readonly label: string;
|
|
27
|
+
/** What the chip SENDS, when it differs from the label. */
|
|
28
|
+
readonly prompt?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Suggested replies surface data payload.
|
|
33
|
+
*/
|
|
34
|
+
export interface SuggestedRepliesData {
|
|
35
|
+
readonly replies: readonly SuggestedReply[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Normalized form, as rendered. */
|
|
39
|
+
export interface SuggestedRepliesPart {
|
|
40
|
+
type: "suggested-replies";
|
|
41
|
+
data: SuggestedRepliesData;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Wire form, as persisted in `chat_messages.parts`. */
|
|
45
|
+
export interface SuggestedRepliesDataPart {
|
|
46
|
+
type: "data-suggested-replies";
|
|
47
|
+
data: SuggestedRepliesData;
|
|
48
|
+
}
|
|
@@ -17,6 +17,7 @@ import type { ToolListMessagePart } from "../mcp/index";
|
|
|
17
17
|
import type { PreviewPart } from "./preview";
|
|
18
18
|
import type { ConfirmationPart } from "./confirmation";
|
|
19
19
|
import type { InteractiveTaskPart } from "./interactive";
|
|
20
|
+
import type { SuggestedRepliesPart } from "./suggested-replies";
|
|
20
21
|
|
|
21
22
|
// =============================================================================
|
|
22
23
|
// Narrative Parts (Streamable)
|
|
@@ -112,6 +113,10 @@ export interface TablePart {
|
|
|
112
113
|
/**
|
|
113
114
|
* Surface parts are rendered atomically (never streamed).
|
|
114
115
|
* Extensible: add new surface types to this union.
|
|
116
|
+
*
|
|
117
|
+
* SuggestedRepliesPart is a surface part but NOT a governed one: a chip fires
|
|
118
|
+
* an ordinary user turn, so it is outside the preview/confirmation/interactive
|
|
119
|
+
* triad and its at-most-one-governed-surface-per-turn rule.
|
|
115
120
|
*/
|
|
116
121
|
export type SurfacePart =
|
|
117
122
|
| ToolListPart
|
|
@@ -120,7 +125,8 @@ export type SurfacePart =
|
|
|
120
125
|
| TablePart
|
|
121
126
|
| ConfirmationPart
|
|
122
127
|
| PreviewPart
|
|
123
|
-
| InteractiveTaskPart
|
|
128
|
+
| InteractiveTaskPart
|
|
129
|
+
| SuggestedRepliesPart;
|
|
124
130
|
|
|
125
131
|
/**
|
|
126
132
|
* All assistant message part types.
|
|
@@ -17,6 +17,10 @@ import type {
|
|
|
17
17
|
InteractiveTaskData,
|
|
18
18
|
InteractiveTaskDataPart,
|
|
19
19
|
} from "./interactive";
|
|
20
|
+
import type {
|
|
21
|
+
SuggestedRepliesData,
|
|
22
|
+
SuggestedRepliesDataPart,
|
|
23
|
+
} from "./suggested-replies";
|
|
20
24
|
import type {
|
|
21
25
|
ExecutionResultData,
|
|
22
26
|
ExecutionResultDataPart,
|
|
@@ -112,6 +116,28 @@ export const WireSurfaceBuilder = {
|
|
|
112
116
|
};
|
|
113
117
|
},
|
|
114
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Build a suggested-replies data part for streaming.
|
|
121
|
+
* Chips beneath an assistant turn that each fire an ORDINARY user turn.
|
|
122
|
+
*
|
|
123
|
+
* INVARIANTS:
|
|
124
|
+
* - Not a governed surface: no executionId, no submitEndpoint, no side
|
|
125
|
+
* effect of its own — so it does not count against the
|
|
126
|
+
* at-most-one-governed-surface-per-turn rule
|
|
127
|
+
* - Stateless: clicking does not consume it; the historical message renders
|
|
128
|
+
* identically forever
|
|
129
|
+
* - Advisory and non-exhaustive: the composer box stays enabled
|
|
130
|
+
*
|
|
131
|
+
* @param data - Suggested replies data (the chip list)
|
|
132
|
+
* @returns Wire-format suggested replies part ready for stream
|
|
133
|
+
*/
|
|
134
|
+
suggestedReplies(data: SuggestedRepliesData): SuggestedRepliesDataPart {
|
|
135
|
+
return {
|
|
136
|
+
type: "data-suggested-replies",
|
|
137
|
+
data,
|
|
138
|
+
};
|
|
139
|
+
},
|
|
140
|
+
|
|
115
141
|
/**
|
|
116
142
|
* Build an execution result data part for streaming.
|
|
117
143
|
* Reports the outcome of an executed action.
|
|
@@ -11,6 +11,7 @@ exports[`monospace budget > overhangs only where content is atomic 1`] = `
|
|
|
11
11
|
"https://app.companysemantics.ai/doc/handbook?thread=th_456",
|
|
12
12
|
"https://app.companysemantics.ai/invite/abc123",
|
|
13
13
|
"https://app.companysemantics.ai/invite/def456",
|
|
14
|
+
"https://app.companysemantics.ai/invite/ghi789",
|
|
14
15
|
"https://app.companysemantics.ai/meeting/8842",
|
|
15
16
|
"https://app.companysemantics.ai/org/unit/42",
|
|
16
17
|
"https://app.companysemantics.ai/owner-transfer/accept/tok123",
|
|
@@ -24,6 +24,7 @@ exports[`NOTIFICATION_DEFINITIONS > titles every kind 1`] = `
|
|
|
24
24
|
"companyMd.access_requested · With message": "Someone requested access to a document",
|
|
25
25
|
"org.invite · Admin": "You've been invited to join Acme Corp on Company Semantics",
|
|
26
26
|
"org.invite · Member": "You've been invited to join Acme Corp on Company Semantics",
|
|
27
|
+
"org.invite · Member + note": "You've been invited to join Acme Corp on Company Semantics",
|
|
27
28
|
"org.ownership_transfer · No note (admin)": "You've been invited to become a workspace owner",
|
|
28
29
|
"org.ownership_transfer · With note + from": "You've been invited to become a workspace owner",
|
|
29
30
|
"org.ownership_transfer_completed · Default": "Workspace ownership has been transferred",
|
|
@@ -3481,6 +3481,213 @@ If you weren't expecting this, no action is
|
|
|
3481
3481
|
required.
|
|
3482
3482
|
|
|
3483
3483
|
|
|
3484
|
+
/* EOM */
|
|
3485
|
+
ⓒ 2026 • Company Semantics
|
|
3486
|
+
https://companysemantics.ai
|
|
3487
|
+
",
|
|
3488
|
+
}
|
|
3489
|
+
`;
|
|
3490
|
+
|
|
3491
|
+
exports[`email render > org.invite · Member + note 1`] = `
|
|
3492
|
+
{
|
|
3493
|
+
"amp": "<!DOCTYPE html>
|
|
3494
|
+
<html amp4email data-css-strict lang="en">
|
|
3495
|
+
<head><meta charset="utf-8">
|
|
3496
|
+
<script async src="https://cdn.ampproject.org/v0.js"></script>
|
|
3497
|
+
<style amp4email-boilerplate>body{visibility:hidden}</style>
|
|
3498
|
+
<style amp-custom>.csr-body { font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; color: #1a1a1a; background-color: #ffffff; margin: 0; padding: 0; }
|
|
3499
|
+
.csr-frame { max-width: 520px; margin: 0 auto; }
|
|
3500
|
+
.csr-p-normal { font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; margin: 0 0 20px 0; }
|
|
3501
|
+
.csr-p-tight { font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; margin: 0 0 4px 0; }
|
|
3502
|
+
.csr-p-none { font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; margin: 0; }
|
|
3503
|
+
.csr-eom { color: #bbb; }
|
|
3504
|
+
.csr-signature-link { color: #0047FF; text-decoration: none; }
|
|
3505
|
+
.csr-warning { color: #e7000b; }
|
|
3506
|
+
.csr-hr-normal { border: none; border-top: 1px solid #bbb; margin: 0 0 20px 0; }
|
|
3507
|
+
.csr-hr-tight { border: none; border-top: 1px solid #bbb; margin: 0 0 4px 0; }
|
|
3508
|
+
.csr-hr-none { border: none; border-top: 1px solid #bbb; margin: 0; }
|
|
3509
|
+
.csr-hero-normal { max-width: 100%; margin: 0 0 20px 0; }
|
|
3510
|
+
.csr-hero-tight { max-width: 100%; margin: 0 0 4px 0; }
|
|
3511
|
+
.csr-hero-none { max-width: 100%; margin: 0; }
|
|
3512
|
+
.csr-cta-box-normal { display: inline-block; border: 1px solid #0047FF; border-radius: 2px; margin: 0 0 20px 0; max-width: 220px; }
|
|
3513
|
+
.csr-cta-box-tight { display: inline-block; border: 1px solid #0047FF; border-radius: 2px; margin: 0 0 4px 0; max-width: 220px; }
|
|
3514
|
+
.csr-cta-box-none { display: inline-block; border: 1px solid #0047FF; border-radius: 2px; margin: 0; max-width: 220px; }
|
|
3515
|
+
.csr-cta-cell-linked { text-align: center; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; }
|
|
3516
|
+
.csr-cta-cell-code { padding: 16px 24px; text-align: center; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; }
|
|
3517
|
+
.csr-cta-label { text-decoration: underline; }
|
|
3518
|
+
.csr-cta-anchor { display: block; padding: 16px 24px; color: #0047FF; text-decoration: none; }
|
|
3519
|
+
.csr-cta-code { color: #0047FF; }
|
|
3520
|
+
.csr-chat-rule-top { border: none; border-top: 1px solid #bbb; margin: 12px 0 24px 0; }
|
|
3521
|
+
.csr-chat-rule-bottom { border: none; border-top: 1px solid #bbb; margin: 24px 0 12px 0; }
|
|
3522
|
+
.csr-chat-row-24 { margin: 0 0 24px 0; }
|
|
3523
|
+
.csr-chat-row-16 { margin: 0 0 16px 0; }
|
|
3524
|
+
.csr-chat-attribution { font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; color: #666; text-align: right; padding-top: 6px; padding-right: 1ch; }
|
|
3525
|
+
.csr-chat-avatar-left { font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; padding-right: 8px; vertical-align: bottom; white-space: nowrap; }
|
|
3526
|
+
.csr-chat-avatar-left-hidden { font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; padding-right: 8px; vertical-align: bottom; visibility: hidden; white-space: nowrap; }
|
|
3527
|
+
.csr-chat-avatar-right { font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; padding-left: 8px; vertical-align: bottom; white-space: nowrap; }
|
|
3528
|
+
.csr-chat-avatar-right-hidden { font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; padding-left: 8px; vertical-align: bottom; visibility: hidden; white-space: nowrap; }
|
|
3529
|
+
.csr-chat-channel-left { width: 100%; text-align: left; }
|
|
3530
|
+
.csr-chat-channel-right { width: 100%; text-align: right; }
|
|
3531
|
+
.csr-chat-bubble-wrap { display: inline-block; max-width: 100%; vertical-align: bottom; }
|
|
3532
|
+
.csr-bubble-user { border-radius: 8px 8px 0 8px; padding: 10px 14px; text-align: right; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; color: #ffffff; background: #666; }
|
|
3533
|
+
.csr-bubble-assistant { border-radius: 8px 8px 8px 0; padding: 10px 14px; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; color: #ffffff; background: #666; }
|
|
3534
|
+
.csr-dots-over-cta { font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 20px; font-weight: bold; color: #666; text-align: center; margin: 0 0 16px 0; }
|
|
3535
|
+
.csr-dots-cell { width: 100%; text-align: center; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 20px; font-weight: bold; color: #666; }
|
|
3536
|
+
.csr-cta-stack { display: inline-block; text-align: left; }
|
|
3537
|
+
.cs-cta-hover:hover { background-color: #d1ffff; }</style></head>
|
|
3538
|
+
<body class="csr-body">
|
|
3539
|
+
<div class="csr-frame">
|
|
3540
|
+
<p class="csr-p-normal">Hi there,</p>
|
|
3541
|
+
<p class="csr-p-normal">Acme Corp uses Company Semantics.</p>
|
|
3542
|
+
<hr class="cs-faint csr-chat-rule-top">
|
|
3543
|
+
<table cellpadding="0" cellspacing="0" border="0" width="100%" class="csr-chat-row-24">
|
|
3544
|
+
<tr>
|
|
3545
|
+
<td class="csr-chat-avatar-left-hidden">[c_S]</td>
|
|
3546
|
+
<td class="csr-chat-channel-right"><table cellpadding="0" cellspacing="0" border="0" class="csr-chat-bubble-wrap">
|
|
3547
|
+
<tr><td class="cs-bubble csr-bubble-user">We start planning next quarter on Monday — glad you are joining.</td></tr>
|
|
3548
|
+
</table></td>
|
|
3549
|
+
<td class="csr-chat-avatar-right">(•̀_ರ╮)</td>
|
|
3550
|
+
</tr>
|
|
3551
|
+
<tr>
|
|
3552
|
+
<td></td>
|
|
3553
|
+
<td class="cs-meta csr-chat-attribution">Alex Rivera</td>
|
|
3554
|
+
<td></td>
|
|
3555
|
+
</tr>
|
|
3556
|
+
</table>
|
|
3557
|
+
<table cellpadding="0" cellspacing="0" border="0" width="100%" class="csr-chat-row-16">
|
|
3558
|
+
<tr>
|
|
3559
|
+
<td class="csr-chat-avatar-left">[c_S]</td>
|
|
3560
|
+
<td class="csr-chat-channel-left"><table cellpadding="0" cellspacing="0" border="0" class="csr-chat-bubble-wrap">
|
|
3561
|
+
<tr><td class="cs-bubble csr-bubble-assistant">Join to accept the invitation.</td></tr>
|
|
3562
|
+
</table></td>
|
|
3563
|
+
<td class="csr-chat-avatar-right-hidden">(•̀_ರ╮)</td>
|
|
3564
|
+
</tr>
|
|
3565
|
+
</table>
|
|
3566
|
+
<table cellpadding="0" cellspacing="0" border="0" width="100%" class="csr-chat-row-24">
|
|
3567
|
+
<tr>
|
|
3568
|
+
<td class="csr-chat-avatar-left-hidden">[c_S]</td>
|
|
3569
|
+
<td class="csr-chat-channel-left"><div class="csr-cta-stack"><table cellpadding="0" cellspacing="0" border="0" class="cs-cta cs-cta-hover csr-cta-box-none">
|
|
3570
|
+
<tr><td class="csr-cta-cell-linked">
|
|
3571
|
+
<a href="https://app.companysemantics.ai/invite/ghi789" class="cs-link csr-cta-anchor">>> <span class="csr-cta-label">JOIN</span> <<</a>
|
|
3572
|
+
</td></tr>
|
|
3573
|
+
</table></div></td>
|
|
3574
|
+
<td class="csr-chat-avatar-right-hidden">(•̀_ರ╮)</td>
|
|
3575
|
+
</tr>
|
|
3576
|
+
</table>
|
|
3577
|
+
<hr class="cs-faint csr-chat-rule-bottom">
|
|
3578
|
+
<p class="csr-p-tight">From: Alex Rivera</p>
|
|
3579
|
+
<p class="csr-p-tight">Workspace: Acme Corp</p>
|
|
3580
|
+
<p class="csr-p-tight">Role: Member</p>
|
|
3581
|
+
<p class="csr-p-normal">Expires: Jun 13, 2026</p>
|
|
3582
|
+
<p class="csr-p-none">This invitation was sent via Company Semantics.<br>If you weren't expecting this, no action is required.</p>
|
|
3583
|
+
<p class="csr-p-none"><br><br><span class="cs-faint csr-eom">/* EOM */</span><br>ⓒ 2026 • Company Semantics<br><a href="https://companysemantics.ai" target="_blank" rel="noopener noreferrer" class="cs-link csr-signature-link">https://companysemantics.ai</a></p>
|
|
3584
|
+
</div>
|
|
3585
|
+
</body>
|
|
3586
|
+
</html>",
|
|
3587
|
+
"html": "<!DOCTYPE html>
|
|
3588
|
+
<html lang="en">
|
|
3589
|
+
<head><meta charset="UTF-8">
|
|
3590
|
+
<meta name="color-scheme" content="light dark">
|
|
3591
|
+
<meta name="supported-color-schemes" content="light dark">
|
|
3592
|
+
<style>.cs-cta-hover:hover { background-color: #d1ffff !important; }
|
|
3593
|
+
@media (prefers-color-scheme: dark) {
|
|
3594
|
+
body { background-color: #1a1a1a !important; color: #e8e8e8 !important; }
|
|
3595
|
+
.cs-faint { color: #666 !important; border-top-color: #666 !important; }
|
|
3596
|
+
.cs-meta { color: #8a8a8a !important; }
|
|
3597
|
+
.cs-cta { border-color: #00ffff !important; }
|
|
3598
|
+
.cs-cta-hover:hover { background-color: #154848 !important; }
|
|
3599
|
+
.cs-bubble { background: #154848 !important; color: #ffffff !important; }
|
|
3600
|
+
.cs-dots { color: #8a8a8a !important; }
|
|
3601
|
+
.cs-link { color: #00ffff !important; }
|
|
3602
|
+
.cs-destructive { color: #ff6b6b !important; }
|
|
3603
|
+
}</style>
|
|
3604
|
+
<!--[if mso]><style>.cs-cta td { padding: 16px 24px !important; }
|
|
3605
|
+
.cs-cta a { padding: 0 !important; }</style><![endif]--></head>
|
|
3606
|
+
<body style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; color: #1a1a1a; background-color: #ffffff; margin: 0; padding: 0;">
|
|
3607
|
+
<div style="max-width: 520px; margin: 0 auto;">
|
|
3608
|
+
<p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; margin: 0 0 20px 0;">Hi there,</p>
|
|
3609
|
+
<p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; margin: 0 0 20px 0;">Acme Corp uses Company Semantics.</p>
|
|
3610
|
+
<hr class="cs-faint" style="border: none; border-top: 1px solid #bbb; margin: 12px 0 24px 0;">
|
|
3611
|
+
<table cellpadding="0" cellspacing="0" border="0" width="100%" style="margin: 0 0 24px 0;">
|
|
3612
|
+
<tr>
|
|
3613
|
+
<td style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; padding-right: 8px; vertical-align: bottom; visibility: hidden; white-space: nowrap;">[c_S]</td>
|
|
3614
|
+
<td style="width: 100%; text-align: right;"><table cellpadding="0" cellspacing="0" border="0" style="display: inline-block; max-width: 100%; vertical-align: bottom;">
|
|
3615
|
+
<tr><td class="cs-bubble" style="border-radius: 8px 8px 0 8px; padding: 10px 14px; text-align: right; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; color: #ffffff; background: #666;">We start planning next quarter on Monday — glad you are joining.</td></tr>
|
|
3616
|
+
</table></td>
|
|
3617
|
+
<td style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; padding-left: 8px; vertical-align: bottom; white-space: nowrap;">(•̀_ರ╮)</td>
|
|
3618
|
+
</tr>
|
|
3619
|
+
<tr>
|
|
3620
|
+
<td></td>
|
|
3621
|
+
<td class="cs-meta" style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; color: #666; text-align: right; padding-top: 6px; padding-right: 1ch;">Alex Rivera</td>
|
|
3622
|
+
<td></td>
|
|
3623
|
+
</tr>
|
|
3624
|
+
</table>
|
|
3625
|
+
<table cellpadding="0" cellspacing="0" border="0" width="100%" style="margin: 0 0 16px 0;">
|
|
3626
|
+
<tr>
|
|
3627
|
+
<td style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; padding-right: 8px; vertical-align: bottom; white-space: nowrap;">[c_S]</td>
|
|
3628
|
+
<td style="width: 100%; text-align: left;"><table cellpadding="0" cellspacing="0" border="0" style="display: inline-block; max-width: 100%; vertical-align: bottom;">
|
|
3629
|
+
<tr><td class="cs-bubble" style="border-radius: 8px 8px 8px 0; padding: 10px 14px; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; color: #ffffff; background: #666;">Join to accept the invitation.</td></tr>
|
|
3630
|
+
</table></td>
|
|
3631
|
+
<td style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; padding-left: 8px; vertical-align: bottom; visibility: hidden; white-space: nowrap;">(•̀_ರ╮)</td>
|
|
3632
|
+
</tr>
|
|
3633
|
+
</table>
|
|
3634
|
+
<table cellpadding="0" cellspacing="0" border="0" width="100%" style="margin: 0 0 24px 0;">
|
|
3635
|
+
<tr>
|
|
3636
|
+
<td style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; padding-right: 8px; vertical-align: bottom; visibility: hidden; white-space: nowrap;">[c_S]</td>
|
|
3637
|
+
<td style="width: 100%; text-align: left;"><div style="display: inline-block; text-align: left;"><table cellpadding="0" cellspacing="0" border="0" class="cs-cta cs-cta-hover" style="display: inline-block; border: 1px solid #0047FF; border-radius: 2px; margin: 0; max-width: 220px;">
|
|
3638
|
+
<tr><td style="text-align: center; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px;">
|
|
3639
|
+
<a href="https://app.companysemantics.ai/invite/ghi789" class="cs-link" style="display: block; padding: 16px 24px; color: #0047FF; text-decoration: none;">>> <span style="text-decoration: underline;">JOIN</span> <<</a>
|
|
3640
|
+
</td></tr>
|
|
3641
|
+
</table></div></td>
|
|
3642
|
+
<td style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; padding-left: 8px; vertical-align: bottom; visibility: hidden; white-space: nowrap;">(•̀_ರ╮)</td>
|
|
3643
|
+
</tr>
|
|
3644
|
+
</table>
|
|
3645
|
+
<hr class="cs-faint" style="border: none; border-top: 1px solid #bbb; margin: 24px 0 12px 0;">
|
|
3646
|
+
<p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; margin: 0 0 4px 0;">From: Alex Rivera</p>
|
|
3647
|
+
<p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; margin: 0 0 4px 0;">Workspace: Acme Corp</p>
|
|
3648
|
+
<p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; margin: 0 0 4px 0;">Role: Member</p>
|
|
3649
|
+
<p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; margin: 0 0 20px 0;">Expires: Jun 13, 2026</p>
|
|
3650
|
+
<p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; margin: 0;">This invitation was sent via Company Semantics.<br>If you weren't expecting this, no action is required.</p>
|
|
3651
|
+
<p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; margin: 0;"><br><br><span class="cs-faint" style="color: #bbb;">/* EOM */</span><br>ⓒ 2026 • Company Semantics<br><a href="https://companysemantics.ai" target="_blank" rel="noopener noreferrer" class="cs-link" style="color: #0047FF; text-decoration: none;">https://companysemantics.ai</a></p>
|
|
3652
|
+
</div>
|
|
3653
|
+
</body>
|
|
3654
|
+
</html>",
|
|
3655
|
+
"subject": "You've been invited to join Acme Corp on Company Semantics",
|
|
3656
|
+
"text": "Hi there,
|
|
3657
|
+
|
|
3658
|
+
Acme Corp uses Company Semantics.
|
|
3659
|
+
|
|
3660
|
+
────────────────────────────────────────────────
|
|
3661
|
+
|
|
3662
|
+
┌──────────────────────────────┐
|
|
3663
|
+
│ We start planning next │
|
|
3664
|
+
│ quarter on Monday — glad you │
|
|
3665
|
+
│ are joining. │ (•̀_ರ╮)
|
|
3666
|
+
└──────────────────────────────┘
|
|
3667
|
+
Alex Rivera
|
|
3668
|
+
|
|
3669
|
+
┌──────────────────────────────┐
|
|
3670
|
+
│ Join to accept the │
|
|
3671
|
+
[c_S] │ invitation. │
|
|
3672
|
+
└──────────────────────────────┘
|
|
3673
|
+
|
|
3674
|
+
*----------------*
|
|
3675
|
+
| >> JOIN << |
|
|
3676
|
+
*----------------*
|
|
3677
|
+
|
|
3678
|
+
https://app.companysemantics.ai/invite/ghi789
|
|
3679
|
+
────────────────────────────────────────────────
|
|
3680
|
+
|
|
3681
|
+
From: Alex Rivera
|
|
3682
|
+
Workspace: Acme Corp
|
|
3683
|
+
Role: Member
|
|
3684
|
+
Expires: Jun 13, 2026
|
|
3685
|
+
|
|
3686
|
+
This invitation was sent via Company Semantics.
|
|
3687
|
+
If you weren't expecting this, no action is
|
|
3688
|
+
required.
|
|
3689
|
+
|
|
3690
|
+
|
|
3484
3691
|
/* EOM */
|
|
3485
3692
|
ⓒ 2026 • Company Semantics
|
|
3486
3693
|
https://companysemantics.ai
|