fix(changes): key opencode backfill by context hash
This commit is contained in:
parent
53012ed623
commit
33a8a5fabc
1 changed files with 35 additions and 55 deletions
|
|
@ -81,6 +81,11 @@ interface OpenCodeDeliveryContextTempFile {
|
||||||
cleanup: () => Promise<void>;
|
cleanup: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface OpenCodeDeliveryContextPayload {
|
||||||
|
rawContext: string;
|
||||||
|
hash: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class ChangeExtractorService {
|
export class ChangeExtractorService {
|
||||||
private cache = new Map<string, CacheEntry>();
|
private cache = new Map<string, CacheEntry>();
|
||||||
private taskChangeSummaryCache = new Map<string, TaskChangeSummaryCacheEntry>();
|
private taskChangeSummaryCache = new Map<string, TaskChangeSummaryCacheEntry>();
|
||||||
|
|
@ -422,12 +427,16 @@ export class ChangeExtractorService {
|
||||||
input.teamName,
|
input.teamName,
|
||||||
input.taskId
|
input.taskId
|
||||||
);
|
);
|
||||||
|
const deliveryContextPayload = this.buildOpenCodeDeliveryContextPayload(
|
||||||
|
input.teamName,
|
||||||
|
input.taskId,
|
||||||
|
deliveryContextRecords
|
||||||
|
);
|
||||||
const backfillMemberName = this.resolveOpenCodeBackfillMemberName(
|
const backfillMemberName = this.resolveOpenCodeBackfillMemberName(
|
||||||
input.effectiveOptions.owner,
|
input.effectiveOptions.owner,
|
||||||
deliveryContextRecords
|
deliveryContextRecords
|
||||||
);
|
);
|
||||||
const deliveryContextFingerprint =
|
const deliveryContextFingerprint = deliveryContextPayload.hash;
|
||||||
this.hashOpenCodeDeliveryContextRecords(deliveryContextRecords);
|
|
||||||
|
|
||||||
const cacheKey = this.buildOpenCodeBackfillCacheKey({
|
const cacheKey = this.buildOpenCodeBackfillCacheKey({
|
||||||
teamName: input.teamName,
|
teamName: input.teamName,
|
||||||
|
|
@ -479,6 +488,7 @@ export class ChangeExtractorService {
|
||||||
workspaceRoot,
|
workspaceRoot,
|
||||||
cacheKey,
|
cacheKey,
|
||||||
deliveryContextRecords,
|
deliveryContextRecords,
|
||||||
|
deliveryContextPayload,
|
||||||
sourceGeneration,
|
sourceGeneration,
|
||||||
backfillMemberName
|
backfillMemberName
|
||||||
).finally(() => {
|
).finally(() => {
|
||||||
|
|
@ -496,13 +506,15 @@ export class ChangeExtractorService {
|
||||||
deliveryContextRecords: Awaited<
|
deliveryContextRecords: Awaited<
|
||||||
ReturnType<ChangeExtractorService['readOpenCodeDeliveryContextRecords']>
|
ReturnType<ChangeExtractorService['readOpenCodeDeliveryContextRecords']>
|
||||||
>,
|
>,
|
||||||
|
deliveryContextPayload: OpenCodeDeliveryContextPayload,
|
||||||
sourceGeneration: string | null,
|
sourceGeneration: string | null,
|
||||||
backfillMemberName?: string
|
backfillMemberName?: string
|
||||||
): Promise<OpenCodeBackfillAttempt> {
|
): Promise<OpenCodeBackfillAttempt> {
|
||||||
const deliveryContext = await this.createOpenCodeDeliveryContextTempFile(
|
const deliveryContext = await this.createOpenCodeDeliveryContextTempFile(
|
||||||
input.teamName,
|
input.teamName,
|
||||||
input.taskId,
|
input.taskId,
|
||||||
deliveryContextRecords
|
deliveryContextRecords,
|
||||||
|
deliveryContextPayload
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
const result = await this.openCodeLedgerBackfillPort!.backfillOpenCodeTaskLedger({
|
const result = await this.openCodeLedgerBackfillPort!.backfillOpenCodeTaskLedger({
|
||||||
|
|
@ -532,7 +544,7 @@ export class ChangeExtractorService {
|
||||||
workspaceRoot,
|
workspaceRoot,
|
||||||
sourceGeneration,
|
sourceGeneration,
|
||||||
deliveryRecordCount: deliveryContextRecords.length,
|
deliveryRecordCount: deliveryContextRecords.length,
|
||||||
deliveryContextFingerprint: this.hashOpenCodeDeliveryContextRecords(deliveryContextRecords),
|
deliveryContextFingerprint: deliveryContextPayload.hash,
|
||||||
result: {
|
result: {
|
||||||
attributionMode: result.attributionMode ?? OPEN_CODE_AUTO_BACKFILL_ATTRIBUTION_MODE,
|
attributionMode: result.attributionMode ?? OPEN_CODE_AUTO_BACKFILL_ATTRIBUTION_MODE,
|
||||||
outcome: result.outcome,
|
outcome: result.outcome,
|
||||||
|
|
@ -586,7 +598,7 @@ export class ChangeExtractorService {
|
||||||
projectDir,
|
projectDir,
|
||||||
workspaceRoot,
|
workspaceRoot,
|
||||||
deliveryRecordCount: deliveryContextRecords.length,
|
deliveryRecordCount: deliveryContextRecords.length,
|
||||||
deliveryContextFingerprint: this.hashOpenCodeDeliveryContextRecords(deliveryContextRecords),
|
deliveryContextFingerprint: deliveryContextPayload.hash,
|
||||||
error: error instanceof Error ? error.message : String(error),
|
error: error instanceof Error ? error.message : String(error),
|
||||||
}).catch(() => undefined);
|
}).catch(() => undefined);
|
||||||
if (deliveryContextRecords.length === 0) {
|
if (deliveryContextRecords.length === 0) {
|
||||||
|
|
@ -664,7 +676,8 @@ export class ChangeExtractorService {
|
||||||
private async createOpenCodeDeliveryContextTempFile(
|
private async createOpenCodeDeliveryContextTempFile(
|
||||||
teamName: string,
|
teamName: string,
|
||||||
taskId: string,
|
taskId: string,
|
||||||
records: Awaited<ReturnType<ChangeExtractorService['readOpenCodeDeliveryContextRecords']>>
|
records: Awaited<ReturnType<ChangeExtractorService['readOpenCodeDeliveryContextRecords']>>,
|
||||||
|
payload = this.buildOpenCodeDeliveryContextPayload(teamName, taskId, records)
|
||||||
): Promise<OpenCodeDeliveryContextTempFile> {
|
): Promise<OpenCodeDeliveryContextTempFile> {
|
||||||
if (records.length === 0) {
|
if (records.length === 0) {
|
||||||
return { filePath: null, hash: null, cleanup: async () => undefined };
|
return { filePath: null, hash: null, cleanup: async () => undefined };
|
||||||
|
|
@ -673,6 +686,21 @@ export class ChangeExtractorService {
|
||||||
const dir = await mkdtemp(path.join(os.tmpdir(), 'claude-team-opencode-ledger-context-'));
|
const dir = await mkdtemp(path.join(os.tmpdir(), 'claude-team-opencode-ledger-context-'));
|
||||||
await chmod(dir, 0o700).catch(() => undefined);
|
await chmod(dir, 0o700).catch(() => undefined);
|
||||||
const filePath = path.join(dir, 'delivery-context.json');
|
const filePath = path.join(dir, 'delivery-context.json');
|
||||||
|
await writeFile(filePath, payload.rawContext, { encoding: 'utf8', mode: 0o600 });
|
||||||
|
return {
|
||||||
|
filePath,
|
||||||
|
hash: payload.hash,
|
||||||
|
cleanup: async () => {
|
||||||
|
await rm(dir, { recursive: true, force: true }).catch(() => undefined);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildOpenCodeDeliveryContextPayload(
|
||||||
|
teamName: string,
|
||||||
|
taskId: string,
|
||||||
|
records: Awaited<ReturnType<ChangeExtractorService['readOpenCodeDeliveryContextRecords']>>
|
||||||
|
): OpenCodeDeliveryContextPayload {
|
||||||
const rawContext = `${JSON.stringify(
|
const rawContext = `${JSON.stringify(
|
||||||
{
|
{
|
||||||
schemaVersion: 1,
|
schemaVersion: 1,
|
||||||
|
|
@ -683,13 +711,9 @@ export class ChangeExtractorService {
|
||||||
null,
|
null,
|
||||||
2
|
2
|
||||||
)}\n`;
|
)}\n`;
|
||||||
await writeFile(filePath, rawContext, { encoding: 'utf8', mode: 0o600 });
|
|
||||||
return {
|
return {
|
||||||
filePath,
|
|
||||||
hash: createHash('sha256').update(rawContext).digest('hex'),
|
hash: createHash('sha256').update(rawContext).digest('hex'),
|
||||||
cleanup: async () => {
|
rawContext,
|
||||||
await rm(dir, { recursive: true, force: true }).catch(() => undefined);
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -798,50 +822,6 @@ export class ChangeExtractorService {
|
||||||
return laneIds.sort((left, right) => left.localeCompare(right));
|
return laneIds.sort((left, right) => left.localeCompare(right));
|
||||||
}
|
}
|
||||||
|
|
||||||
private hashOpenCodeDeliveryContextRecords(
|
|
||||||
records: Awaited<ReturnType<ChangeExtractorService['readOpenCodeDeliveryContextRecords']>>
|
|
||||||
): string {
|
|
||||||
const stableRecords = records
|
|
||||||
.map((record) => ({
|
|
||||||
memberName: record.memberName,
|
|
||||||
laneId: record.laneId ?? '',
|
|
||||||
runtimeSessionId: record.runtimeSessionId ?? '',
|
|
||||||
inboxMessageId: record.inboxMessageId ?? '',
|
|
||||||
deliveredUserMessageId: record.deliveredUserMessageId ?? '',
|
|
||||||
taskRefs: record.taskRefs
|
|
||||||
.map((taskRef) => ({
|
|
||||||
taskId: taskRef.taskId,
|
|
||||||
displayId: taskRef.displayId,
|
|
||||||
teamName: taskRef.teamName,
|
|
||||||
}))
|
|
||||||
.sort((left, right) =>
|
|
||||||
`${left.teamName}\0${left.taskId}\0${left.displayId}`.localeCompare(
|
|
||||||
`${right.teamName}\0${right.taskId}\0${right.displayId}`
|
|
||||||
)
|
|
||||||
),
|
|
||||||
}))
|
|
||||||
.sort((left, right) =>
|
|
||||||
[
|
|
||||||
left.laneId,
|
|
||||||
left.memberName,
|
|
||||||
left.runtimeSessionId,
|
|
||||||
left.inboxMessageId,
|
|
||||||
left.deliveredUserMessageId,
|
|
||||||
]
|
|
||||||
.join('\0')
|
|
||||||
.localeCompare(
|
|
||||||
[
|
|
||||||
right.laneId,
|
|
||||||
right.memberName,
|
|
||||||
right.runtimeSessionId,
|
|
||||||
right.inboxMessageId,
|
|
||||||
right.deliveredUserMessageId,
|
|
||||||
].join('\0')
|
|
||||||
)
|
|
||||||
);
|
|
||||||
return createHash('sha256').update(JSON.stringify(stableRecords)).digest('hex');
|
|
||||||
}
|
|
||||||
|
|
||||||
private async readOpenCodePromptDeliveryLedgerRecords(
|
private async readOpenCodePromptDeliveryLedgerRecords(
|
||||||
filePath: string
|
filePath: string
|
||||||
): Promise<OpenCodePromptDeliveryLedgerRecord[]> {
|
): Promise<OpenCodePromptDeliveryLedgerRecord[]> {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue