@atolis-hq/wake 0.1.12 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,77 @@
1
+ import { rm } from 'node:fs/promises';
2
+ import { isAbsolute, join, relative } from 'node:path';
3
+ import { createEventEnvelope } from '../lib/event-log.js';
4
+ // Cleans up per-issue workspaces (and, unless retained, transcripts) once the
5
+ // originating issue is closed. A cleanup failure is recorded as an event and
6
+ // skipped rather than aborting the sweep.
7
+ export function createWorkspaceCleanup(deps) {
8
+ function eventStampNow() {
9
+ return deps.clock.now().toISOString();
10
+ }
11
+ function isPerIssueWorkspacePath(workspacePath) {
12
+ const workspacesRoot = join(deps.config.paths.wakeRoot, 'workspaces');
13
+ const rel = relative(workspacesRoot, workspacePath);
14
+ return !rel.startsWith('..') && !isAbsolute(rel) && rel.length > 0;
15
+ }
16
+ async function cleanupClosedIssueWorkspaces(projections) {
17
+ for (const projection of projections) {
18
+ const { workspacePath } = projection.wake;
19
+ if (projection.issue.state === 'closed' &&
20
+ workspacePath !== undefined &&
21
+ isPerIssueWorkspacePath(workspacePath)) {
22
+ try {
23
+ await deps.workspaceManager.cleanupWorkspace({ workspacePath });
24
+ if (!deps.config.transcripts.retainAfterWorkspaceCleanup) {
25
+ await rm(deps.stateStore.paths.transcriptWorkDir(projection.workItemKey), {
26
+ recursive: true,
27
+ force: true,
28
+ });
29
+ }
30
+ }
31
+ catch (error) {
32
+ const failedAt = eventStampNow();
33
+ await deps.stateStore.appendEventEnvelope(createEventEnvelope({
34
+ eventId: `workspace-cleanup-failed-${projection.issue.repo.replace(/[^a-z0-9]+/gi, '-')}-${projection.issue.number}`,
35
+ workItemKey: projection.workItemKey,
36
+ streamScope: 'work-item',
37
+ direction: 'internal',
38
+ sourceSystem: 'wake',
39
+ sourceEventType: 'wake.workspace.cleanup-failed',
40
+ sourceRefs: {
41
+ repo: projection.issue.repo,
42
+ issueNumber: projection.issue.number,
43
+ },
44
+ occurredAt: failedAt,
45
+ ingestedAt: failedAt,
46
+ trigger: 'context-only',
47
+ payload: {
48
+ workspacePath,
49
+ error: error instanceof Error ? error.message : String(error),
50
+ },
51
+ }));
52
+ continue;
53
+ }
54
+ const cleanedAt = eventStampNow();
55
+ const cleanupEvent = createEventEnvelope({
56
+ eventId: `workspace-cleaned-${projection.issue.repo.replace(/[^a-z0-9]+/gi, '-')}-${projection.issue.number}-${deps.clock.now().getTime()}`,
57
+ workItemKey: projection.workItemKey,
58
+ streamScope: 'work-item',
59
+ direction: 'internal',
60
+ sourceSystem: 'wake',
61
+ sourceEventType: 'wake.workspace.cleaned',
62
+ sourceRefs: {
63
+ repo: projection.issue.repo,
64
+ issueNumber: projection.issue.number,
65
+ },
66
+ occurredAt: cleanedAt,
67
+ ingestedAt: cleanedAt,
68
+ trigger: 'immediate',
69
+ payload: { workspacePath },
70
+ });
71
+ await deps.stateStore.appendEventEnvelope(cleanupEvent);
72
+ await deps.projectionUpdater.rebuildFromEvents([cleanupEvent]);
73
+ }
74
+ }
75
+ }
76
+ return { cleanupClosedIssueWorkspaces };
77
+ }
@@ -0,0 +1,41 @@
1
+ // Presentation-only formatting for run summaries (cost, duration, tokens).
2
+ // Pure functions with no dependency on tick state — kept in lib/ so they are
3
+ // directly unit-testable rather than only reachable through a full run.
4
+ export function extractTokenCount(tokenUsage) {
5
+ if (tokenUsage === undefined) {
6
+ return undefined;
7
+ }
8
+ // Cache tokens dominate real usage and were previously dropped from this
9
+ // total entirely, understating the reported figure by roughly an order of
10
+ // magnitude (#135).
11
+ return (tokenUsage.inputTokens +
12
+ tokenUsage.outputTokens +
13
+ (tokenUsage.cacheCreationInputTokens ?? 0) +
14
+ (tokenUsage.cacheReadInputTokens ?? 0));
15
+ }
16
+ export function formatCostUsd(costUsd) {
17
+ return `$${costUsd.toFixed(costUsd < 1 ? 4 : 2)}`;
18
+ }
19
+ export function formatDuration(startedAtStr, finishedAtStr) {
20
+ const startedAt = new Date(startedAtStr);
21
+ const finishedAt = new Date(finishedAtStr);
22
+ const durationMs = finishedAt.getTime() - startedAt.getTime();
23
+ if (durationMs < 0 || !isFinite(durationMs))
24
+ return undefined;
25
+ const totalSeconds = Math.floor(durationMs / 1000);
26
+ const minutes = Math.floor(totalSeconds / 60);
27
+ const seconds = totalSeconds % 60;
28
+ if (minutes > 0) {
29
+ return `${minutes}m${seconds}s`;
30
+ }
31
+ return `${seconds}s`;
32
+ }
33
+ export function formatTokenCount(count) {
34
+ if (count >= 1000000) {
35
+ return `${(count / 1000000).toFixed(1)}M`;
36
+ }
37
+ if (count >= 1000) {
38
+ return `${(count / 1000).toFixed(0)}k`;
39
+ }
40
+ return String(count);
41
+ }
@@ -1 +1 @@
1
- export const wakeVersion = "0.1.12+g6a4753f";
1
+ export const wakeVersion = "0.1.13+g386bfbe";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atolis-hq/wake",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
4
4
  "description": "Local autonomous agent control plane for software development",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {