fix(ci): clean member work sync validation
This commit is contained in:
parent
e634895a08
commit
7d08205a23
40 changed files with 194 additions and 169 deletions
|
|
@ -1,2 +1,2 @@
|
||||||
export * from './ipc';
|
export * from './ipc';
|
||||||
export * from './types';
|
export type * from './types';
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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';
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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: {
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 () => {
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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).
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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"');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue