@company-semantics/contracts 62.11.0 → 63.1.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 +2 -2
- package/src/api/generated-spec-hash.ts +2 -2
- package/src/api/generated.ts +116 -10
- package/src/chat/README.md +13 -0
- package/src/chat/__tests__/message-mentions.test.ts +203 -0
- package/src/chat/index.ts +6 -0
- package/src/chat/schemas.ts +83 -1
- package/src/chat/types.ts +13 -0
- package/src/comments/README.md +53 -5
- package/src/comments/__tests__/README.md +7 -0
- package/src/comments/__tests__/schemas.test.ts +174 -5
- package/src/comments/index.ts +5 -0
- package/src/comments/schemas.ts +124 -18
- package/src/identity/README.md +8 -0
- package/src/identity/__tests__/agent.test.ts +95 -0
- package/src/identity/agent.ts +79 -0
- package/src/identity/index.ts +14 -0
- package/src/index.ts +29 -0
- package/src/notifications/renderers/ascii/README.md +6 -0
- package/src/notifications/renderers/ascii/geometry.ts +11 -2
- package/src/notifications/renderers/email/chat.ts +4 -3
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
AGENT_PRINCIPAL,
|
|
5
|
+
CS_AGENT,
|
|
6
|
+
CS_AGENT_HANDLE,
|
|
7
|
+
PrincipalRefSchema,
|
|
8
|
+
isAgentPrincipal,
|
|
9
|
+
principalKey,
|
|
10
|
+
userPrincipal,
|
|
11
|
+
} from "../agent.js";
|
|
12
|
+
|
|
13
|
+
const USER_ID = "33333333-3333-4333-8333-333333333333";
|
|
14
|
+
|
|
15
|
+
describe("CS_AGENT", () => {
|
|
16
|
+
// The point of the constant: the renderers derive the glyph rather than
|
|
17
|
+
// retyping it, so this assertion is what keeps them honest.
|
|
18
|
+
it("the glyph is derived from the handle", () => {
|
|
19
|
+
expect(CS_AGENT.glyph).toBe(`[${CS_AGENT.handle}]`);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("names the agent once — handle, displayName and the constant agree", () => {
|
|
23
|
+
expect(CS_AGENT.handle).toBe(CS_AGENT_HANDLE);
|
|
24
|
+
expect(CS_AGENT.displayName).toBe(CS_AGENT_HANDLE);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe("PrincipalRefSchema", () => {
|
|
29
|
+
it("accepts the agent arm with no id", () => {
|
|
30
|
+
expect(PrincipalRefSchema.parse({ kind: "agent" })).toEqual({
|
|
31
|
+
kind: "agent",
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("accepts a user arm carrying a userId", () => {
|
|
36
|
+
expect(PrincipalRefSchema.parse({ kind: "user", userId: USER_ID })).toEqual(
|
|
37
|
+
{
|
|
38
|
+
kind: "user",
|
|
39
|
+
userId: USER_ID,
|
|
40
|
+
},
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Strict, not stripping: a stray userId on an agent ref means the caller
|
|
45
|
+
// believes the agent has a users row. Refuse rather than silently drop it.
|
|
46
|
+
it("an agent ref rejects a userId", () => {
|
|
47
|
+
expect(
|
|
48
|
+
PrincipalRefSchema.safeParse({ kind: "agent", userId: "x" }).success,
|
|
49
|
+
).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("a user ref requires a userId", () => {
|
|
53
|
+
expect(PrincipalRefSchema.safeParse({ kind: "user" }).success).toBe(false);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("rejects an unknown kind", () => {
|
|
57
|
+
expect(PrincipalRefSchema.safeParse({ kind: "system" }).success).toBe(
|
|
58
|
+
false,
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Mirrors the comments boundary rather than the uuid-tight rest of this
|
|
63
|
+
// domain: tightening here would reject ids the server considers valid.
|
|
64
|
+
it("does not require the userId to be a uuid", () => {
|
|
65
|
+
expect(
|
|
66
|
+
PrincipalRefSchema.safeParse({ kind: "user", userId: "u_1" }).success,
|
|
67
|
+
).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe("principal helpers", () => {
|
|
72
|
+
it("AGENT_PRINCIPAL parses as the agent arm", () => {
|
|
73
|
+
expect(PrincipalRefSchema.parse(AGENT_PRINCIPAL)).toEqual({
|
|
74
|
+
kind: "agent",
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("userPrincipal builds a parseable user ref", () => {
|
|
79
|
+
expect(PrincipalRefSchema.parse(userPrincipal(USER_ID))).toEqual({
|
|
80
|
+
kind: "user",
|
|
81
|
+
userId: USER_ID,
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("isAgentPrincipal narrows the agent arm only", () => {
|
|
86
|
+
expect(isAgentPrincipal(AGENT_PRINCIPAL)).toBe(true);
|
|
87
|
+
expect(isAgentPrincipal(userPrincipal(USER_ID))).toBe(false);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("principalKey is stable and cannot collide across arms", () => {
|
|
91
|
+
expect(principalKey(AGENT_PRINCIPAL)).toBe("agent");
|
|
92
|
+
expect(principalKey(userPrincipal(USER_ID))).toBe(`user:${USER_ID}`);
|
|
93
|
+
expect(new Set([principalKey(AGENT_PRINCIPAL), "agent"]).size).toBe(1);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* THE AGENT PRINCIPAL (control INVARIANTS.md, INV-AGENT-IDENTITY).
|
|
5
|
+
*
|
|
6
|
+
* One identity constant. The glyph is DERIVED from the handle so this package
|
|
7
|
+
* carries exactly one literal; the ASCII/email renderers and the app's avatar
|
|
8
|
+
* read it from here, never retype it.
|
|
9
|
+
*
|
|
10
|
+
* SINGLETON, BY DECISION: `{ kind: 'agent' }` carries no id because there is
|
|
11
|
+
* exactly one agent principal, forever. A second agent is a new decision and a
|
|
12
|
+
* MAJOR that adds `agentId`; nothing is pre-added now (ADR-CONTRACTS-160).
|
|
13
|
+
*
|
|
14
|
+
* NOT the ACL `Principal` in src/permissions/share-api.ts. That one names a
|
|
15
|
+
* GRANTEE on a share (user | unit | org, discriminated by `type`); this one
|
|
16
|
+
* names WHO authored an utterance or was mentioned in one (user | agent,
|
|
17
|
+
* discriminated by `kind`). Different axis — do not conflate or extend either
|
|
18
|
+
* into the other.
|
|
19
|
+
*
|
|
20
|
+
* `userId` is a bare `z.string()`, not `.uuid()`, deliberately diverging from
|
|
21
|
+
* the rest of this domain (Person, IdentityLink and PositionRef are all
|
|
22
|
+
* uuid-tight). It mirrors the comments boundary, where the shapes in this
|
|
23
|
+
* package mirror the backend's looseness rather than tightening past it:
|
|
24
|
+
* a ref that rejects an id the server considers valid is a client-side outage
|
|
25
|
+
* for no security gain. Do not "fix" this to `.uuid()`.
|
|
26
|
+
*
|
|
27
|
+
* Zero-dependency (zod only): every domain that speaks principals imports this
|
|
28
|
+
* module, so it must never import one of them back.
|
|
29
|
+
*/
|
|
30
|
+
export const CS_AGENT_HANDLE = "c_S" as const;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The agent's identity, in the three forms a surface needs: the handle a
|
|
34
|
+
* mention is typed as, the name a chip renders, and the glyph a monospace or
|
|
35
|
+
* plain-text surface draws. `glyph` is a template over the handle — the single
|
|
36
|
+
* point of truth the renderers derive from.
|
|
37
|
+
*/
|
|
38
|
+
export const CS_AGENT = {
|
|
39
|
+
handle: CS_AGENT_HANDLE,
|
|
40
|
+
displayName: CS_AGENT_HANDLE,
|
|
41
|
+
glyph: `[${CS_AGENT_HANDLE}]`,
|
|
42
|
+
} as const;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* A principal: a person by id, or the one agent.
|
|
46
|
+
*
|
|
47
|
+
* STRICT on both arms, following the comment-anchor precedent: the failure
|
|
48
|
+
* modes worth having are real refusals, not silent coercions. A non-strict
|
|
49
|
+
* object would STRIP a stray `userId` off an agent ref and hand the caller a
|
|
50
|
+
* ref that parsed cleanly while meaning something it did not say.
|
|
51
|
+
*/
|
|
52
|
+
export const PrincipalRefSchema = z.discriminatedUnion("kind", [
|
|
53
|
+
z.strictObject({ kind: z.literal("user"), userId: z.string() }),
|
|
54
|
+
z.strictObject({ kind: z.literal("agent") }),
|
|
55
|
+
]);
|
|
56
|
+
export type PrincipalRef = z.infer<typeof PrincipalRefSchema>;
|
|
57
|
+
|
|
58
|
+
/** The one agent principal. There is no second value of this shape. */
|
|
59
|
+
export const AGENT_PRINCIPAL: PrincipalRef = { kind: "agent" };
|
|
60
|
+
|
|
61
|
+
/** A person as a principal. */
|
|
62
|
+
export function userPrincipal(userId: string): PrincipalRef {
|
|
63
|
+
return { kind: "user", userId };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Narrows a ref to the agent arm. */
|
|
67
|
+
export function isAgentPrincipal(ref: PrincipalRef): ref is { kind: "agent" } {
|
|
68
|
+
return ref.kind === "agent";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Stable key for Set/Map use: 'agent' | 'user:<id>'.
|
|
73
|
+
*
|
|
74
|
+
* The agent needs no id to be keyable precisely because it is a singleton;
|
|
75
|
+
* `'agent'` cannot collide with `'user:<id>'` because a user key is prefixed.
|
|
76
|
+
*/
|
|
77
|
+
export function principalKey(ref: PrincipalRef): string {
|
|
78
|
+
return ref.kind === "agent" ? "agent" : `user:${ref.userId}`;
|
|
79
|
+
}
|
package/src/identity/index.ts
CHANGED
|
@@ -94,3 +94,17 @@ export type { Person } from "./person";
|
|
|
94
94
|
// Identity Link — external system id → Person (provider is an open string)
|
|
95
95
|
export { IdentityLinkSchema } from "./identity-link";
|
|
96
96
|
export type { IdentityLink } from "./identity-link";
|
|
97
|
+
|
|
98
|
+
// Agent Principal — CS_AGENT (the one identity constant, glyph derived from the
|
|
99
|
+
// handle) and PrincipalRef, the only vocabulary for an author, a mention target
|
|
100
|
+
// or a picker candidate (ADR-CONTRACTS-160, INV-AGENT-IDENTITY).
|
|
101
|
+
export {
|
|
102
|
+
CS_AGENT,
|
|
103
|
+
CS_AGENT_HANDLE,
|
|
104
|
+
PrincipalRefSchema,
|
|
105
|
+
AGENT_PRINCIPAL,
|
|
106
|
+
userPrincipal,
|
|
107
|
+
isAgentPrincipal,
|
|
108
|
+
principalKey,
|
|
109
|
+
} from "./agent";
|
|
110
|
+
export type { PrincipalRef } from "./agent";
|
package/src/index.ts
CHANGED
|
@@ -204,6 +204,20 @@ export type { Person } from "./identity/index";
|
|
|
204
204
|
export { IdentityLinkSchema } from "./identity/index";
|
|
205
205
|
export type { IdentityLink } from "./identity/index";
|
|
206
206
|
|
|
207
|
+
// Agent Principal — CS_AGENT and PrincipalRef (ADR-CONTRACTS-160).
|
|
208
|
+
// Not the ACL `Principal` in src/permissions: that names a share grantee, this
|
|
209
|
+
// names who authored an utterance or was mentioned in one.
|
|
210
|
+
export {
|
|
211
|
+
CS_AGENT,
|
|
212
|
+
CS_AGENT_HANDLE,
|
|
213
|
+
PrincipalRefSchema,
|
|
214
|
+
AGENT_PRINCIPAL,
|
|
215
|
+
userPrincipal,
|
|
216
|
+
isAgentPrincipal,
|
|
217
|
+
principalKey,
|
|
218
|
+
} from "./identity/index";
|
|
219
|
+
export type { PrincipalRef } from "./identity/index";
|
|
220
|
+
|
|
207
221
|
// Auth domain types
|
|
208
222
|
export type { AuthStartMode, AuthStartResponse } from "./auth/index";
|
|
209
223
|
export { OTPErrorCode } from "./auth/index";
|
|
@@ -469,6 +483,8 @@ export {
|
|
|
469
483
|
export type { SuggestionReceipt } from "./comments/index";
|
|
470
484
|
|
|
471
485
|
export {
|
|
486
|
+
AGENT_OUTCOME_REASONS,
|
|
487
|
+
AgentOutcomeSchema,
|
|
472
488
|
CommentAnchorSchema,
|
|
473
489
|
CommentAnchorTypeSchema,
|
|
474
490
|
CommentMentionSchema,
|
|
@@ -481,9 +497,11 @@ export {
|
|
|
481
497
|
MentionableCandidateSchema,
|
|
482
498
|
MentionableResponseSchema,
|
|
483
499
|
SuggestionSchema,
|
|
500
|
+
ThreadOriginSchema,
|
|
484
501
|
} from "./comments/index";
|
|
485
502
|
|
|
486
503
|
export type {
|
|
504
|
+
AgentOutcome,
|
|
487
505
|
CommentAnchor,
|
|
488
506
|
CommentAnchorType,
|
|
489
507
|
CommentMention,
|
|
@@ -499,6 +517,7 @@ export type {
|
|
|
499
517
|
Suggestion,
|
|
500
518
|
SuggestionOp,
|
|
501
519
|
SuggestionStatus,
|
|
520
|
+
ThreadOrigin,
|
|
502
521
|
} from "./comments/index";
|
|
503
522
|
|
|
504
523
|
// Chat domain types
|
|
@@ -544,6 +563,11 @@ export type {
|
|
|
544
563
|
// Page context (PRD-00969, ADR-CONTRACTS-153): where the reader is, as
|
|
545
564
|
// identifiers. Never content, never free text.
|
|
546
565
|
ChatPageContext,
|
|
566
|
+
// Chat mentions (ADR-CONTRACTS-160): a turn's mention rows are keyed on a
|
|
567
|
+
// PrincipalRef, so the agent is nameable on the chat surface too, and the
|
|
568
|
+
// composer's candidate list reuses the comments candidate shape.
|
|
569
|
+
ChatMessageMention,
|
|
570
|
+
ChatMentionableResponse,
|
|
547
571
|
} from "./chat/index";
|
|
548
572
|
|
|
549
573
|
export {
|
|
@@ -559,6 +583,11 @@ export {
|
|
|
559
583
|
// older client reject the whole chat list; this is the ONE place it becomes
|
|
560
584
|
// a ProactiveEventKind. See src/chat/proactive-kind.ts.
|
|
561
585
|
recognizeProactiveKind,
|
|
586
|
+
// Chat mentions: the mention row a turn carries, and the composer's
|
|
587
|
+
// candidate envelope (items are `MentionableCandidateSchema`, imported from
|
|
588
|
+
// comments rather than restated).
|
|
589
|
+
ChatMessageMentionSchema,
|
|
590
|
+
ChatMentionableResponseSchema,
|
|
562
591
|
} from "./chat/index";
|
|
563
592
|
|
|
564
593
|
// Organization domain types
|
|
@@ -71,5 +71,11 @@ looking for it again with a regex. That is the hack this shape exists to prevent
|
|
|
71
71
|
|
|
72
72
|
- `../../content` — `CallToAction`, `ChatTurn`, `ChatUnitItem`. Types only; this
|
|
73
73
|
layer reads the vocabulary and never extends it.
|
|
74
|
+
- `../../../identity/agent` — `CS_AGENT`, for `AVATAR` alone. The agent has one
|
|
75
|
+
identity and the glyph is DERIVED from it rather than retyped here
|
|
76
|
+
(INV-AGENT-IDENTITY); a second copy is how the drawing and the chip quietly
|
|
77
|
+
drift apart. It is a value rather than a type, and it brings `zod` into this
|
|
78
|
+
layer's transitive imports for the first time — the price of one source of
|
|
79
|
+
truth, and cheap, because `zod` is already a dependency of the package.
|
|
74
80
|
|
|
75
81
|
Nothing else. It knows no channel, no colour, no markup and no `Renderer`.
|
|
@@ -23,8 +23,17 @@
|
|
|
23
23
|
* promise we are willing to keep.
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
import { CS_AGENT } from "../../../identity/agent";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The assistant's avatar, drawn beside the last line of its bubble.
|
|
30
|
+
*
|
|
31
|
+
* DERIVED from `CS_AGENT.glyph`, never retyped (INV-AGENT-IDENTITY): the agent
|
|
32
|
+
* has one identity and this layer reads it rather than restating it. Its LENGTH
|
|
33
|
+
* feeds `indent` below and therefore every box width in this directory — so a
|
|
34
|
+
* change to the handle re-flows the drawing, which is the intended coupling.
|
|
35
|
+
*/
|
|
36
|
+
export const AVATAR = CS_AGENT.glyph;
|
|
28
37
|
|
|
29
38
|
/** The recipient's avatar, drawn beside the last line of their bubble. */
|
|
30
39
|
export const KAOMOJI = "(•̀_ರ╮)";
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
* through it.
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
+
import { CS_AGENT } from "../../../identity/agent";
|
|
24
25
|
import type { CallToAction, ChatTurn, ChatUnitItem } from "../../content";
|
|
25
26
|
import {
|
|
26
27
|
ruleAscii,
|
|
@@ -95,7 +96,7 @@ function renderBubble(turn: ChatTurn, row: ChatRow): ChatPartRender {
|
|
|
95
96
|
|
|
96
97
|
const html = `<table cellpadding="0" cellspacing="0" border="0" width="100%" ${styleClass(row)}>
|
|
97
98
|
<tr>
|
|
98
|
-
<td ${styleClass(avatar)}
|
|
99
|
+
<td ${styleClass(avatar)}>${CS_AGENT.glyph}</td>
|
|
99
100
|
<td ${styleClass(channel)}><table cellpadding="0" cellspacing="0" border="0" ${styleClass("chat-bubble-wrap")}>
|
|
100
101
|
<tr><td ${styleClass(bubble, "bubble")}>${escapeHtml(clamped)}</td></tr>
|
|
101
102
|
</table></td>
|
|
@@ -130,7 +131,7 @@ function renderChatCta(
|
|
|
130
131
|
const stack = `<div ${styleClass("cta-stack")}>${withDots ? dotsOverCtaHtml() : ""}${btnHtml}</div>`;
|
|
131
132
|
const html = `<table cellpadding="0" cellspacing="0" border="0" width="100%" ${styleClass("chat-row-24")}>
|
|
132
133
|
<tr>
|
|
133
|
-
<td ${styleClass("chat-avatar-left-hidden")}
|
|
134
|
+
<td ${styleClass("chat-avatar-left-hidden")}>${CS_AGENT.glyph}</td>
|
|
134
135
|
<td ${styleClass(`chat-channel-${align}`)}>${stack}</td>
|
|
135
136
|
<td ${styleClass("chat-avatar-right-hidden")}>(•̀_ರ╮)</td>
|
|
136
137
|
</tr>
|
|
@@ -150,7 +151,7 @@ function renderChatCta(
|
|
|
150
151
|
function renderChatDots(): ChatPartRender {
|
|
151
152
|
const html = `<table cellpadding="0" cellspacing="0" border="0" width="100%" ${styleClass("chat-row-16")}>
|
|
152
153
|
<tr>
|
|
153
|
-
<td ${styleClass("chat-avatar-left-hidden")}
|
|
154
|
+
<td ${styleClass("chat-avatar-left-hidden")}>${CS_AGENT.glyph}</td>
|
|
154
155
|
<td ${styleClass("dots-cell", "dots")}>⋮</td>
|
|
155
156
|
<td ${styleClass("chat-avatar-right-hidden")}>(•̀_ರ╮)</td>
|
|
156
157
|
</tr>
|