fix(ci): clean member work sync validation

This commit is contained in:
777genius 2026-04-30 00:05:58 +03:00
parent e634895a08
commit 7d08205a23
40 changed files with 194 additions and 169 deletions

View file

@ -1,2 +1,2 @@
export * from './ipc'; export * from './ipc';
export * from './types'; export type * from './types';

View file

@ -221,11 +221,11 @@ export interface MemberWorkSyncNudgePayload {
source: 'member-work-sync'; source: 'member-work-sync';
actionMode: 'do'; actionMode: 'do';
text: string; text: string;
taskRefs: Array<{ taskRefs: {
taskId: string; taskId: string;
displayId: string; displayId: string;
teamName: string; teamName: string;
}>; }[];
} }
export interface MemberWorkSyncOutboxItem { export interface MemberWorkSyncOutboxItem {

View file

@ -1,5 +1,6 @@
import type { MemberWorkSyncStatus, MemberWorkSyncStatusRequest } from '../../contracts';
import { MemberWorkSyncReconciler } from './MemberWorkSyncReconciler'; import { MemberWorkSyncReconciler } from './MemberWorkSyncReconciler';
import type { MemberWorkSyncStatus, MemberWorkSyncStatusRequest } from '../../contracts';
import type { MemberWorkSyncUseCaseDeps } from './ports'; import type { MemberWorkSyncUseCaseDeps } from './ports';
export class MemberWorkSyncDiagnosticsReader { export class MemberWorkSyncDiagnosticsReader {

View file

@ -1,5 +1,6 @@
import type { MemberWorkSyncMetricsRequest, MemberWorkSyncTeamMetrics } from '../../contracts';
import { assessMemberWorkSyncPhase2Readiness } from '../domain'; import { assessMemberWorkSyncPhase2Readiness } from '../domain';
import type { MemberWorkSyncMetricsRequest, MemberWorkSyncTeamMetrics } from '../../contracts';
import type { MemberWorkSyncUseCaseDeps } from './ports'; import type { MemberWorkSyncUseCaseDeps } from './ports';
function emptyMetrics(teamName: string, generatedAt: string): MemberWorkSyncTeamMetrics { function emptyMetrics(teamName: string, generatedAt: string): MemberWorkSyncTeamMetrics {

View file

@ -1,5 +1,6 @@
import type { MemberWorkSyncStatus } from '../../contracts';
import { buildMemberWorkSyncOutboxEnsureInput } from '../domain'; import { buildMemberWorkSyncOutboxEnsureInput } from '../domain';
import type { MemberWorkSyncStatus } from '../../contracts';
import type { MemberWorkSyncUseCaseDeps } from './ports'; import type { MemberWorkSyncUseCaseDeps } from './ports';
export interface MemberWorkSyncNudgeOutboxPlanResult { export interface MemberWorkSyncNudgeOutboxPlanResult {

View file

@ -1,5 +1,6 @@
import type { MemberWorkSyncReportIntentStatus } from '../../contracts';
import { MemberWorkSyncReporter } from './MemberWorkSyncReporter'; import { MemberWorkSyncReporter } from './MemberWorkSyncReporter';
import type { MemberWorkSyncReportIntentStatus } from '../../contracts';
import type { MemberWorkSyncUseCaseDeps } from './ports'; import type { MemberWorkSyncUseCaseDeps } from './ports';
export interface MemberWorkSyncPendingReportReplaySummary { export interface MemberWorkSyncPendingReportReplaySummary {

View file

@ -4,9 +4,11 @@ import {
decideMemberWorkSyncStatus, decideMemberWorkSyncStatus,
formatAgendaFingerprint, formatAgendaFingerprint,
} from '../domain'; } from '../domain';
import { MemberWorkSyncNudgeOutboxPlanner } from './MemberWorkSyncNudgeOutboxPlanner';
import type { MemberWorkSyncStatus, MemberWorkSyncStatusRequest } from '../../contracts'; import type { MemberWorkSyncStatus, MemberWorkSyncStatusRequest } from '../../contracts';
import type { MemberWorkSyncAgendaSourceResult, MemberWorkSyncUseCaseDeps } from './ports'; import type { MemberWorkSyncAgendaSourceResult, MemberWorkSyncUseCaseDeps } from './ports';
import { MemberWorkSyncNudgeOutboxPlanner } from './MemberWorkSyncNudgeOutboxPlanner';
export interface MemberWorkSyncReconcileContext { export interface MemberWorkSyncReconcileContext {
reconciledBy?: 'request' | 'queue'; reconciledBy?: 'request' | 'queue';

View file

@ -1,15 +1,17 @@
import { validateMemberWorkSyncReport } from '../domain';
import {
attachMemberWorkSyncReportToken,
finalizeMemberWorkSyncAgenda,
MemberWorkSyncReconciler,
} from './MemberWorkSyncReconciler';
import type { import type {
MemberWorkSyncReport, MemberWorkSyncReport,
MemberWorkSyncReportRequest, MemberWorkSyncReportRequest,
MemberWorkSyncReportResult, MemberWorkSyncReportResult,
MemberWorkSyncStatus, MemberWorkSyncStatus,
} from '../../contracts'; } from '../../contracts';
import { validateMemberWorkSyncReport } from '../domain';
import {
attachMemberWorkSyncReportToken,
finalizeMemberWorkSyncAgenda,
MemberWorkSyncReconciler,
} from './MemberWorkSyncReconciler';
import type { MemberWorkSyncUseCaseDeps } from './ports'; import type { MemberWorkSyncUseCaseDeps } from './ports';
export class MemberWorkSyncReporter { export class MemberWorkSyncReporter {
@ -28,7 +30,11 @@ export class MemberWorkSyncReporter {
: true; : true;
if (!teamActive) { if (!teamActive) {
const status = await this.reconciler.execute(request); const status = await this.reconciler.execute(request);
const rejectedStatus = await this.recordRejectedReport(status, request, 'team_runtime_inactive'); const rejectedStatus = await this.recordRejectedReport(
status,
request,
'team_runtime_inactive'
);
return { return {
accepted: false, accepted: false,
code: 'team_runtime_inactive', code: 'team_runtime_inactive',

View file

@ -5,6 +5,6 @@ export * from './MemberWorkSyncNudgeOutboxPlanner';
export * from './MemberWorkSyncPendingReportIntentReplayer'; export * from './MemberWorkSyncPendingReportIntentReplayer';
export * from './MemberWorkSyncReconciler'; export * from './MemberWorkSyncReconciler';
export * from './MemberWorkSyncReporter'; export * from './MemberWorkSyncReporter';
export * from './ports'; export type * from './ports';
export * from './RuntimeTurnSettledIngestor'; export * from './RuntimeTurnSettledIngestor';
export * from './RuntimeTurnSettledPorts'; export type * from './RuntimeTurnSettledPorts';

View file

@ -1,7 +1,5 @@
import type { import type {
MemberWorkSyncAgenda, MemberWorkSyncAgenda,
MemberWorkSyncTeamMetrics,
MemberWorkSyncProviderId,
MemberWorkSyncOutboxClaimInput, MemberWorkSyncOutboxClaimInput,
MemberWorkSyncOutboxCountRecentDeliveredInput, MemberWorkSyncOutboxCountRecentDeliveredInput,
MemberWorkSyncOutboxEnsureInput, MemberWorkSyncOutboxEnsureInput,
@ -10,11 +8,13 @@ import type {
MemberWorkSyncOutboxMarkDeliveredInput, MemberWorkSyncOutboxMarkDeliveredInput,
MemberWorkSyncOutboxMarkFailedInput, MemberWorkSyncOutboxMarkFailedInput,
MemberWorkSyncOutboxMarkSupersededInput, MemberWorkSyncOutboxMarkSupersededInput,
MemberWorkSyncProviderId,
MemberWorkSyncReport, MemberWorkSyncReport,
MemberWorkSyncReportIntent, MemberWorkSyncReportIntent,
MemberWorkSyncReportIntentStatus, MemberWorkSyncReportIntentStatus,
MemberWorkSyncReportRequest, MemberWorkSyncReportRequest,
MemberWorkSyncStatus, MemberWorkSyncStatus,
MemberWorkSyncTeamMetrics,
} from '../../contracts'; } from '../../contracts';
export interface MemberWorkSyncClockPort { export interface MemberWorkSyncClockPort {

View file

@ -1,8 +1,3 @@
import type {
MemberWorkSyncActionableWorkItem,
MemberWorkSyncAgenda,
MemberWorkSyncProviderId,
} from '../../contracts';
import { import {
buildAgendaFingerprintPayload, buildAgendaFingerprintPayload,
canonicalizeAgendaFingerprintPayload, canonicalizeAgendaFingerprintPayload,
@ -11,6 +6,12 @@ import {
import { resolveCurrentReviewOwner, type ReviewHistoryEventLike } from './currentReviewCycle'; import { resolveCurrentReviewOwner, type ReviewHistoryEventLike } from './currentReviewCycle';
import { isReservedMemberName, normalizeMemberName, sameMemberName } from './memberName'; import { isReservedMemberName, normalizeMemberName, sameMemberName } from './memberName';
import type {
MemberWorkSyncActionableWorkItem,
MemberWorkSyncAgenda,
MemberWorkSyncProviderId,
} from '../../contracts';
export interface MemberWorkSyncTaskLike { export interface MemberWorkSyncTaskLike {
id: string; id: string;
displayId?: string; displayId?: string;

View file

@ -19,7 +19,7 @@ export interface AgendaFingerprintPayload {
version: 1; version: 1;
teamName: string; teamName: string;
memberName: string; memberName: string;
items: Array<{ items: {
taskId: string; taskId: string;
displayId?: string; displayId?: string;
subject: string; subject: string;
@ -27,7 +27,7 @@ export interface AgendaFingerprintPayload {
assignee: string; assignee: string;
priority: string; priority: string;
evidence: MemberWorkSyncActionableWorkItem['evidence']; evidence: MemberWorkSyncActionableWorkItem['evidence'];
}>; }[];
sourceRevision?: string; sourceRevision?: string;
} }

View file

@ -1,9 +1,10 @@
import { isReservedMemberName, normalizeMemberName, sameMemberName } from './memberName';
import type { import type {
MemberWorkSyncAgenda, MemberWorkSyncAgenda,
MemberWorkSyncReportRequest, MemberWorkSyncReportRequest,
MemberWorkSyncReportState, MemberWorkSyncReportState,
} from '../../contracts'; } from '../../contracts';
import { isReservedMemberName, normalizeMemberName, sameMemberName } from './memberName';
export interface MemberWorkSyncReportValidation { export interface MemberWorkSyncReportValidation {
ok: boolean; ok: boolean;

View file

@ -2,8 +2,8 @@ export * from './ActionableWorkAgenda';
export * from './AgendaFingerprint'; export * from './AgendaFingerprint';
export * from './currentReviewCycle'; export * from './currentReviewCycle';
export * from './memberName'; export * from './memberName';
export * from './MemberWorkSyncPhase2Readiness';
export * from './MemberWorkSyncNudge'; export * from './MemberWorkSyncNudge';
export * from './MemberWorkSyncPhase2Readiness';
export * from './MemberWorkSyncReportValidator'; export * from './MemberWorkSyncReportValidator';
export * from './RuntimeTurnSettledEvent'; export * from './RuntimeTurnSettledEvent';
export * from './RuntimeTurnSettledProvider'; export * from './RuntimeTurnSettledProvider';

View file

@ -1,3 +1,5 @@
import { createLogger } from '@shared/utils/logger';
import { import {
MEMBER_WORK_SYNC_GET_METRICS, MEMBER_WORK_SYNC_GET_METRICS,
MEMBER_WORK_SYNC_GET_STATUS, MEMBER_WORK_SYNC_GET_STATUS,
@ -9,7 +11,6 @@ import {
type MemberWorkSyncStatusRequest, type MemberWorkSyncStatusRequest,
type MemberWorkSyncTeamMetrics, type MemberWorkSyncTeamMetrics,
} from '../../../contracts'; } from '../../../contracts';
import { createLogger } from '@shared/utils/logger';
import type { MemberWorkSyncFeatureFacade } from '../../composition/createMemberWorkSyncFeature'; import type { MemberWorkSyncFeatureFacade } from '../../composition/createMemberWorkSyncFeature';
import type { IpcMain } from 'electron'; import type { IpcMain } from 'electron';

View file

@ -1,5 +1,6 @@
import { TeamInboxReader } from '@main/services/team/TeamInboxReader'; import { TeamInboxReader } from '@main/services/team/TeamInboxReader';
import { TeamInboxWriter } from '@main/services/team/TeamInboxWriter'; import { TeamInboxWriter } from '@main/services/team/TeamInboxWriter';
import type { MemberWorkSyncInboxNudgePort } from '../../../core/application'; import type { MemberWorkSyncInboxNudgePort } from '../../../core/application';
export class TeamInboxMemberWorkSyncNudgeSink implements MemberWorkSyncInboxNudgePort { export class TeamInboxMemberWorkSyncNudgeSink implements MemberWorkSyncInboxNudgePort {

View file

@ -1,16 +1,16 @@
import path from 'path';
import { isReservedMemberName, normalizeMemberName } from '../../../core/domain';
import type {
RuntimeTurnSettledTargetResolution,
RuntimeTurnSettledTargetResolverPort,
} from '../../../core/application';
import { TeamMemberLogsFinder } from '@main/services/team/TeamMemberLogsFinder'; import { TeamMemberLogsFinder } from '@main/services/team/TeamMemberLogsFinder';
import { import {
inferTeamProviderIdFromModel, inferTeamProviderIdFromModel,
normalizeOptionalTeamProviderId, normalizeOptionalTeamProviderId,
} from '@shared/utils/teamProvider'; } from '@shared/utils/teamProvider';
import path from 'path';
import { isReservedMemberName, normalizeMemberName } from '../../../core/domain';
import type {
RuntimeTurnSettledTargetResolution,
RuntimeTurnSettledTargetResolverPort,
} from '../../../core/application';
import type { RuntimeTurnSettledEvent } from '../../../core/domain'; import type { RuntimeTurnSettledEvent } from '../../../core/domain';
import type { TeamConfigReader } from '@main/services/team/TeamConfigReader'; import type { TeamConfigReader } from '@main/services/team/TeamConfigReader';
import type { TeamMembersMetaStore } from '@main/services/team/TeamMembersMetaStore'; import type { TeamMembersMetaStore } from '@main/services/team/TeamMembersMetaStore';
@ -69,9 +69,7 @@ function normalizePath(value: string | undefined): string | null {
return path.resolve(value.trim()); return path.resolve(value.trim());
} }
export class TeamRuntimeTurnSettledTargetResolver export class TeamRuntimeTurnSettledTargetResolver implements RuntimeTurnSettledTargetResolverPort {
implements RuntimeTurnSettledTargetResolverPort
{
private readonly memberLogsFinder: AttributedMemberFileSource; private readonly memberLogsFinder: AttributedMemberFileSource;
private readonly maxTeamsToScan: number; private readonly maxTeamsToScan: number;
@ -99,12 +97,12 @@ export class TeamRuntimeTurnSettledTargetResolver
.filter((team) => !team.deletedAt) .filter((team) => !team.deletedAt)
.slice(0, this.maxTeamsToScan); .slice(0, this.maxTeamsToScan);
const candidates: Array<{ const candidates: {
teamName: string; teamName: string;
memberName: string; memberName: string;
exactPath: boolean; exactPath: boolean;
mtimeMs: number; mtimeMs: number;
}> = []; }[] = [];
for (const team of teams) { for (const team of teams) {
const attributedFiles = await this.memberLogsFinder const attributedFiles = await this.memberLogsFinder

View file

@ -1,19 +1,20 @@
import {
buildActionableWorkAgenda,
isReservedMemberName,
normalizeMemberName,
type MemberWorkSyncMemberLike,
} from '../../../core/domain';
import type {
MemberWorkSyncAgendaSourcePort,
MemberWorkSyncAgendaSourceResult,
MemberWorkSyncHashPort,
} from '../../../core/application';
import { import {
inferTeamProviderIdFromModel, inferTeamProviderIdFromModel,
normalizeOptionalTeamProviderId, normalizeOptionalTeamProviderId,
} from '@shared/utils/teamProvider'; } from '@shared/utils/teamProvider';
import {
buildActionableWorkAgenda,
isReservedMemberName,
type MemberWorkSyncMemberLike,
normalizeMemberName,
} from '../../../core/domain';
import type {
MemberWorkSyncAgendaSourcePort,
MemberWorkSyncAgendaSourceResult,
MemberWorkSyncHashPort,
} from '../../../core/application';
import type { TeamConfigReader } from '@main/services/team/TeamConfigReader'; import type { TeamConfigReader } from '@main/services/team/TeamConfigReader';
import type { TeamKanbanManager } from '@main/services/team/TeamKanbanManager'; import type { TeamKanbanManager } from '@main/services/team/TeamKanbanManager';
import type { TeamMembersMetaStore } from '@main/services/team/TeamMembersMetaStore'; import type { TeamMembersMetaStore } from '@main/services/team/TeamMembersMetaStore';

View file

@ -1,5 +1,6 @@
import { readFile } from 'fs/promises'; import { readFile } from 'fs/promises';
import { join } from 'path'; import { join } from 'path';
import type { MemberWorkSyncWatchdogCooldownPort } from '../../../core/application'; import type { MemberWorkSyncWatchdogCooldownPort } from '../../../core/application';
const DEFAULT_WATCHDOG_COOLDOWN_MS = 10 * 60_000; const DEFAULT_WATCHDOG_COOLDOWN_MS = 10 * 60_000;
@ -54,10 +55,7 @@ export class TeamTaskStallJournalWorkSyncCooldown implements MemberWorkSyncWatch
return alertedAt != null && now - alertedAt <= this.cooldownMs; return alertedAt != null && now - alertedAt <= this.cooldownMs;
}); });
} catch (error) { } catch (error) {
if ((error as NodeJS.ErrnoException).code === 'ENOENT') { return (error as NodeJS.ErrnoException).code !== 'ENOENT';
return false;
}
return true;
} }
} }
} }

View file

@ -1,3 +1,43 @@
import {
MemberWorkSyncDiagnosticsReader,
MemberWorkSyncMetricsReader,
MemberWorkSyncNudgeDispatcher,
type MemberWorkSyncNudgeDispatchSummary,
MemberWorkSyncPendingReportIntentReplayer,
type MemberWorkSyncPendingReportReplaySummary,
type MemberWorkSyncReconcileContext,
MemberWorkSyncReconciler,
MemberWorkSyncReporter,
type RuntimeTurnSettledDrainSummary,
RuntimeTurnSettledIngestor,
type RuntimeTurnSettledTargetResolverPort,
} from '../../core/application';
import { MemberWorkSyncTeamChangeRouter } from '../adapters/input/MemberWorkSyncTeamChangeRouter';
import { TeamInboxMemberWorkSyncNudgeSink } from '../adapters/output/TeamInboxMemberWorkSyncNudgeSink';
import { TeamRuntimeTurnSettledTargetResolver } from '../adapters/output/TeamRuntimeTurnSettledTargetResolver';
import { TeamTaskAgendaSource } from '../adapters/output/TeamTaskAgendaSource';
import { TeamTaskStallJournalWorkSyncCooldown } from '../adapters/output/TeamTaskStallJournalWorkSyncCooldown';
import { ClaudeStopHookPayloadNormalizer } from '../infrastructure/ClaudeStopHookPayloadNormalizer';
import { CodexNativeTurnSettledPayloadNormalizer } from '../infrastructure/CodexNativeTurnSettledPayloadNormalizer';
import { CompositeRuntimeTurnSettledPayloadNormalizer } from '../infrastructure/CompositeRuntimeTurnSettledPayloadNormalizer';
import { FileRuntimeTurnSettledEventStore } from '../infrastructure/FileRuntimeTurnSettledEventStore';
import { HmacMemberWorkSyncReportTokenAdapter } from '../infrastructure/HmacMemberWorkSyncReportTokenAdapter';
import { JsonMemberWorkSyncStore } from '../infrastructure/JsonMemberWorkSyncStore';
import {
MemberWorkSyncEventQueue,
type MemberWorkSyncQueueDiagnostics,
} from '../infrastructure/MemberWorkSyncEventQueue';
import { MemberWorkSyncNudgeDispatchScheduler } from '../infrastructure/MemberWorkSyncNudgeDispatchScheduler';
import { MemberWorkSyncStorePaths } from '../infrastructure/MemberWorkSyncStorePaths';
import { MemberWorkSyncToolActivityBusySignal } from '../infrastructure/MemberWorkSyncToolActivityBusySignal';
import { NodeHashAdapter } from '../infrastructure/NodeHashAdapter';
import { RuntimeTurnSettledDrainScheduler } from '../infrastructure/RuntimeTurnSettledDrainScheduler';
import { buildRuntimeTurnSettledEnvironment } from '../infrastructure/runtimeTurnSettledEnvironment';
import { buildRuntimeTurnSettledHookSettings } from '../infrastructure/runtimeTurnSettledHookSettings';
import { RuntimeTurnSettledSpoolPaths } from '../infrastructure/RuntimeTurnSettledSpoolPaths';
import { ShellRuntimeTurnSettledHookScriptInstaller } from '../infrastructure/ShellRuntimeTurnSettledHookScriptInstaller';
import { SystemClockAdapter } from '../infrastructure/SystemClockAdapter';
import type { import type {
MemberWorkSyncMetricsRequest, MemberWorkSyncMetricsRequest,
MemberWorkSyncReportRequest, MemberWorkSyncReportRequest,
@ -6,53 +46,13 @@ import type {
MemberWorkSyncStatusRequest, MemberWorkSyncStatusRequest,
MemberWorkSyncTeamMetrics, MemberWorkSyncTeamMetrics,
} from '../../contracts'; } from '../../contracts';
import { import type { MemberWorkSyncLoggerPort } from '../../core/application';
MemberWorkSyncDiagnosticsReader, import type { RuntimeTurnSettledProvider } from '../../core/domain';
MemberWorkSyncMetricsReader,
MemberWorkSyncNudgeDispatcher,
type MemberWorkSyncNudgeDispatchSummary,
MemberWorkSyncPendingReportIntentReplayer,
type MemberWorkSyncPendingReportReplaySummary,
MemberWorkSyncReconciler,
MemberWorkSyncReporter,
type MemberWorkSyncReconcileContext,
RuntimeTurnSettledIngestor,
type RuntimeTurnSettledDrainSummary,
type RuntimeTurnSettledTargetResolverPort,
} from '../../core/application';
import { MemberWorkSyncTeamChangeRouter } from '../adapters/input/MemberWorkSyncTeamChangeRouter';
import { TeamInboxMemberWorkSyncNudgeSink } from '../adapters/output/TeamInboxMemberWorkSyncNudgeSink';
import { TeamTaskStallJournalWorkSyncCooldown } from '../adapters/output/TeamTaskStallJournalWorkSyncCooldown';
import { TeamTaskAgendaSource } from '../adapters/output/TeamTaskAgendaSource';
import { TeamRuntimeTurnSettledTargetResolver } from '../adapters/output/TeamRuntimeTurnSettledTargetResolver';
import { ClaudeStopHookPayloadNormalizer } from '../infrastructure/ClaudeStopHookPayloadNormalizer';
import { CodexNativeTurnSettledPayloadNormalizer } from '../infrastructure/CodexNativeTurnSettledPayloadNormalizer';
import { CompositeRuntimeTurnSettledPayloadNormalizer } from '../infrastructure/CompositeRuntimeTurnSettledPayloadNormalizer';
import { FileRuntimeTurnSettledEventStore } from '../infrastructure/FileRuntimeTurnSettledEventStore';
import { HmacMemberWorkSyncReportTokenAdapter } from '../infrastructure/HmacMemberWorkSyncReportTokenAdapter';
import {
MemberWorkSyncEventQueue,
type MemberWorkSyncQueueDiagnostics,
} from '../infrastructure/MemberWorkSyncEventQueue';
import { JsonMemberWorkSyncStore } from '../infrastructure/JsonMemberWorkSyncStore';
import { MemberWorkSyncStorePaths } from '../infrastructure/MemberWorkSyncStorePaths';
import { MemberWorkSyncNudgeDispatchScheduler } from '../infrastructure/MemberWorkSyncNudgeDispatchScheduler';
import { MemberWorkSyncToolActivityBusySignal } from '../infrastructure/MemberWorkSyncToolActivityBusySignal';
import { NodeHashAdapter } from '../infrastructure/NodeHashAdapter';
import { RuntimeTurnSettledDrainScheduler } from '../infrastructure/RuntimeTurnSettledDrainScheduler';
import { RuntimeTurnSettledSpoolPaths } from '../infrastructure/RuntimeTurnSettledSpoolPaths';
import { ShellRuntimeTurnSettledHookScriptInstaller } from '../infrastructure/ShellRuntimeTurnSettledHookScriptInstaller';
import { buildRuntimeTurnSettledEnvironment } from '../infrastructure/runtimeTurnSettledEnvironment';
import { buildRuntimeTurnSettledHookSettings } from '../infrastructure/runtimeTurnSettledHookSettings';
import { SystemClockAdapter } from '../infrastructure/SystemClockAdapter';
import type { TeamConfigReader } from '@main/services/team/TeamConfigReader'; import type { TeamConfigReader } from '@main/services/team/TeamConfigReader';
import type { TeamKanbanManager } from '@main/services/team/TeamKanbanManager'; import type { TeamKanbanManager } from '@main/services/team/TeamKanbanManager';
import type { TeamMembersMetaStore } from '@main/services/team/TeamMembersMetaStore'; import type { TeamMembersMetaStore } from '@main/services/team/TeamMembersMetaStore';
import type { TeamTaskReader } from '@main/services/team/TeamTaskReader'; import type { TeamTaskReader } from '@main/services/team/TeamTaskReader';
import type { TeamChangeEvent } from '@shared/types'; import type { TeamChangeEvent } from '@shared/types';
import type { MemberWorkSyncLoggerPort } from '../../core/application';
import type { RuntimeTurnSettledProvider } from '../../core/domain';
export const MEMBER_WORK_SYNC_NUDGE_SIDE_EFFECTS_ENV = export const MEMBER_WORK_SYNC_NUDGE_SIDE_EFFECTS_ENV =
'CLAUDE_TEAM_MEMBER_WORK_SYNC_NUDGES_ENABLED'; 'CLAUDE_TEAM_MEMBER_WORK_SYNC_NUDGES_ENABLED';
@ -208,17 +208,18 @@ export function createMemberWorkSyncFeature(deps: {
drain: () => runtimeTurnSettledIngestor.drainPending(), drain: () => runtimeTurnSettledIngestor.drainPending(),
logger: deps.logger, logger: deps.logger,
}); });
const nudgeDispatchScheduler = nudgeSideEffectsEnabled && deps.listLifecycleActiveTeamNames const nudgeDispatchScheduler =
? new MemberWorkSyncNudgeDispatchScheduler({ nudgeSideEffectsEnabled && deps.listLifecycleActiveTeamNames
listLifecycleActiveTeamNames: deps.listLifecycleActiveTeamNames, ? new MemberWorkSyncNudgeDispatchScheduler({
dispatchDue: (teamNames) => listLifecycleActiveTeamNames: deps.listLifecycleActiveTeamNames,
nudgeDispatcher.dispatchDue({ dispatchDue: (teamNames) =>
teamNames, nudgeDispatcher.dispatchDue({
claimedBy: `member-work-sync:${process.pid}:scheduled`, teamNames,
}), claimedBy: `member-work-sync:${process.pid}:scheduled`,
logger: deps.logger, }),
}) logger: deps.logger,
: null; })
: null;
runtimeTurnSettledDrainScheduler.start(); runtimeTurnSettledDrainScheduler.start();
nudgeDispatchScheduler?.start(); nudgeDispatchScheduler?.start();

View file

@ -1,10 +1,11 @@
export type { RuntimeTurnSettledProvider } from '../core/domain';
export { export {
registerMemberWorkSyncIpc, registerMemberWorkSyncIpc,
removeMemberWorkSyncIpc, removeMemberWorkSyncIpc,
} from './adapters/input/registerMemberWorkSyncIpc'; } from './adapters/input/registerMemberWorkSyncIpc';
export type { MemberWorkSyncFeatureFacade } from './composition/createMemberWorkSyncFeature';
export { export {
MEMBER_WORK_SYNC_NUDGE_SIDE_EFFECTS_ENV,
createMemberWorkSyncFeature, createMemberWorkSyncFeature,
MEMBER_WORK_SYNC_NUDGE_SIDE_EFFECTS_ENV,
resolveMemberWorkSyncNudgeSideEffectsEnabled, resolveMemberWorkSyncNudgeSideEffectsEnabled,
} from './composition/createMemberWorkSyncFeature'; } from './composition/createMemberWorkSyncFeature';
export type { MemberWorkSyncFeatureFacade } from './composition/createMemberWorkSyncFeature';

View file

@ -2,6 +2,7 @@ import {
buildRuntimeTurnSettledSourceId, buildRuntimeTurnSettledSourceId,
type RuntimeTurnSettledProvider, type RuntimeTurnSettledProvider,
} from '../../core/domain'; } from '../../core/domain';
import type { import type {
MemberWorkSyncHashPort, MemberWorkSyncHashPort,
RuntimeTurnSettledPayloadNormalization, RuntimeTurnSettledPayloadNormalization,

View file

@ -2,6 +2,7 @@ import {
buildRuntimeTurnSettledSourceId, buildRuntimeTurnSettledSourceId,
type RuntimeTurnSettledProvider, type RuntimeTurnSettledProvider,
} from '../../core/domain'; } from '../../core/domain';
import type { import type {
MemberWorkSyncHashPort, MemberWorkSyncHashPort,
RuntimeTurnSettledPayloadNormalization, RuntimeTurnSettledPayloadNormalization,
@ -28,9 +29,7 @@ function getString(record: Record<string, unknown>, ...keys: string[]): string |
return undefined; return undefined;
} }
export class CodexNativeTurnSettledPayloadNormalizer export class CodexNativeTurnSettledPayloadNormalizer implements RuntimeTurnSettledPayloadNormalizerPort {
implements RuntimeTurnSettledPayloadNormalizerPort
{
constructor(private readonly hash: MemberWorkSyncHashPort) {} constructor(private readonly hash: MemberWorkSyncHashPort) {}
normalize(input: { normalize(input: {

View file

@ -2,6 +2,7 @@ import { mkdir, readdir, readFile, rename, rm, stat, writeFile } from 'fs/promis
import path from 'path'; import path from 'path';
import { isRuntimeTurnSettledProvider } from '../../core/domain'; import { isRuntimeTurnSettledProvider } from '../../core/domain';
import type { import type {
RuntimeTurnSettledClaimedPayload, RuntimeTurnSettledClaimedPayload,
RuntimeTurnSettledEventStorePort, RuntimeTurnSettledEventStorePort,
@ -131,7 +132,11 @@ export class FileRuntimeTurnSettledEventStore implements RuntimeTurnSettledEvent
): Promise<void> { ): Promise<void> {
const processedPath = path.join(this.deps.paths.getProcessedDir(), payload.fileName); const processedPath = path.join(this.deps.paths.getProcessedDir(), payload.fileName);
await moveFileBestEffort(payload.filePath, processedPath); await moveFileBestEffort(payload.filePath, processedPath);
await writeFile(buildMetaFilePath(processedPath), `${JSON.stringify(result, null, 2)}\n`, 'utf8'); await writeFile(
buildMetaFilePath(processedPath),
`${JSON.stringify(result, null, 2)}\n`,
'utf8'
);
await this.cleanupDirectory(this.deps.paths.getProcessedDir()); await this.cleanupDirectory(this.deps.paths.getProcessedDir());
} }
@ -164,9 +169,7 @@ export class FileRuntimeTurnSettledEventStore implements RuntimeTurnSettledEvent
entries entries
.filter( .filter(
(entry) => (entry) =>
entry.isFile() && entry.isFile() && !entry.name.startsWith('.') && !entry.name.endsWith('.meta.json')
!entry.name.startsWith('.') &&
!entry.name.endsWith('.meta.json')
) )
.map(async (entry) => { .map(async (entry) => {
const processingPath = path.join(this.deps.paths.getProcessingDir(), entry.name); const processingPath = path.join(this.deps.paths.getProcessingDir(), entry.name);

View file

@ -6,8 +6,8 @@ import { atomicWriteAsync } from '@main/utils/atomicWrite';
import type { import type {
MemberWorkSyncReportTokenCreateInput, MemberWorkSyncReportTokenCreateInput,
MemberWorkSyncReportTokenPort, MemberWorkSyncReportTokenPort,
MemberWorkSyncReportTokenVerifyInput,
MemberWorkSyncReportTokenVerification, MemberWorkSyncReportTokenVerification,
MemberWorkSyncReportTokenVerifyInput,
} from '../../core/application'; } from '../../core/application';
import type { MemberWorkSyncStorePaths } from './MemberWorkSyncStorePaths'; import type { MemberWorkSyncStorePaths } from './MemberWorkSyncStorePaths';

View file

@ -1,8 +1,10 @@
import { withFileLock } from '@main/services/team/fileLock';
import { atomicWriteAsync } from '@main/utils/atomicWrite'; import { atomicWriteAsync } from '@main/utils/atomicWrite';
import { createHash } from 'crypto'; import { createHash } from 'crypto';
import { mkdir, readFile, rename } from 'fs/promises'; import { mkdir, readFile, rename } from 'fs/promises';
import { withFileLock } from '@main/services/team/fileLock'; import { assessMemberWorkSyncPhase2Readiness } from '../../core/domain';
import type { import type {
MemberWorkSyncMetricEvent, MemberWorkSyncMetricEvent,
MemberWorkSyncOutboxClaimInput, MemberWorkSyncOutboxClaimInput,
@ -19,7 +21,6 @@ import type {
MemberWorkSyncStatusState, MemberWorkSyncStatusState,
MemberWorkSyncTeamMetrics, MemberWorkSyncTeamMetrics,
} from '../../contracts'; } from '../../contracts';
import { assessMemberWorkSyncPhase2Readiness } from '../../core/domain';
import type { import type {
MemberWorkSyncOutboxStorePort, MemberWorkSyncOutboxStorePort,
MemberWorkSyncReportStorePort, MemberWorkSyncReportStorePort,
@ -288,13 +289,10 @@ export class JsonMemberWorkSyncStore
stateCounts, stateCounts,
actionableItemCount, actionableItemCount,
wouldNudgeCount: recentEvents.filter((event) => event.kind === 'would_nudge').length, wouldNudgeCount: recentEvents.filter((event) => event.kind === 'would_nudge').length,
fingerprintChangeCount: recentEvents.filter( fingerprintChangeCount: recentEvents.filter((event) => event.kind === 'fingerprint_changed')
(event) => event.kind === 'fingerprint_changed'
).length,
reportAcceptedCount: recentEvents.filter((event) => event.kind === 'report_accepted')
.length,
reportRejectedCount: recentEvents.filter((event) => event.kind === 'report_rejected')
.length, .length,
reportAcceptedCount: recentEvents.filter((event) => event.kind === 'report_accepted').length,
reportRejectedCount: recentEvents.filter((event) => event.kind === 'report_rejected').length,
recentEvents, recentEvents,
}; };
return { return {
@ -349,7 +347,7 @@ export class JsonMemberWorkSyncStore
await withFileLock(this.paths.getPendingReportsPath(teamName), async () => { await withFileLock(this.paths.getPendingReportsPath(teamName), async () => {
const existing = await this.readPendingFile(teamName); const existing = await this.readPendingFile(teamName);
const current = existing.intents[id]; const current = existing.intents[id];
if (!current || current.status !== 'pending') { if (current?.status !== 'pending') {
return; return;
} }
existing.intents[id] = { existing.intents[id] = {
@ -471,7 +469,7 @@ export class JsonMemberWorkSyncStore
async markDelivered(input: MemberWorkSyncOutboxMarkDeliveredInput): Promise<void> { async markDelivered(input: MemberWorkSyncOutboxMarkDeliveredInput): Promise<void> {
await this.updateOutboxItem(input.teamName, input.id, (current) => { await this.updateOutboxItem(input.teamName, input.id, (current) => {
if (!current || current.attemptGeneration !== input.attemptGeneration) { if (current?.attemptGeneration !== input.attemptGeneration) {
return current; return current;
} }
const next: MemberWorkSyncOutboxItem = { const next: MemberWorkSyncOutboxItem = {
@ -501,7 +499,7 @@ export class JsonMemberWorkSyncStore
async markFailed(input: MemberWorkSyncOutboxMarkFailedInput): Promise<void> { async markFailed(input: MemberWorkSyncOutboxMarkFailedInput): Promise<void> {
await this.updateOutboxItem(input.teamName, input.id, (current) => { await this.updateOutboxItem(input.teamName, input.id, (current) => {
if (!current || current.attemptGeneration !== input.attemptGeneration) { if (current?.attemptGeneration !== input.attemptGeneration) {
return current; return current;
} }
const next: MemberWorkSyncOutboxItem = { const next: MemberWorkSyncOutboxItem = {
@ -589,9 +587,7 @@ export class JsonMemberWorkSyncStore
private async updateOutboxItem( private async updateOutboxItem(
teamName: string, teamName: string,
id: string, id: string,
updater: ( updater: (current: MemberWorkSyncOutboxItem | undefined) => MemberWorkSyncOutboxItem | undefined
current: MemberWorkSyncOutboxItem | undefined
) => MemberWorkSyncOutboxItem | undefined
): Promise<void> { ): Promise<void> {
await this.enqueue(teamName, async () => { await this.enqueue(teamName, async () => {
await withFileLock(this.paths.getOutboxPath(teamName), async () => { await withFileLock(this.paths.getOutboxPath(teamName), async () => {

View file

@ -1,5 +1,5 @@
import type { MemberWorkSyncReconcileContext } from '../../core/application/MemberWorkSyncReconciler';
import type { MemberWorkSyncLoggerPort } from '../../core/application'; import type { MemberWorkSyncLoggerPort } from '../../core/application';
import type { MemberWorkSyncReconcileContext } from '../../core/application/MemberWorkSyncReconciler';
export type MemberWorkSyncTriggerReason = export type MemberWorkSyncTriggerReason =
| 'startup_scan' | 'startup_scan'

View file

@ -1,5 +1,4 @@
import type { MemberWorkSyncBusySignalPort } from '../../core/application'; import type { MemberWorkSyncBusySignalPort } from '../../core/application';
import type { TeamChangeEvent, ToolActivityEventPayload } from '@shared/types'; import type { TeamChangeEvent, ToolActivityEventPayload } from '@shared/types';
const DEFAULT_TOOL_ACTIVITY_BUSY_GRACE_MS = 90_000; const DEFAULT_TOOL_ACTIVITY_BUSY_GRACE_MS = 90_000;

View file

@ -1,6 +1,6 @@
import fs from 'fs/promises'; import fs from 'fs/promises';
import { RuntimeTurnSettledSpoolPaths } from './RuntimeTurnSettledSpoolPaths'; import type { RuntimeTurnSettledSpoolPaths } from './RuntimeTurnSettledSpoolPaths';
const HOOK_SCRIPT_CONTENT = `#!/bin/sh const HOOK_SCRIPT_CONTENT = `#!/bin/sh
set +e set +e
@ -58,6 +58,7 @@ export class ShellRuntimeTurnSettledHookScriptInstaller {
const scriptPath = this.paths.getHookScriptPath(); const scriptPath = this.paths.getHookScriptPath();
await fs.writeFile(scriptPath, HOOK_SCRIPT_CONTENT, 'utf8'); await fs.writeFile(scriptPath, HOOK_SCRIPT_CONTENT, 'utf8');
// eslint-disable-next-line sonarjs/file-permissions -- the hook script must be executable on POSIX hosts.
await fs.chmod(scriptPath, 0o755); await fs.chmod(scriptPath, 0o755);
return { return {

View file

@ -2,12 +2,13 @@ import { useEffect, useState } from 'react';
import { api } from '@renderer/api'; import { api } from '@renderer/api';
import type { MemberWorkSyncStatus } from '../../contracts';
import { import {
toMemberWorkSyncStatusViewModel,
type MemberWorkSyncStatusViewModel, type MemberWorkSyncStatusViewModel,
toMemberWorkSyncStatusViewModel,
} from '../adapters/memberWorkSyncStatusViewModel'; } from '../adapters/memberWorkSyncStatusViewModel';
import type { MemberWorkSyncStatus } from '../../contracts';
export interface UseMemberWorkSyncStatusOptions { export interface UseMemberWorkSyncStatusOptions {
teamName?: string | null; teamName?: string | null;
memberName?: string | null; memberName?: string | null;

View file

@ -1,18 +1,19 @@
import { Badge } from '@renderer/components/ui/badge'; import { Badge } from '@renderer/components/ui/badge';
import { cn } from '@renderer/lib/utils'; import { cn } from '@renderer/lib/utils';
import type React from 'react';
import type { MemberWorkSyncStatus } from '../../contracts';
import { import {
toMemberWorkSyncStatusViewModel,
type MemberWorkSyncStatusViewModel, type MemberWorkSyncStatusViewModel,
toMemberWorkSyncStatusViewModel,
} from '../adapters/memberWorkSyncStatusViewModel'; } from '../adapters/memberWorkSyncStatusViewModel';
interface MemberWorkSyncBadgeProps { import type { MemberWorkSyncStatus } from '../../contracts';
import type React from 'react';
type MemberWorkSyncBadgeProps = Readonly<{
status?: MemberWorkSyncStatus | null; status?: MemberWorkSyncStatus | null;
viewModel?: MemberWorkSyncStatusViewModel; viewModel?: MemberWorkSyncStatusViewModel;
className?: string; className?: string;
} }>;
const toneClassName: Record<MemberWorkSyncStatusViewModel['tone'], string> = { const toneClassName: Record<MemberWorkSyncStatusViewModel['tone'], string> = {
neutral: 'border-[var(--color-border)] text-[var(--color-text-muted)]', neutral: 'border-[var(--color-border)] text-[var(--color-text-muted)]',
@ -32,7 +33,11 @@ export function MemberWorkSyncBadge({
return ( return (
<Badge <Badge
variant="outline" variant="outline"
className={cn('cursor-default whitespace-nowrap font-medium', toneClassName[resolved.tone], className)} className={cn(
'cursor-default whitespace-nowrap font-medium',
toneClassName[resolved.tone],
className
)}
title={resolved.tooltip} title={resolved.tooltip}
> >
{resolved.label} {resolved.label}

View file

@ -1,12 +1,14 @@
import { MemberWorkSyncBadge } from './MemberWorkSyncBadge';
import type { MemberWorkSyncStatus } from '../../contracts';
import { toMemberWorkSyncStatusViewModel } from '../adapters/memberWorkSyncStatusViewModel'; import { toMemberWorkSyncStatusViewModel } from '../adapters/memberWorkSyncStatusViewModel';
import { MemberWorkSyncBadge } from './MemberWorkSyncBadge';
import type { MemberWorkSyncStatus } from '../../contracts';
import type React from 'react'; import type React from 'react';
interface MemberWorkSyncDetailsProps { type MemberWorkSyncDetailsProps = Readonly<{
status: MemberWorkSyncStatus | null; status: MemberWorkSyncStatus | null;
showDiagnostics?: boolean; showDiagnostics?: boolean;
} }>;
function shortFingerprint(fingerprint?: string): string { function shortFingerprint(fingerprint?: string): string {
if (!fingerprint) { if (!fingerprint) {

View file

@ -1,15 +1,16 @@
import type React from 'react'; import { useMemberWorkSyncStatus } from '../hooks/useMemberWorkSyncStatus';
import { MemberWorkSyncBadge } from './MemberWorkSyncBadge'; import { MemberWorkSyncBadge } from './MemberWorkSyncBadge';
import { MemberWorkSyncDetails } from './MemberWorkSyncDetails'; import { MemberWorkSyncDetails } from './MemberWorkSyncDetails';
import { useMemberWorkSyncStatus } from '../hooks/useMemberWorkSyncStatus';
interface MemberWorkSyncStatusPanelProps { import type React from 'react';
type MemberWorkSyncStatusPanelProps = Readonly<{
teamName: string; teamName: string;
memberName: string; memberName: string;
enabled?: boolean; enabled?: boolean;
showDiagnostics?: boolean; showDiagnostics?: boolean;
} }>;
export function MemberWorkSyncStatusPanel({ export function MemberWorkSyncStatusPanel({
teamName, teamName,

View file

@ -9,7 +9,6 @@ import {
type RecentProjectsFeatureFacade, type RecentProjectsFeatureFacade,
registerRecentProjectsHttp, registerRecentProjectsHttp,
} from '@features/recent-projects/main'; } from '@features/recent-projects/main';
import type { MemberWorkSyncFeatureFacade } from '@features/member-work-sync/main';
import { createLogger } from '@shared/utils/logger'; import { createLogger } from '@shared/utils/logger';
import { registerConfigRoutes } from './config'; import { registerConfigRoutes } from './config';
@ -36,6 +35,7 @@ import type {
import type { SshConnectionManager } from '../services/infrastructure/SshConnectionManager'; import type { SshConnectionManager } from '../services/infrastructure/SshConnectionManager';
import type { TeamDataService } from '../services/team/TeamDataService'; import type { TeamDataService } from '../services/team/TeamDataService';
import type { TeamProvisioningService } from '../services/team/TeamProvisioningService'; import type { TeamProvisioningService } from '../services/team/TeamProvisioningService';
import type { MemberWorkSyncFeatureFacade } from '@features/member-work-sync/main';
import type { FastifyInstance } from 'fastify'; import type { FastifyInstance } from 'fastify';
const logger = createLogger('HTTP:routes'); const logger = createLogger('HTTP:routes');

View file

@ -30,18 +30,18 @@ import {
type CodexModelCatalogFeatureFacade, type CodexModelCatalogFeatureFacade,
createCodexModelCatalogFeature, createCodexModelCatalogFeature,
} from '@features/codex-model-catalog/main'; } from '@features/codex-model-catalog/main';
import {
createRecentProjectsFeature,
type RecentProjectsFeatureFacade,
registerRecentProjectsIpc,
removeRecentProjectsIpc,
} from '@features/recent-projects/main';
import { import {
createMemberWorkSyncFeature, createMemberWorkSyncFeature,
type MemberWorkSyncFeatureFacade, type MemberWorkSyncFeatureFacade,
registerMemberWorkSyncIpc, registerMemberWorkSyncIpc,
removeMemberWorkSyncIpc, removeMemberWorkSyncIpc,
} from '@features/member-work-sync/main'; } from '@features/member-work-sync/main';
import {
createRecentProjectsFeature,
type RecentProjectsFeatureFacade,
registerRecentProjectsIpc,
removeRecentProjectsIpc,
} from '@features/recent-projects/main';
import { import {
createRuntimeProviderManagementFeature, createRuntimeProviderManagementFeature,
registerRuntimeProviderManagementIpc, registerRuntimeProviderManagementIpc,
@ -179,19 +179,19 @@ import {
SshConnectionManager, SshConnectionManager,
TaskBoundaryParser, TaskBoundaryParser,
TeamDataService, TeamDataService,
TeamKanbanManager,
TeamLogSourceTracker, TeamLogSourceTracker,
TeammateToolTracker, TeammateToolTracker,
TeamMemberLogsFinder, TeamMemberLogsFinder,
TeamMembersMetaStore,
TeamProvisioningService, TeamProvisioningService,
TeamRuntimeAdapterRegistry, TeamRuntimeAdapterRegistry,
TeamKanbanManager, TeamTaskReader,
TeamMembersMetaStore,
TeamTaskStallJournal, TeamTaskStallJournal,
TeamTaskStallMonitor, TeamTaskStallMonitor,
TeamTaskStallNotifier, TeamTaskStallNotifier,
TeamTaskStallPolicy, TeamTaskStallPolicy,
TeamTaskStallSnapshotSource, TeamTaskStallSnapshotSource,
TeamTaskReader,
UpdaterService, UpdaterService,
} from './services'; } from './services';

View file

@ -1152,7 +1152,7 @@ export class TaskChangeLedgerReader {
string, string,
{ event: LedgerEvent; index: number; rank: number } { event: LedgerEvent; index: number; rank: number }
>(); >();
const passthrough: Array<{ event: LedgerEvent; index: number }> = []; const passthrough: { event: LedgerEvent; index: number }[] = [];
events.forEach((event, index) => { events.forEach((event, index) => {
const sourceImportKey = this.sourceImportKeyForEvent(event); const sourceImportKey = this.sourceImportKeyForEvent(event);

View file

@ -2,7 +2,6 @@ import {
resolveAnthropicFastMode, resolveAnthropicFastMode,
resolveAnthropicRuntimeSelection, resolveAnthropicRuntimeSelection,
} from '@features/anthropic-runtime-profile/main'; } from '@features/anthropic-runtime-profile/main';
import type { RuntimeTurnSettledProvider } from '@features/member-work-sync/core/domain';
import { import {
buildCodexFastModeArgs, buildCodexFastModeArgs,
resolveCodexFastMode, resolveCodexFastMode,
@ -239,6 +238,7 @@ import type {
TeamRuntimePrepareResult, TeamRuntimePrepareResult,
TeamRuntimeStopInput, TeamRuntimeStopInput,
} from './runtime'; } from './runtime';
import type { RuntimeTurnSettledProvider } from '@features/member-work-sync/main';
/** /**
* Kill a team CLI process using SIGKILL (uncatchable). * Kill a team CLI process using SIGKILL (uncatchable).

View file

@ -1,12 +1,12 @@
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import { MemberWorkSyncStatusPanel } from '@features/member-work-sync/renderer';
import { Button } from '@renderer/components/ui/button'; import { Button } from '@renderer/components/ui/button';
import { Dialog, DialogContent, DialogFooter, DialogHeader } from '@renderer/components/ui/dialog'; import { Dialog, DialogContent, DialogFooter, DialogHeader } from '@renderer/components/ui/dialog';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@renderer/components/ui/tabs'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@renderer/components/ui/tabs';
import { useMemberStats } from '@renderer/hooks/useMemberStats'; import { useMemberStats } from '@renderer/hooks/useMemberStats';
import { useStore } from '@renderer/store'; import { useStore } from '@renderer/store';
import { selectMemberMessagesForTeamMember } from '@renderer/store/slices/teamSlice'; import { selectMemberMessagesForTeamMember } from '@renderer/store/slices/teamSlice';
import { MemberWorkSyncStatusPanel } from '@features/member-work-sync/renderer';
import { import {
buildMemberLaunchDiagnosticsPayload, buildMemberLaunchDiagnosticsPayload,
getMemberLaunchDiagnosticsErrorMessage, getMemberLaunchDiagnosticsErrorMessage,

View file

@ -95,8 +95,6 @@ import type { TerminalAPI } from './terminal';
import type { TmuxAPI } from './tmux'; import type { TmuxAPI } from './tmux';
import type { WaterfallData } from './visualization'; import type { WaterfallData } from './visualization';
import type { CodexAccountElectronApi } from '@features/codex-account/contracts'; import type { CodexAccountElectronApi } from '@features/codex-account/contracts';
import type { RecentProjectsElectronApi } from '@features/recent-projects/contracts';
import type { RuntimeProviderManagementApi } from '@features/runtime-provider-management/contracts';
import type { import type {
MemberWorkSyncMetricsRequest, MemberWorkSyncMetricsRequest,
MemberWorkSyncReportRequest, MemberWorkSyncReportRequest,
@ -105,6 +103,8 @@ import type {
MemberWorkSyncStatusRequest, MemberWorkSyncStatusRequest,
MemberWorkSyncTeamMetrics, MemberWorkSyncTeamMetrics,
} from '@features/member-work-sync/contracts'; } from '@features/member-work-sync/contracts';
import type { RecentProjectsElectronApi } from '@features/recent-projects/contracts';
import type { RuntimeProviderManagementApi } from '@features/runtime-provider-management/contracts';
import type { import type {
ConversationGroup, ConversationGroup,
FileChangeEvent, FileChangeEvent,

View file

@ -79,8 +79,10 @@ describe('runtime turn-settled hook settings', () => {
expect(fs.existsSync(paths.getProcessedDir())).toBe(true); expect(fs.existsSync(paths.getProcessedDir())).toBe(true);
expect(fs.existsSync(paths.getInvalidDir())).toBe(true); expect(fs.existsSync(paths.getInvalidDir())).toBe(true);
const stat = fs.statSync(result.scriptPath); if (process.platform !== 'win32') {
expect(stat.mode & 0o111).toBeGreaterThan(0); const stat = fs.statSync(result.scriptPath);
expect(stat.mode & 0o111).toBeGreaterThan(0);
}
expect(fs.readFileSync(result.scriptPath, 'utf8')).toContain('dd bs="$max_bytes"'); expect(fs.readFileSync(result.scriptPath, 'utf8')).toContain('dd bs="$max_bytes"');
}); });
}); });