Files
SeedPass/torch/src/cmd-complete.mjs
T
2026-02-25 13:46:50 -05:00

125 lines
4.1 KiB
JavaScript

import {
getRelays as _getRelays,
getNamespace as _getNamespace,
getHashtag as _getHashtag,
} from './torch-config.mjs';
import {
KIND_APP_DATA,
MS_PER_SECOND,
} from './constants.mjs';
import { todayDateStr, nowUnix, detectPlatform } from './utils.mjs';
import { queryLocks as _queryLocks, publishLock as _publishLock } from './lock-ops.mjs';
import { ExitError } from './errors.mjs';
import { generateSecretKey as _generateSecretKey, getPublicKey as _getPublicKey, finalizeEvent as _finalizeEvent } from 'nostr-tools/pure';
/**
* Marks a task as permanently completed by publishing a new lock event with
* `status: 'completed'` and no expiration.
*
* This function:
* 1. Verifies that the agent currently holds a valid lock.
* 2. Publishes a replacement event that preserves the original `startedAt` time.
*
* @param {string} agent - Agent name
* @param {string} cadence - 'daily' or 'weekly'
* @param {boolean} [dryRun=false] - If true, skips publishing
* @param {Object} [deps] - Dependency injection
* @returns {Promise<{status: string, eventId: string}>}
* @throws {ExitError} If no active lock exists for the agent
*/
export async function cmdComplete(agent, cadence, optionsOrDryRun = false, deps = {}) {
const options = typeof optionsOrDryRun === 'object' ? optionsOrDryRun : { dryRun: !!optionsOrDryRun };
const { dryRun = false, platform = null, model = null } = options;
const {
getRelays = _getRelays,
getNamespace = _getNamespace,
getHashtag = _getHashtag,
queryLocks = _queryLocks,
publishLock = _publishLock,
generateSecretKey = _generateSecretKey,
getPublicKey = _getPublicKey,
finalizeEvent = _finalizeEvent,
getDateStr = todayDateStr,
log = console.log,
error = console.error
} = deps;
const relays = await getRelays();
const namespace = await getNamespace();
const hashtag = await getHashtag();
const dateStr = getDateStr();
const now = nowUnix();
error(`Completing task: namespace=${namespace}, agent=${agent}, cadence=${cadence}, date=${dateStr}`);
error(`Relays: ${relays.join(', ')}`);
// 1. Find existing lock
const locks = await queryLocks(relays, cadence, dateStr, namespace);
const myLock = locks.find((l) => l.agent === agent);
if (!myLock) {
error(`ERROR: No active lock found for agent "${agent}" on ${dateStr}.`);
error(`Cannot complete a task that is not locked or has already expired.`);
throw new ExitError(1, 'No active lock found');
}
if (myLock.status === 'completed') {
error(`Task is already marked as completed (event ${myLock.eventId}).`);
log('LOCK_STATUS=completed');
return { status: 'completed', eventId: myLock.eventId };
}
// 2. Build completion event
const startedAtIso = myLock.createdAtIso;
error('Step 1: Generating completion event...');
const sk = generateSecretKey();
const pk = getPublicKey(sk);
const event = finalizeEvent(
{
kind: KIND_APP_DATA,
created_at: now,
tags: [
['d', `${namespace}-lock/${cadence}/${agent}/${dateStr}`],
['t', hashtag],
['t', `${namespace}-lock-${cadence}`],
['t', `${namespace}-lock-${cadence}-${dateStr}`],
// No expiration tag -> permanent
],
content: JSON.stringify({
agent,
cadence,
status: 'completed',
namespace,
date: dateStr,
platform: platform || process.env.AGENT_PLATFORM || detectPlatform() || 'unknown',
model: model || process.env.AGENT_MODEL || 'unknown',
startedAt: startedAtIso,
completedAt: new Date(now * MS_PER_SECOND).toISOString(),
}),
},
sk,
);
error(` Event ID: ${event.id}`);
if (dryRun) {
error('Step 2: [DRY RUN] Skipping publish — event built but not sent');
} else {
error('Step 2: Publishing completion event...');
await publishLock(relays, event);
error(' Published successfully.');
}
log('LOCK_STATUS=completed');
log(`LOCK_EVENT_ID=${event.id}`);
log(`LOCK_PUBKEY=${pk}`);
log(`LOCK_AGENT=${agent}`);
log(`LOCK_CADENCE=${cadence}`);
log(`LOCK_DATE=${dateStr}`);
return { status: 'completed', eventId: event.id };
}