Promote AgentCompany-New to the repository root and retire the old app
The legacy application - its src, tests, landing site, promo assets, docs, release tooling, CI workflows, and root configs - leaves the tree; it stays reachable in history. Everything under AgentCompany-New moves up unchanged: same package name, so the production userData/database path is untouched, and e2e/build scripts already resolved paths relative to the project. The developer cred.txt stays gitignored at its new location. Full verify:ci passes from the new root.
This commit is contained in:
1 parent
2350f744e3
commit
07d0b4587d
1904 files changed
+1133
-282075
No files matched your search
@@ -0,0 +1,213 @@
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
import type { SendMessageInput } from "../../shared/commands";
|
||||
import {
|
||||
BOARD_INTERVENTION_ACTION,
|
||||
BOARD_INTERVENTION_MODE,
|
||||
CHAT_THREAD_BOARD_STATE,
|
||||
RISK_LEVEL,
|
||||
type BoardInterventionAction,
|
||||
type BoardInterventionMode,
|
||||
} from "../../shared/domain";
|
||||
import { createId } from "../../shared/ids";
|
||||
import type { ResolvedMention } from "./chat-mentions";
|
||||
import { ApprovalService } from "./approval-service";
|
||||
import { AuditService } from "./audit-service";
|
||||
|
||||
interface PrincipalRef {
|
||||
type: "employee" | "agent";
|
||||
id: string;
|
||||
}
|
||||
|
||||
interface ApprovalRequester {
|
||||
type: "employee" | "agent" | "system";
|
||||
id: string | null;
|
||||
}
|
||||
|
||||
interface BoardCommand {
|
||||
mode: BoardInterventionMode;
|
||||
action: BoardInterventionAction;
|
||||
details: string;
|
||||
}
|
||||
|
||||
interface BoardTarget {
|
||||
type: "employee" | "agent" | "board";
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface RecordBoardInterventionInput {
|
||||
messageInput: SendMessageInput;
|
||||
sender: PrincipalRef | null;
|
||||
mentions: ResolvedMention[];
|
||||
args: string;
|
||||
messageId: string;
|
||||
threadId: string;
|
||||
spaceId: string;
|
||||
now: string;
|
||||
}
|
||||
|
||||
export class BoardInterventionService {
|
||||
private readonly audit: AuditService;
|
||||
private readonly approvals: ApprovalService;
|
||||
|
||||
constructor(private readonly db: DatabaseSync) {
|
||||
this.audit = new AuditService(db);
|
||||
this.approvals = new ApprovalService(db);
|
||||
}
|
||||
|
||||
record(input: RecordBoardInterventionInput): string {
|
||||
if (input.messageInput.senderType !== "board") throw new Error("Board intervention requires board sender.");
|
||||
const command = parseBoardCommand(input.args);
|
||||
const target = resolveBoardTarget(command, input.messageInput.orgId, input.mentions);
|
||||
const approvalId = command.action === BOARD_INTERVENTION_ACTION.FORCE_APPROVAL
|
||||
? this.createBoardApproval(input, command)
|
||||
: null;
|
||||
const interventionId = createId("bdi");
|
||||
this.db.prepare(`
|
||||
insert into board_interventions(
|
||||
id, org_id, thread_id, message_id, mode, action, details, target_type, target_id,
|
||||
approval_id, created_by_type, created_by_id, created_at
|
||||
) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(
|
||||
interventionId,
|
||||
input.messageInput.orgId,
|
||||
input.threadId,
|
||||
input.messageId,
|
||||
command.mode,
|
||||
command.action,
|
||||
command.details,
|
||||
target.type,
|
||||
target.id,
|
||||
approvalId,
|
||||
input.messageInput.senderType,
|
||||
input.messageInput.senderId,
|
||||
input.now,
|
||||
);
|
||||
this.applyBoardThreadState(input, command, target);
|
||||
this.audit.record({
|
||||
orgId: input.messageInput.orgId,
|
||||
entityType: "board_intervention",
|
||||
entityId: interventionId,
|
||||
actorType: input.messageInput.senderType,
|
||||
actorId: input.messageInput.senderId,
|
||||
action: "recorded",
|
||||
payload: {
|
||||
threadId: input.threadId,
|
||||
messageId: input.messageId,
|
||||
mode: command.mode,
|
||||
action: command.action,
|
||||
targetType: target.type,
|
||||
targetId: target.id,
|
||||
approvalId,
|
||||
},
|
||||
});
|
||||
return interventionId;
|
||||
}
|
||||
|
||||
private createBoardApproval(input: RecordBoardInterventionInput, command: BoardCommand): string {
|
||||
const requester = approvalRequester(input.messageInput.senderType, input.sender);
|
||||
return this.approvals.create({
|
||||
orgId: input.messageInput.orgId,
|
||||
kind: "board_force_approval",
|
||||
title: `Board approval ${spaceName(this.db, input.spaceId)}`,
|
||||
details: command.details,
|
||||
risk: RISK_LEVEL.HIGH,
|
||||
requestedByType: requester.type,
|
||||
requestedById: requester.id,
|
||||
}, {
|
||||
sourceMessageId: input.messageId,
|
||||
sourceThreadId: input.threadId,
|
||||
sourceEntityType: "chat_message",
|
||||
sourceEntityId: input.messageId,
|
||||
});
|
||||
}
|
||||
|
||||
private applyBoardThreadState(input: RecordBoardInterventionInput, command: BoardCommand, target: BoardTarget): void {
|
||||
if (command.action === BOARD_INTERVENTION_ACTION.FREEZE) {
|
||||
this.updateBoardThreadState(input, CHAT_THREAD_BOARD_STATE.FROZEN, null, null);
|
||||
return;
|
||||
}
|
||||
if (command.action === BOARD_INTERVENTION_ACTION.TAKEOVER) {
|
||||
this.updateBoardThreadState(input, CHAT_THREAD_BOARD_STATE.TAKEOVER, target.type, target.id);
|
||||
return;
|
||||
}
|
||||
if (command.action === BOARD_INTERVENTION_ACTION.ROLLBACK) {
|
||||
this.updateBoardThreadState(input, CHAT_THREAD_BOARD_STATE.OPEN, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
private updateBoardThreadState(
|
||||
input: RecordBoardInterventionInput,
|
||||
boardState: string,
|
||||
controlledByType: BoardTarget["type"] | null,
|
||||
controlledById: string | null,
|
||||
): void {
|
||||
this.db.prepare(`
|
||||
update chat_threads
|
||||
set board_state = ?, controlled_by_type = ?, controlled_by_id = ?, updated_at = ?
|
||||
where id = ? and org_id = ?
|
||||
`).run(boardState, controlledByType, controlledById, input.now, input.threadId, input.messageInput.orgId);
|
||||
this.audit.record({
|
||||
orgId: input.messageInput.orgId,
|
||||
entityType: "chat_thread",
|
||||
entityId: input.threadId,
|
||||
actorType: "board",
|
||||
actorId: input.messageInput.senderId,
|
||||
action: "board_state_changed",
|
||||
payload: { threadId: input.threadId, boardState, controlledByType, controlledById },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function parseBoardCommand(args: string): BoardCommand {
|
||||
const trimmed = args.trim();
|
||||
if (!trimmed) throw new Error("Slash command /board requires an action.");
|
||||
const [rawAction = "", ...rest] = trimmed.split(/\s+/);
|
||||
const action = normalizeBoardAction(rawAction);
|
||||
const details = rest.join(" ").trim();
|
||||
if (!details) throw new Error(`Slash command /board ${rawAction} requires details.`);
|
||||
return {
|
||||
mode: boardModeFor(action, rawAction),
|
||||
action,
|
||||
details,
|
||||
};
|
||||
}
|
||||
|
||||
function resolveBoardTarget(command: BoardCommand, orgId: string, mentions: ResolvedMention[]): BoardTarget {
|
||||
if (command.action !== BOARD_INTERVENTION_ACTION.TAKEOVER) return { type: "board", id: orgId };
|
||||
const principal = mentions.find((mention) => mention.target.type === "employee" || mention.target.type === "agent");
|
||||
if (!principal) return { type: "board", id: orgId };
|
||||
if (principal.target.type === "employee" || principal.target.type === "agent") {
|
||||
return { type: principal.target.type, id: principal.target.id };
|
||||
}
|
||||
return { type: "board", id: orgId };
|
||||
}
|
||||
|
||||
function normalizeBoardAction(value: string): BoardInterventionAction {
|
||||
const normalized = value.toLowerCase().replace(/-/g, "_");
|
||||
if (normalized === "observe") return BOARD_INTERVENTION_ACTION.DECISION;
|
||||
if (normalized === BOARD_INTERVENTION_ACTION.DECISION) return BOARD_INTERVENTION_ACTION.DECISION;
|
||||
if (normalized === BOARD_INTERVENTION_ACTION.FREEZE) return BOARD_INTERVENTION_ACTION.FREEZE;
|
||||
if (normalized === BOARD_INTERVENTION_ACTION.TAKEOVER) return BOARD_INTERVENTION_ACTION.TAKEOVER;
|
||||
if (normalized === BOARD_INTERVENTION_ACTION.ROLLBACK) return BOARD_INTERVENTION_ACTION.ROLLBACK;
|
||||
if (normalized === BOARD_INTERVENTION_ACTION.FORCE_APPROVAL) return BOARD_INTERVENTION_ACTION.FORCE_APPROVAL;
|
||||
throw new Error(`Unsupported board action: ${value}.`);
|
||||
}
|
||||
|
||||
function boardModeFor(action: BoardInterventionAction, rawAction: string): BoardInterventionMode {
|
||||
if (rawAction.toLowerCase() === "observe") return BOARD_INTERVENTION_MODE.OBSERVE;
|
||||
if (action === BOARD_INTERVENTION_ACTION.TAKEOVER) return BOARD_INTERVENTION_MODE.TAKEOVER;
|
||||
return BOARD_INTERVENTION_MODE.INTERVENE;
|
||||
}
|
||||
|
||||
function approvalRequester(senderType: SendMessageInput["senderType"], sender: PrincipalRef | null): ApprovalRequester {
|
||||
if (senderType === "system") return { type: "system", id: null };
|
||||
if (!sender) throw new Error("Approval requester is missing.");
|
||||
if (senderType === "agent") return { type: "agent", id: sender.id };
|
||||
return { type: "employee", id: sender.id };
|
||||
}
|
||||
|
||||
function spaceName(db: DatabaseSync, spaceId: string): string {
|
||||
const row = db.prepare("select name from chat_spaces where id = ?").get(spaceId) as { name: string } | undefined;
|
||||
if (!row) throw new Error("Chat space was not found.");
|
||||
return row.name;
|
||||
}
|
||||
Reference in new issue
Block a user