feat(team): Datenschicht für Property On
Property On wird als Funktionsbereich "Teamübersicht" in Property Match eingebettet — sieben digitale Mitarbeiter, die ausführen, während der Mensch freigibt, anpasst oder zurückweist. Durchgehend Provider → Service → Hook, kein übersprungener Layer: - domain/: teamAgent, agentWorkItem, agentProtocol, agentConnection, agentFilters - mock-data/: 7 Personalblätter aus dem Agenten-Katalog (Aufgabenzahlen exakt: Ferdi 9, Sina 7, Lea 11, Bruno 9, Reto 10, Livia 11, Nora 8), 14 Vorgänge, 38 Protokolleinträge, 9 Verbindungen - provider/: vier Mockup-Provider mit versionierter localStorage-Persistenz - services/: Fachlogik inkl. Freigabe, Zurückweisung, Kennzahlen, Demo-Reset - hooks/: React Query, Mutationen entwerten Vorgänge, Kennzahlen und Protokoll gemeinsam — sonst zeigt die Oberfläche widersprüchliche Zahlen Namensgebung: Der Entitätstyp heisst TeamAgent, nicht Agent. Im Repo existiert bereits PowerOn als KI-Backend-Proxy, und Claude Code legt Arbeitskopien unter agent-* ab; ein nackter Typ Agent wäre in Suchen nicht auffindbar. Alle Satellitentypen sind Agent*-präfixiert, weil domain/index.ts per export * bündelt und generische Namen dort kollidieren. Eigener Protokolltyp statt ActivityEvent: den Namen gibt es im Repo dreifach und gegenseitig inkompatibel (domain/activityEvent.ts, governanceService.ts, PropertyActivityLogPanel.tsx). Ein Anschluss hätte den Konflikt zementiert. Demo-Uhr in lib/teamClock.ts: die Mockdaten sind auf den 20.05.2026 verankert. Gegen die Systemzeit gerechnet wären die Filter "heute / diese Woche / dieser Monat" an jedem anderen Kalendertag leer und die Auswertung sähe kaputt aus, obwohl sie korrekt rechnet. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
36ce548169
commit
52dc7de25e
38 files changed
+9055
No files matched your search
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Property On — Subreiter «Kanäle & Systeme»: die zentralen Verbindungen der
|
||||
* Organisation.
|
||||
*
|
||||
* Bewusst getrennt von `domain/dataSource.ts` (`DataSource`/`ConnectorRun`):
|
||||
* jenes Modell beschreibt Crawler-Quellen mit AGB-Status und Crawl-Läufen, hier
|
||||
* geht es um angebundene Kommunikationskanäle und Fachsysteme. Zwei fachlich
|
||||
* verschiedene Dinge im selben Typ zu führen wäre eine Scheinvereinfachung.
|
||||
*
|
||||
* Alle Verbindungen sind Frontend-Simulation — es werden nie echte Zugangsdaten
|
||||
* erfasst und nie echte Systeme kontaktiert.
|
||||
*/
|
||||
|
||||
import type { AgentAccessLevel, AgentConnectionStatus } from './teamAgent'
|
||||
|
||||
export const AgentConnectionCategory = {
|
||||
EMAIL_M365: 'EMAIL_M365',
|
||||
WHATSAPP: 'WHATSAPP',
|
||||
TEAMS: 'TEAMS',
|
||||
CALENDAR: 'CALENDAR',
|
||||
DOCUMENT_STORE: 'DOCUMENT_STORE',
|
||||
PHONE_VOICE: 'PHONE_VOICE',
|
||||
ERP: 'ERP',
|
||||
CRM: 'CRM',
|
||||
PUBLIC_SOURCES: 'PUBLIC_SOURCES',
|
||||
} as const
|
||||
export type AgentConnectionCategory = typeof AgentConnectionCategory[keyof typeof AgentConnectionCategory]
|
||||
|
||||
/** ERP-Auswahl aus §13.4. */
|
||||
export const AgentErpVendor = {
|
||||
IMMOTOP2: 'IMMOTOP2',
|
||||
ABACUS_ABAIMMO: 'ABACUS_ABAIMMO',
|
||||
RIMO_R5: 'RIMO_R5',
|
||||
GARAIO_REM: 'GARAIO_REM',
|
||||
OTHER: 'OTHER',
|
||||
} as const
|
||||
export type AgentErpVendor = typeof AgentErpVendor[keyof typeof AgentErpVendor]
|
||||
|
||||
export interface AgentConnectionPermission {
|
||||
id: string
|
||||
label: string
|
||||
access: AgentAccessLevel
|
||||
granted: boolean
|
||||
}
|
||||
|
||||
export interface AgentConnection {
|
||||
id: string
|
||||
category: AgentConnectionCategory
|
||||
name: string
|
||||
description: string
|
||||
status: AgentConnectionStatus
|
||||
accountCount: number
|
||||
usedByAgentIds: string[]
|
||||
lastSyncAt?: string
|
||||
/** Nur bei `category === ERP` gesetzt. */
|
||||
vendor?: AgentErpVendor
|
||||
/** Frei benannte Verbindung aus dem Assistenten, z. B. «Bewirtschaftung Zürich». */
|
||||
connectionLabel?: string
|
||||
permissions: AgentConnectionPermission[]
|
||||
}
|
||||
|
||||
/** Ergebnis von «Verbindung testen» — deterministisch, kein Zufall (§19.2). */
|
||||
export interface AgentConnectionTestResult {
|
||||
ok: boolean
|
||||
message: string
|
||||
checkedAt: string
|
||||
}
|
||||
|
||||
export interface CreateAgentConnectionInput {
|
||||
connectionId: string
|
||||
vendor?: AgentErpVendor
|
||||
connectionLabel: string
|
||||
permissionIds: string[]
|
||||
agentIds: string[]
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Property On — Filter- und Sortierdimensionen.
|
||||
*
|
||||
* Eigene Datei, weil sowohl die Auswertung auf der Teamübersicht (§4.2) als auch
|
||||
* der Bearbeitungsverlauf (§5.3) und das Protokoll (§12.3) denselben Zeitraum-
|
||||
* begriff teilen. Ohne gemeinsamen Typ driften die drei Filter auseinander.
|
||||
*/
|
||||
|
||||
export const AgentPeriod = {
|
||||
TODAY: 'TODAY',
|
||||
WEEK: 'WEEK',
|
||||
MONTH: 'MONTH',
|
||||
YEAR: 'YEAR',
|
||||
ALL: 'ALL',
|
||||
} as const
|
||||
export type AgentPeriod = typeof AgentPeriod[keyof typeof AgentPeriod]
|
||||
|
||||
export const AgentWorkItemSort = {
|
||||
NEWEST: 'NEWEST',
|
||||
OLDEST: 'OLDEST',
|
||||
PRIORITY: 'PRIORITY',
|
||||
AGENT: 'AGENT',
|
||||
OBJECT_ID: 'OBJECT_ID',
|
||||
} as const
|
||||
export type AgentWorkItemSort = typeof AgentWorkItemSort[keyof typeof AgentWorkItemSort]
|
||||
|
||||
/**
|
||||
* Grenze eines Zeitraums, gemessen am Referenzdatum.
|
||||
* Gibt den frühesten noch enthaltenen Zeitpunkt zurück, oder null für ALL.
|
||||
*/
|
||||
export function periodStart(period: AgentPeriod, reference: Date): Date | null {
|
||||
const d = new Date(reference)
|
||||
switch (period) {
|
||||
case AgentPeriod.TODAY:
|
||||
d.setHours(0, 0, 0, 0)
|
||||
return d
|
||||
case AgentPeriod.WEEK: {
|
||||
// Woche beginnt am Montag — Schweizer Konvention
|
||||
const day = (d.getDay() + 6) % 7
|
||||
d.setDate(d.getDate() - day)
|
||||
d.setHours(0, 0, 0, 0)
|
||||
return d
|
||||
}
|
||||
case AgentPeriod.MONTH:
|
||||
d.setDate(1)
|
||||
d.setHours(0, 0, 0, 0)
|
||||
return d
|
||||
case AgentPeriod.YEAR:
|
||||
d.setMonth(0, 1)
|
||||
d.setHours(0, 0, 0, 0)
|
||||
return d
|
||||
case AgentPeriod.ALL:
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/** Liegt `isoTimestamp` im gewählten Zeitraum? */
|
||||
export function isWithinPeriod(isoTimestamp: string, period: AgentPeriod, reference: Date): boolean {
|
||||
const start = periodStart(period, reference)
|
||||
if (!start) return true
|
||||
const t = new Date(isoTimestamp).getTime()
|
||||
return !Number.isNaN(t) && t >= start.getTime()
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Property On — Protokolleinträge.
|
||||
*
|
||||
* Bewusst ein eigener Typ und nicht `ActivityEvent`: den Namen gibt es im Repo
|
||||
* bereits dreifach und gegenseitig inkompatibel — in `domain/activityEvent.ts`
|
||||
* (über das Domain-Barrel exportiert), lokal in `services/governanceService.ts`
|
||||
* und noch einmal in `components/supply/PropertyActivityLogPanel.tsx`. Ein
|
||||
* Anschluss an einen dieser Typen würde den Konflikt zementieren.
|
||||
*/
|
||||
|
||||
import type { AgentChannelType } from './teamAgent'
|
||||
import type { AgentDetailField, AgentProcessingStep, AgentSourceReference } from './agentWorkItem'
|
||||
|
||||
export const AgentProtocolStatus = {
|
||||
SUCCESS: 'SUCCESS',
|
||||
WARNING: 'WARNING',
|
||||
ERROR: 'ERROR',
|
||||
INFO: 'INFO',
|
||||
} as const
|
||||
export type AgentProtocolStatus = typeof AgentProtocolStatus[keyof typeof AgentProtocolStatus]
|
||||
|
||||
export const AgentProtocolEventType = {
|
||||
TASK_RUN: 'TASK_RUN',
|
||||
MESSAGE_PROCESSED: 'MESSAGE_PROCESSED',
|
||||
REMINDER_SENT: 'REMINDER_SENT',
|
||||
ESCALATION: 'ESCALATION',
|
||||
APPROVAL: 'APPROVAL',
|
||||
REJECTION: 'REJECTION',
|
||||
EDIT: 'EDIT',
|
||||
SETTINGS_SAVED: 'SETTINGS_SAVED',
|
||||
TASKS_SAVED: 'TASKS_SAVED',
|
||||
AGENT_PAUSED: 'AGENT_PAUSED',
|
||||
AGENT_RESUMED: 'AGENT_RESUMED',
|
||||
CHANNEL_TESTED: 'CHANNEL_TESTED',
|
||||
CHANNEL_TOGGLED: 'CHANNEL_TOGGLED',
|
||||
CONNECTION_CREATED: 'CONNECTION_CREATED',
|
||||
CONNECTION_REMOVED: 'CONNECTION_REMOVED',
|
||||
DEMO_RESET: 'DEMO_RESET',
|
||||
} as const
|
||||
export type AgentProtocolEventType = typeof AgentProtocolEventType[keyof typeof AgentProtocolEventType]
|
||||
|
||||
/** Wer den Eintrag ausgelöst hat — Mensch oder Zeitplan/Systemtrigger. */
|
||||
export const AgentTriggerSource = {
|
||||
USER: 'USER',
|
||||
SYSTEM: 'SYSTEM',
|
||||
} as const
|
||||
export type AgentTriggerSource = typeof AgentTriggerSource[keyof typeof AgentTriggerSource]
|
||||
|
||||
export const AgentApprovalDecision = {
|
||||
APPROVED: 'APPROVED',
|
||||
REJECTED: 'REJECTED',
|
||||
EDITED: 'EDITED',
|
||||
} as const
|
||||
export type AgentApprovalDecision = typeof AgentApprovalDecision[keyof typeof AgentApprovalDecision]
|
||||
|
||||
export interface AgentProtocolEntry {
|
||||
id: string
|
||||
/**
|
||||
* Verantwortlicher digitaler Mitarbeiter. Fehlt bei organisationsweiten
|
||||
* Ereignissen — eine Verbindung im Subreiter «Kanäle & Systeme» gehört keinem
|
||||
* einzelnen Mitarbeiter. Solche Einträge erscheinen im Gesamtprotokoll, nicht
|
||||
* im Personalblatt.
|
||||
*/
|
||||
agentId?: string
|
||||
/** Nur bei Verbindungsereignissen gesetzt. */
|
||||
connectionId?: string
|
||||
timestamp: string
|
||||
eventType: AgentProtocolEventType
|
||||
title: string
|
||||
description: string
|
||||
status: AgentProtocolStatus
|
||||
objectId?: string
|
||||
channel?: AgentChannelType
|
||||
triggeredBy: AgentTriggerSource
|
||||
/** Klarname der auslösenden Person, wenn `triggeredBy === USER`. */
|
||||
triggeredByName?: string
|
||||
approvalDecision?: AgentApprovalDecision
|
||||
workItemId?: string
|
||||
// Detailansicht (§12.5)
|
||||
input?: AgentDetailField[]
|
||||
processingSteps?: AgentProcessingStep[]
|
||||
output?: AgentDetailField[]
|
||||
sourceReferences?: AgentSourceReference[]
|
||||
}
|
||||
|
||||
export type CreateAgentProtocolEntryInput = Omit<AgentProtocolEntry, 'id'>
|
||||
@@ -0,0 +1,185 @@
|
||||
/**
|
||||
* Property On — Vorgänge im «Bearbeitungsverlauf».
|
||||
*
|
||||
* Ein Vorgang ist entweder bereits autonom erledigt («Erledigte Aufträge») oder
|
||||
* wartet auf einen Menschen («Pendente Anfragen»). Das Bedienprinzip ist
|
||||
* human-led, agent-operated: der Agent arbeitet, der Mensch entscheidet.
|
||||
*/
|
||||
|
||||
import type { AgentChannelType } from './teamAgent'
|
||||
|
||||
export const AgentWorkItemStatus = {
|
||||
PENDING: 'PENDING',
|
||||
APPROVED: 'APPROVED',
|
||||
REJECTED: 'REJECTED',
|
||||
EDITING: 'EDITING',
|
||||
COMPLETED: 'COMPLETED',
|
||||
} as const
|
||||
export type AgentWorkItemStatus = typeof AgentWorkItemStatus[keyof typeof AgentWorkItemStatus]
|
||||
|
||||
export const AgentWorkItemPriority = {
|
||||
LOW: 'LOW',
|
||||
MEDIUM: 'MEDIUM',
|
||||
HIGH: 'HIGH',
|
||||
CRITICAL: 'CRITICAL',
|
||||
} as const
|
||||
export type AgentWorkItemPriority = typeof AgentWorkItemPriority[keyof typeof AgentWorkItemPriority]
|
||||
|
||||
/** Vorgangstyp — Filterdimension in §5.3. */
|
||||
export const AgentWorkItemKind = {
|
||||
MESSAGE_TRIAGE: 'MESSAGE_TRIAGE',
|
||||
REMINDER_CREATED: 'REMINDER_CREATED',
|
||||
CONTRACT_ANSWER: 'CONTRACT_ANSWER',
|
||||
LOCATION_MODULE: 'LOCATION_MODULE',
|
||||
INQUIRY_QUALIFIED: 'INQUIRY_QUALIFIED',
|
||||
AUTO_REJECTION: 'AUTO_REJECTION',
|
||||
APPOINTMENT: 'APPOINTMENT',
|
||||
BRIEFING: 'BRIEFING',
|
||||
RECAP: 'RECAP',
|
||||
MARKET_SIGNAL: 'MARKET_SIGNAL',
|
||||
ESCALATION: 'ESCALATION',
|
||||
DOC_GAP: 'DOC_GAP',
|
||||
SYSTEM_ISSUE: 'SYSTEM_ISSUE',
|
||||
} as const
|
||||
export type AgentWorkItemKind = typeof AgentWorkItemKind[keyof typeof AgentWorkItemKind]
|
||||
|
||||
/** Fachbereich — Filterdimension «Bereich» in §4.2 und §5.3. */
|
||||
export const AgentDomainArea = {
|
||||
CONTRACT_DEADLINES: 'CONTRACT_DEADLINES',
|
||||
DOCUMENTATION: 'DOCUMENTATION',
|
||||
INQUIRIES: 'INQUIRIES',
|
||||
VIEWINGS: 'VIEWINGS',
|
||||
LOCATION: 'LOCATION',
|
||||
MARKET: 'MARKET',
|
||||
} as const
|
||||
export type AgentDomainArea = typeof AgentDomainArea[keyof typeof AgentDomainArea]
|
||||
|
||||
export const AgentWorkItemAction = {
|
||||
APPROVE: 'APPROVE',
|
||||
ONE_CLICK_CONFIRM: 'ONE_CLICK_CONFIRM',
|
||||
DECIDE: 'DECIDE',
|
||||
EDIT: 'EDIT',
|
||||
REJECT: 'REJECT',
|
||||
ANSWER_QUERY: 'ANSWER_QUERY',
|
||||
OPEN_SOURCE: 'OPEN_SOURCE',
|
||||
MARK_DONE: 'MARK_DONE',
|
||||
} as const
|
||||
export type AgentWorkItemAction = typeof AgentWorkItemAction[keyof typeof AgentWorkItemAction]
|
||||
|
||||
// ── Bestandteile der Detailansicht ────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Geordnetes Schlüssel-Wert-Paar. Die Spezifikation nennt
|
||||
* `Record<string, string | string[]>`; ein Array erhält die fachliche
|
||||
* Reihenfolge verlässlich und lässt sich direkt rendern.
|
||||
*/
|
||||
export interface AgentDetailField {
|
||||
label: string
|
||||
value: string | string[]
|
||||
}
|
||||
|
||||
/** Fundstelle: Dokument, Seite, Zitat — Sinas Quellenzwang hängt daran. */
|
||||
export interface AgentSourceReference {
|
||||
id: string
|
||||
label: string
|
||||
documentName?: string
|
||||
locator?: string
|
||||
quote?: string
|
||||
}
|
||||
|
||||
/** Ein Nachrichten- oder Mailverlauf, wie er im Detail gezeigt wird (§5.4). */
|
||||
export interface AgentMessageEntry {
|
||||
id: string
|
||||
at: string
|
||||
from: string
|
||||
to?: string
|
||||
channel: AgentChannelType
|
||||
subject?: string
|
||||
body: string
|
||||
}
|
||||
|
||||
/** Simulierter Verarbeitungsschritt — bewusst in Alltagssprache. */
|
||||
export interface AgentProcessingStep {
|
||||
id: string
|
||||
label: string
|
||||
detail?: string
|
||||
}
|
||||
|
||||
export interface AgentWorkItemHistoryEntry {
|
||||
id: string
|
||||
at: string
|
||||
actor: string
|
||||
action: string
|
||||
note?: string
|
||||
}
|
||||
|
||||
/** Im «Anpassen»-Dialog editierbares Feld. */
|
||||
export interface AgentEditableField {
|
||||
id: string
|
||||
label: string
|
||||
value: string
|
||||
multiline?: boolean
|
||||
helperText?: string
|
||||
}
|
||||
|
||||
// ── Entität ───────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface AgentWorkItem {
|
||||
id: string
|
||||
agentId: string
|
||||
kind: AgentWorkItemKind
|
||||
area: AgentDomainArea
|
||||
title: string
|
||||
summary: string
|
||||
objectId?: string
|
||||
objectLabel?: string
|
||||
sourceChannel: AgentChannelType
|
||||
targetChannel?: AgentChannelType
|
||||
createdAt: string
|
||||
completedAt?: string
|
||||
priority: AgentWorkItemPriority
|
||||
status: AgentWorkItemStatus
|
||||
/** true → Reiter «Pendente Anfragen», false → «Erledigte Aufträge». */
|
||||
requiresDecision: boolean
|
||||
/** Grund der Rückfrage (§5.5) — nur bei pendenten Vorgängen. */
|
||||
escalationReason?: string
|
||||
/** Die konkrete Frage, die der Mensch beantworten muss. */
|
||||
decisionQuestion?: string
|
||||
inputs: AgentDetailField[]
|
||||
result: AgentDetailField[]
|
||||
processingSteps: AgentProcessingStep[]
|
||||
sourceReferences: AgentSourceReference[]
|
||||
messageThread?: AgentMessageEntry[]
|
||||
availableActions: AgentWorkItemAction[]
|
||||
editableFields?: AgentEditableField[]
|
||||
history: AgentWorkItemHistoryEntry[]
|
||||
rejectionReason?: string
|
||||
}
|
||||
|
||||
// ── Auswertung ────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Die drei Kennzahlen der Teamübersicht (§4.2).
|
||||
*
|
||||
* Abgrenzung der beiden pendenten Werte: Ein Vorgang zählt als Freigabeanfrage,
|
||||
* wenn er dem Menschen eine Entscheidung zur Bestätigung vorlegt (Freigeben,
|
||||
* 1-Klick-Bestätigung, Entscheidung treffen). Alles andere Pendente — Rückfragen,
|
||||
* fehlende Angaben, Verarbeitungs- und Systemprobleme — zählt als Nachricht.
|
||||
*/
|
||||
export interface AgentWorkloadKpis {
|
||||
completedTasks: number
|
||||
pendingMessages: number
|
||||
pendingApprovals: number
|
||||
}
|
||||
|
||||
// ── Schreib-Inputs ────────────────────────────────────────────────────────────
|
||||
|
||||
export interface UpdateAgentWorkItemInput {
|
||||
status?: AgentWorkItemStatus
|
||||
result?: AgentDetailField[]
|
||||
editableFields?: AgentEditableField[]
|
||||
rejectionReason?: string
|
||||
completedAt?: string
|
||||
requiresDecision?: boolean
|
||||
historyEntry?: Omit<AgentWorkItemHistoryEntry, 'id'>
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
/**
|
||||
* Property On — «Teamübersicht»: digitale Mitarbeiter.
|
||||
*
|
||||
* Namensgebung: Der Entitätstyp heisst `TeamAgent`, nicht `Agent`. Im Repo existiert
|
||||
* bereits `PowerOn` (AI-Backend-Proxy) und Claude-Code legt Worktrees unter `agent-*`
|
||||
* ab — ein nackter Typ `Agent` wäre in Suchen praktisch nicht auffindbar. Alle
|
||||
* Satellitentypen tragen deshalb das Präfix `Agent*`; generische Namen wie `Status`,
|
||||
* `Priority` oder `Channel` sind tabu, weil `src/domain/index.ts` per `export *`
|
||||
* bündelt und dort bereits `ReviewStatus`, `SourceType`, `RiskLevel` u. a. liegen.
|
||||
*
|
||||
* Fachliche Wahrheit ist der Agenten-Katalog: Rollen, Personalnummern und
|
||||
* Zuständigkeiten der sieben Kernagenten werden nicht verändert.
|
||||
*/
|
||||
|
||||
// ── Status & Autonomie ────────────────────────────────────────────────────────
|
||||
|
||||
export const AgentStatus = {
|
||||
ACTIVE: 'ACTIVE',
|
||||
BUILDING: 'BUILDING',
|
||||
PAUSED: 'PAUSED',
|
||||
} as const
|
||||
export type AgentStatus = typeof AgentStatus[keyof typeof AgentStatus]
|
||||
|
||||
export const AgentAutonomyLevel = {
|
||||
AUTONOMOUS: 'AUTONOMOUS',
|
||||
APPROVAL_REQUIRED: 'APPROVAL_REQUIRED',
|
||||
PROPOSAL_ONLY: 'PROPOSAL_ONLY',
|
||||
} as const
|
||||
export type AgentAutonomyLevel = typeof AgentAutonomyLevel[keyof typeof AgentAutonomyLevel]
|
||||
|
||||
// ── Kanäle ────────────────────────────────────────────────────────────────────
|
||||
|
||||
export const AgentChannelType = {
|
||||
EMAIL: 'EMAIL',
|
||||
TEAMS: 'TEAMS',
|
||||
WHATSAPP: 'WHATSAPP',
|
||||
WORKSPACE_CHAT: 'WORKSPACE_CHAT',
|
||||
CALENDAR: 'CALENDAR',
|
||||
WEB_FORM: 'WEB_FORM',
|
||||
PORTAL_LEAD_MAIL: 'PORTAL_LEAD_MAIL',
|
||||
PHONE_VOICE: 'PHONE_VOICE',
|
||||
PHONE_NOTE: 'PHONE_NOTE',
|
||||
AUDIO_OUTPUT: 'AUDIO_OUTPUT',
|
||||
STRUCTURED_MAIL_FALLBACK: 'STRUCTURED_MAIL_FALLBACK',
|
||||
} as const
|
||||
export type AgentChannelType = typeof AgentChannelType[keyof typeof AgentChannelType]
|
||||
|
||||
export const AgentChannelDirection = {
|
||||
INBOUND: 'INBOUND',
|
||||
OUTBOUND: 'OUTBOUND',
|
||||
BOTH: 'BOTH',
|
||||
} as const
|
||||
export type AgentChannelDirection = typeof AgentChannelDirection[keyof typeof AgentChannelDirection]
|
||||
|
||||
export const AgentConnectionStatus = {
|
||||
CONNECTED: 'CONNECTED',
|
||||
DISCONNECTED: 'DISCONNECTED',
|
||||
ROADMAP: 'ROADMAP',
|
||||
} as const
|
||||
export type AgentConnectionStatus = typeof AgentConnectionStatus[keyof typeof AgentConnectionStatus]
|
||||
|
||||
/** Simulierte Kanalkonfiguration — es werden nie echte Zugangsdaten erfasst. */
|
||||
export interface AgentChannelConfig {
|
||||
displayName: string
|
||||
senderAddress?: string
|
||||
inboxAddress?: string
|
||||
defaultRecipients: string[]
|
||||
autoReplyEnabled: boolean
|
||||
}
|
||||
|
||||
export interface AgentChannel {
|
||||
id: string
|
||||
type: AgentChannelType
|
||||
direction: AgentChannelDirection
|
||||
description: string
|
||||
status: AgentConnectionStatus
|
||||
enabled: boolean
|
||||
/** Kennzeichnet im Katalog als «optional» geführte Kanäle. */
|
||||
optional?: boolean
|
||||
config: AgentChannelConfig
|
||||
}
|
||||
|
||||
// ── Systeme ───────────────────────────────────────────────────────────────────
|
||||
|
||||
export const AgentSystemType = {
|
||||
M365_EXCHANGE: 'M365_EXCHANGE',
|
||||
OUTLOOK: 'OUTLOOK',
|
||||
SHAREPOINT: 'SHAREPOINT',
|
||||
ONEDRIVE: 'ONEDRIVE',
|
||||
FILESHARE: 'FILESHARE',
|
||||
DMS: 'DMS',
|
||||
CALENDAR: 'CALENDAR',
|
||||
IMMOTOP2: 'IMMOTOP2',
|
||||
ABACUS_ABAIMMO: 'ABACUS_ABAIMMO',
|
||||
RIMO_R5: 'RIMO_R5',
|
||||
GARAIO_REM: 'GARAIO_REM',
|
||||
CRM: 'CRM',
|
||||
ZEFIX: 'ZEFIX',
|
||||
SHAB: 'SHAB',
|
||||
PUBLIC_WEB: 'PUBLIC_WEB',
|
||||
PORTAL_LEAD_INBOX: 'PORTAL_LEAD_INBOX',
|
||||
WORKSPACE: 'WORKSPACE',
|
||||
} as const
|
||||
export type AgentSystemType = typeof AgentSystemType[keyof typeof AgentSystemType]
|
||||
|
||||
export const AgentAccessLevel = {
|
||||
READ: 'READ',
|
||||
WRITE: 'WRITE',
|
||||
READ_WRITE: 'READ_WRITE',
|
||||
} as const
|
||||
export type AgentAccessLevel = typeof AgentAccessLevel[keyof typeof AgentAccessLevel]
|
||||
|
||||
export interface AgentSystem {
|
||||
id: string
|
||||
type: AgentSystemType
|
||||
access: AgentAccessLevel
|
||||
status: AgentConnectionStatus
|
||||
/** Wofür der Agent dieses System nutzt — in Alltagssprache, ohne KI-Jargon. */
|
||||
usage: string
|
||||
/** Klartext zur Berechtigung, z. B. «Schreibt erst nach Freigabe». */
|
||||
permissionNote: string
|
||||
}
|
||||
|
||||
// ── Aufgaben ──────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface AgentTask {
|
||||
id: string
|
||||
title: string
|
||||
description: string
|
||||
enabled: boolean
|
||||
/** Zeitplan oder auslösendes Ereignis, z. B. «täglich 06:00» oder «bei Eingang». */
|
||||
schedule: string
|
||||
requiresApproval: boolean
|
||||
dependsOnChannel?: AgentChannelType
|
||||
dependsOnSystem?: AgentSystemType
|
||||
}
|
||||
|
||||
// ── Einstellungen ─────────────────────────────────────────────────────────────
|
||||
|
||||
export const AgentSettingKind = {
|
||||
TEXT: 'TEXT',
|
||||
NUMBER: 'NUMBER',
|
||||
SELECT: 'SELECT',
|
||||
MULTI_SELECT: 'MULTI_SELECT',
|
||||
BOOLEAN: 'BOOLEAN',
|
||||
TIME: 'TIME',
|
||||
} as const
|
||||
export type AgentSettingKind = typeof AgentSettingKind[keyof typeof AgentSettingKind]
|
||||
|
||||
export const AgentSettingGroup = {
|
||||
GENERAL: 'GENERAL',
|
||||
APPROVAL: 'APPROVAL',
|
||||
DELIVERY: 'DELIVERY',
|
||||
SPECIFIC: 'SPECIFIC',
|
||||
} as const
|
||||
export type AgentSettingGroup = typeof AgentSettingGroup[keyof typeof AgentSettingGroup]
|
||||
|
||||
export interface AgentSettingOption {
|
||||
value: string
|
||||
label: string
|
||||
}
|
||||
|
||||
export interface AgentSetting {
|
||||
id: string
|
||||
label: string
|
||||
description?: string
|
||||
kind: AgentSettingKind
|
||||
group: AgentSettingGroup
|
||||
value: string | number | boolean | string[]
|
||||
options?: AgentSettingOption[]
|
||||
unit?: string
|
||||
min?: number
|
||||
max?: number
|
||||
/** Fachlich nicht abschaltbar — z. B. Sinas Quellenzwang. */
|
||||
locked?: boolean
|
||||
lockedReason?: string
|
||||
}
|
||||
|
||||
// ── Kennzahlen & Profil ───────────────────────────────────────────────────────
|
||||
|
||||
export interface AgentMetric {
|
||||
id: string
|
||||
label: string
|
||||
value: string
|
||||
hint?: string
|
||||
}
|
||||
|
||||
/** Informationsbox unter dem Kopfbereich des Personalblatts (§7.6). */
|
||||
export interface AgentProfile {
|
||||
purpose: string
|
||||
input: string[]
|
||||
coreFlow: string[]
|
||||
output: string[]
|
||||
}
|
||||
|
||||
// ── Entität ───────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Farbton des Avatars. Bewusst ein Token-Schlüssel statt eines Hex-Werts —
|
||||
* `npm run check:tokens` liegt über dem Schwellwert und darf nicht wachsen.
|
||||
*/
|
||||
export const AgentAvatarTone = {
|
||||
BRAND: 'BRAND',
|
||||
SUCCESS: 'SUCCESS',
|
||||
INFO: 'INFO',
|
||||
SIGNAL: 'SIGNAL',
|
||||
WARNING: 'WARNING',
|
||||
GOLD: 'GOLD',
|
||||
SLATE: 'SLATE',
|
||||
} as const
|
||||
export type AgentAvatarTone = typeof AgentAvatarTone[keyof typeof AgentAvatarTone]
|
||||
|
||||
export interface TeamAgent {
|
||||
id: string
|
||||
name: string
|
||||
role: string
|
||||
personnelNumber: string
|
||||
department: string
|
||||
email: string
|
||||
avatarTone: AgentAvatarTone
|
||||
status: AgentStatus
|
||||
autonomyLevel: AgentAutonomyLevel
|
||||
/** Ein Satz für Karte und Listeneintrag. */
|
||||
shortDescription: string
|
||||
/** Freitext zur Autonomie, wo der Katalog differenziert («autonom beim Erinnern, Freigabe bei Eskalationen»). */
|
||||
autonomyNote: string
|
||||
profile: AgentProfile
|
||||
responsibilities: string[]
|
||||
tasks: AgentTask[]
|
||||
channels: AgentChannel[]
|
||||
systems: AgentSystem[]
|
||||
settings: AgentSetting[]
|
||||
metrics: AgentMetric[]
|
||||
/** Kurzkennzahl für die Agentenkarte auf der Teamübersicht. */
|
||||
headlineMetricId: string
|
||||
lastRun?: string
|
||||
}
|
||||
|
||||
// ── Schreib-Inputs ────────────────────────────────────────────────────────────
|
||||
|
||||
export interface UpdateTeamAgentInput {
|
||||
status?: AgentStatus
|
||||
tasks?: AgentTask[]
|
||||
channels?: AgentChannel[]
|
||||
settings?: AgentSetting[]
|
||||
}
|
||||
Reference in new issue
Block a user