@opencxh/domain 1.191.0 → 1.193.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.
@@ -38,6 +38,15 @@ export interface ToolCatalogEntry {
38
38
  /** internal | outward as the tool declares it; absent = outward. */
39
39
  effect?: "internal" | "outward";
40
40
  }
41
+ /**
42
+ * Can this tool reach outside the building? A read never does; an `effect: "internal"` write says
43
+ * so itself. Everything else — including a tool that declares neither — counts as outward, so a
44
+ * forgotten declaration costs a tool for the turn rather than quietly sending a mail.
45
+ */
46
+ export declare function isOutwardTool(tool: {
47
+ access?: "read" | "write";
48
+ effect?: "internal" | "outward";
49
+ }): boolean;
41
50
  /** A model's request to call a tool. */
42
51
  export interface AiToolCall {
43
52
  id: string;
@@ -1,99 +1,104 @@
1
- import { LocaleBundle } from '../entities/analytics/dashboard';
2
- import { NotificationText } from './notification';
1
+ import { LocaleBundle, LocalizedText } from './localization';
3
2
  /**
4
- * Role group of an app that reports what currently needs someone's attention.
5
- *
6
- * A constant and not a literal at both ends: `Bridge.providers.list` answers an unknown group
7
- * with an empty list, so a typo would read as "nothing needs attention".
3
+ * Role group of an app that reports what currently needs someone's attention. A constant, not a
4
+ * literal: `Bridge.providers.list` answers an unknown group with an empty list.
8
5
  */
9
6
  export declare const ASSIST_SOURCE_PROVIDER_GROUP = "assist-source";
10
7
  /**
11
- * One thing that needs attention now, as the app that owns it sees it.
12
- *
13
- * Declared by the owner rather than derived centrally: only comms knows that an unanswered
14
- * first reply is worse than an unread newsletter, and only `apps/work` knows that `closedAt`
15
- * and not a status name decides whether an item is done. A hub that scored this itself would
16
- * need every app's types.
8
+ * How urgent a nudge is, across sources. `blocking` = nothing moves until you answer (an approval),
9
+ * `due` = a deadline is here or past (SLA, overdue), `open` = everything else. The hub orders by
10
+ * band first, then `priority` within the band, so no source has to calibrate against another.
17
11
  */
18
- export interface AssistNudge {
12
+ export declare const ASSIST_BANDS: readonly ["blocking", "due", "open"];
13
+ export type AssistBand = (typeof ASSIST_BANDS)[number];
14
+ /** What a source gets to say about one thing: the same texts, whether nudge or starter. */
15
+ interface AssistRanked {
19
16
  /** `<app>.<thing>` — the prefix is the owner, as with memory kinds and dossier keys. */
20
17
  id: string;
21
- /** Short label: "SLA", "Over tijd", "Straks". Bare i18n key; the hub stamps the namespace. */
22
- tag: string;
18
+ /** Absent counts as `"open"`. */
19
+ band?: AssistBand;
20
+ /** Rank within the band; higher sorts earlier. */
21
+ priority?: number;
23
22
  /** Lucide icon name. */
24
23
  icon?: string;
25
- /** One line, in the owner's words. */
26
- text: NotificationText;
27
24
  /** The assignment that handles this — fills the launcher, or the agent's procedure. */
28
- ask: NotificationText;
29
- /** What it is about, as `<kind>:<ref>`. Also the scope the walkthrough's composer writes to. */
25
+ ask: LocalizedText;
26
+ }
27
+ /**
28
+ * One thing that needs attention now, declared by the app that owns it. Only comms knows that an
29
+ * unanswered first reply outranks an unread newsletter; a hub scoring this itself would need every
30
+ * app's types. All texts are bare i18n keys; the hub stamps the source's namespace.
31
+ */
32
+ export interface AssistNudge extends AssistRanked {
33
+ /** Short label: "SLA", "Over tijd". */
34
+ tag: LocalizedText;
35
+ /** One line, in the owner's words. */
36
+ text: LocalizedText;
37
+ /** What it is about, as `<kind>:<ref>`. Also the scope the queue's composer writes to. */
30
38
  subjectKey?: string;
31
- /** Higher sorts earlier. The source ranks its own; the hub only merges. */
32
- priority?: number;
33
- /**
34
- * Where "open it" goes. `/…` follows the kernel router, `https://…` opens a tab — the same
35
- * convention `AiToolResult.sources` uses.
36
- */
39
+ /** Where "open it" goes: `/…` follows the kernel router, `https://…` opens a tab. */
37
40
  url?: string;
38
41
  /**
39
- * Where "later" posts `{ remindAt }`, as an invoke action
40
- * (`communication.interaction.<id>.remind`).
41
- *
42
- * The owner names the route rather than the board deriving one, because parking checks the
43
- * resource's own ACL and only the owning app has it. A source without such a route omits this
44
- * and simply gets no "later" button — better than a button that always fails.
42
+ * Where "later" posts `{ remindAt }`, as an invoke action. The owner names the route because
43
+ * parking checks the resource's own ACL. Absent = no "later" button, which beats one that fails.
45
44
  */
46
45
  snoozeAction?: string;
46
+ /** Where "done" posts, as an invoke action. Same rule as `snoozeAction`. */
47
+ resolveAction?: string;
47
48
  /**
48
49
  * Federated resource that draws this nudge's body in the queue, as `"<app>:<Component>"`.
49
- *
50
- * Absent = the queue falls back to `text` plus a button to `url`, so a source that never
51
- * adopts this keeps working. The component is handed `{ nudge, onHandled }`.
50
+ * Handed {@link AssistCardProps}. Absent = the queue falls back to `text` plus a button to `url`.
52
51
  */
53
52
  resource?: string;
54
- /**
55
- * Where "done" posts, as an invoke action. Same reasoning as `snoozeAction`: only the owning
56
- * app knows what done means (comms closes, work leaves the status ladder) and only it holds
57
- * the ACL. A source without such a route omits this and gets no "done" button.
58
- */
59
- resolveAction?: string;
53
+ }
54
+ /** What a nudge's federated body receives. */
55
+ export interface AssistCardProps {
56
+ nudge: AssistNudge;
57
+ /** The body handled the card itself (a reply went out); the queue moves on. */
58
+ onHandled: () => void;
60
59
  }
61
60
  /** A fixed starting point, offered even when nothing needs attention. */
62
- export interface AssistStarter {
63
- id: string;
64
- label: NotificationText;
65
- sub?: NotificationText;
66
- icon?: string;
67
- ask: NotificationText;
61
+ export interface AssistStarter extends AssistRanked {
62
+ label: LocalizedText;
63
+ sub?: LocalizedText;
68
64
  /**
69
- * Whether this sets an agent to work or just asks something. The source knows — "handle the
70
- * SLA conversations" is work, "summarise my open threads" is a question — and guessing it
71
- * from the wording is the classify step the launcher deliberately does not have. Absent
72
- * counts as `"task"`: a nudge is always something to be done.
65
+ * Whether this sets an agent to work or just asks something. The source knows; guessing it from
66
+ * the wording is the classify step the launcher deliberately does not have. Absent = `"task"`.
73
67
  */
74
68
  mode?: "chat" | "task";
75
69
  }
76
70
  /**
77
- * Bare payload of `GET /provider/assist/describe` — not wrapped in `ResponseFactory`, the way
78
- * every fan-out in this repo reads a describe.
79
- *
80
- * User-scoped: the hub calls it with an acting identity, because a nudge is per person.
71
+ * Bare payload of `GET /provider/assist/describe` — not wrapped in `ResponseFactory`, like every
72
+ * fan-out describe in this repo. User-scoped: the hub calls it with an acting identity.
81
73
  */
82
74
  export interface AssistSourceDescribe {
83
- /** The declaring app (== manifest name == `req.source.app`). */
75
+ /** The declaring app. A label: the hub namespaces on which provider actually answered. */
84
76
  source: string;
85
77
  nudges: AssistNudge[];
86
78
  starters: AssistStarter[];
87
79
  /** Texts for every key above, in every language this app ships. `flattenLocales()` helps. */
88
80
  locales?: LocaleBundle;
81
+ /**
82
+ * This app's own SSE event names that can change the rows above, so the board refreshes on a
83
+ * push. Bare names, matched against `SSEMessage.payload.event` exactly as pushed.
84
+ */
85
+ refreshOn?: string[];
89
86
  }
90
87
  /** Every source's answer, merged. */
91
88
  export interface AssistBoard {
92
89
  nudges: AssistNudge[];
93
90
  starters: AssistStarter[];
94
- /** Which apps answered. */
95
- sources: string[];
96
91
  locales: LocaleBundle;
97
92
  /** Apps that answered nothing. Silence is not an answer, so it is named. */
98
93
  degradedApps: string[];
94
+ /**
95
+ * Which app's realtime events invalidate this board, as app name -> event names. Assembled by
96
+ * the hub because the client cannot know its sources at build time.
97
+ */
98
+ refreshOn: Record<string, string[]>;
99
99
  }
100
+ /** The `<ref>` of a nudge's `subjectKey` when it is of `kind`, else `undefined`. */
101
+ export declare function subjectRef(nudge: Pick<AssistNudge, "subjectKey">, kind: string): string | undefined;
102
+ /** Band, then priority, then id — so two equal rows never reshuffle between reads. */
103
+ export declare function compareAssistRank(a: AssistRanked, b: AssistRanked): number;
104
+ export {};
@@ -1,23 +1,32 @@
1
+ import { ResourceReminder } from '../entities/resource-reminder/types';
1
2
  /**
2
3
  * The primitives both attention scorers share.
3
4
  *
4
5
  * Here rather than in one of them, so `apps/work` can score its items without importing
5
6
  * interaction types and vice versa: each app owns one half and neither owns the other.
6
7
  */
7
- /** Score of something that must not surface at all. Filter with a single `> INELIGIBLE`. */
8
+ export declare const HOUR_MS = 3600000;
9
+ export declare const DAY_MS = 86400000;
10
+ /** Score of something that must not surface at all. Filter with {@link isEligible}. */
8
11
  export declare const INELIGIBLE: number;
12
+ export declare const isEligible: (scored: {
13
+ score: number;
14
+ }) => boolean;
15
+ export type Priority = "urgent" | "high" | "normal" | "low";
9
16
  /** Priority weight, shared by interactions and work items. */
10
- export declare const W_PRIORITY: Record<"urgent" | "high" | "normal" | "low", number>;
17
+ export declare const W_PRIORITY: Record<Priority, number>;
18
+ /**
19
+ * Every contributor a scorer may name. A closed union, because the assist sources map these to
20
+ * nudge tags: a reason renamed here fails to compile there instead of silently falling through.
21
+ */
22
+ export type AttentionReason = `priority:${Priority}` | "unseen" | "assigned-to-you" | `waiting:${number}h` | "awaiting-approval" | "no-reply" | "awaiting-reply" | "bulk" | "overdue" | "due-today" | `auto:${string}`;
11
23
  export interface Scored {
12
24
  score: number;
13
- /** Human-readable contributors, insertion order. Drives the tag on an assist nudge. */
14
- reasons: string[];
25
+ /** Contributors in insertion order. Drives the tag on an assist nudge. */
26
+ reasons: AttentionReason[];
15
27
  }
16
28
  /** One user's own "later" mark on a resource, as both scorers read it. */
17
- export interface ParkMark {
18
- remindAt: number;
19
- parkedAt: number;
20
- }
29
+ export type ParkMark = Pick<ResourceReminder, "remindAt" | "parkedAt">;
21
30
  export interface AttentionCtx {
22
31
  /** Whose attention. Drives the unseen + assigned-to-me boosts. */
23
32
  userId: string;
@@ -26,3 +35,5 @@ export interface AttentionCtx {
26
35
  /** Personally parked: resourceId -> own mark. */
27
36
  remindersById?: Record<string, ParkMark>;
28
37
  }
38
+ /** Index one user's reminders the way `AttentionCtx.remindersById` wants them. */
39
+ export declare function parkMarksById(reminders: readonly ResourceReminder[]): Record<string, ParkMark>;
@@ -4,4 +4,24 @@ type Prev = [never, 0, 1, 2, 3, 4, 5, ...0[]];
4
4
  export type Paths<T, D extends number = 5> = [D] extends [never] ? never : T extends object ? {
5
5
  [K in keyof T]-?: K extends string | number ? T[K] extends object ? `${K}` | `${K}.${Paths<T[K], Prev[D]>}` : `${K}` : never;
6
6
  }[keyof T] : "";
7
+ /**
8
+ * An i18n reference as federated describes ship it: a bare key plus its variables. The hub that
9
+ * receives it stamps the source's namespace (`qualifyText` in platform-api).
10
+ */
11
+ export interface LocalizedText {
12
+ key: string;
13
+ vars?: Record<string, string | number>;
14
+ }
15
+ export interface LocaleString {
16
+ lang: string;
17
+ key: string;
18
+ value: string;
19
+ }
20
+ /**
21
+ * Flat list, not a nested map: every consumer already iterates it. Kept flat since the http key
22
+ * transform that once forced it was removed (2026-08-17).
23
+ */
24
+ export type LocaleBundle = LocaleString[];
25
+ /** Authoring convenience: turn a nested { lang: { key: value } } map into a LocaleBundle. */
26
+ export declare function flattenLocales(nested: Record<string, Record<string, string>>): LocaleBundle;
7
27
  export {};
@@ -1,4 +1,4 @@
1
- import { LocaleBundle } from '../entities/analytics/dashboard';
1
+ import { LocaleBundle, LocalizedText } from './localization';
2
2
  /**
3
3
  * Role group of an app that declares its own notification kinds.
4
4
  *
@@ -7,20 +7,8 @@ import { LocaleBundle } from '../entities/analytics/dashboard';
7
7
  * `ONBOARDING_SOURCE_PROVIDER_GROUP` is a constant.
8
8
  */
9
9
  export declare const NOTIFICATION_SOURCE_PROVIDER_GROUP = "notification-source";
10
- /**
11
- * Text a reader renders without the sender knowing what it says: an i18n key from the declaring
12
- * app's own bundle, plus values for `{name}` placeholders.
13
- *
14
- * Not a rendered string, because one notification is read on a laptop in Dutch and pushed to a
15
- * phone in English. Not a bare key either: half of these lines carry data ("Vincent mentioned you")
16
- * and a key per person does not exist.
17
- *
18
- * Sources write **bare** keys; the hub stamps the namespace (see `qualifyText` in platform-api).
19
- */
20
- export interface NotificationText {
21
- key: string;
22
- vars?: Record<string, string | number>;
23
- }
10
+ /** Alias of {@link LocalizedText}: sources write bare keys, the hub stamps the namespace. */
11
+ export type NotificationText = LocalizedText;
24
12
  /**
25
13
  * One switchable kind of notification, declared by the app that sends it.
26
14
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opencxh/domain",
3
- "version": "1.191.0",
3
+ "version": "1.193.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",