@atolis-hq/wake 0.2.1 → 0.2.2
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.
- package/README.md +2 -2
- package/dist/src/adapters/fs/state-store.js +1 -6
- package/dist/src/cli/scaffold-assets.js +5 -2
- package/dist/src/config/discover-config-files.js +11 -0
- package/dist/src/config/load-config.js +37 -10
- package/dist/src/config/split-config.js +14 -0
- package/dist/src/domain/schema.js +23 -4
- package/dist/src/lib/deep-merge.js +14 -0
- package/dist/src/lib/paths.js +2 -1
- package/dist/src/lib/yaml-file.js +20 -0
- package/dist/src/main.js +0 -6
- package/dist/src/version.js +1 -1
- package/docker/log-command.sh +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -97,7 +97,7 @@ For more detail, see [docs/vision.md](docs/vision.md) and
|
|
|
97
97
|
log; projections can be rebuilt, and the loop can crash and resume without
|
|
98
98
|
losing its place.
|
|
99
99
|
- **Local and inspectable.** Everything lives in a plain-file Wake home
|
|
100
|
-
directory: `config.
|
|
100
|
+
directory: `config.yaml`, `config.workflows.yaml`, `prompts/`, and `workspaces/` at the top level for
|
|
101
101
|
what you edit or browse day-to-day, with durable/internal state (events,
|
|
102
102
|
projections, runs, logs, sandbox auth) nested under a hidden `.wake/`.
|
|
103
103
|
- **Sandbox-oriented execution.** Wake can run from a persistent Docker sandbox
|
|
@@ -169,7 +169,7 @@ any time for the full command list, or see
|
|
|
169
169
|
- [docs/architecture.md](docs/architecture.md) — module boundaries and the event-sourced core.
|
|
170
170
|
- [docs/workflows.md](docs/workflows.md) — how stages, prompts, and runner routes are configured.
|
|
171
171
|
- [docs/prompts.md](docs/prompts.md) — how prompt templates map to workflow stages.
|
|
172
|
-
- [docs/configuration.md](docs/configuration.md) — `config.
|
|
172
|
+
- [docs/configuration.md](docs/configuration.md) — `config.yaml`/`config.workflows.yaml` options and the operator correlation escape hatch.
|
|
173
173
|
- [docs/development.md](docs/development.md) — source-checkout dev setup (`wake-dev`), npm scripts, formatting, self-update, GitHub polling.
|
|
174
174
|
- [docs/runner-comparison.md](docs/runner-comparison.md) — capability differences between supported runners.
|
|
175
175
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { access, appendFile, mkdir, readFile, readdir, rename } from 'node:fs/promises';
|
|
2
2
|
import { dirname, join } from 'node:path';
|
|
3
|
-
import { parseEventEnvelope, parseIssueStateRecord, parseLedger, parseRunRecord, parseSourceStateRecord,
|
|
3
|
+
import { parseEventEnvelope, parseIssueStateRecord, parseLedger, parseRunRecord, parseSourceStateRecord, } from '../../domain/schema.js';
|
|
4
4
|
import { isTerminalStage } from '../../domain/stages.js';
|
|
5
5
|
import { appendJsonLine, readJsonFile, writeJsonFile } from '../../lib/json-file.js';
|
|
6
6
|
import { createWakePaths } from '../../lib/paths.js';
|
|
@@ -144,11 +144,6 @@ export function createStateStore({ wakeRoot }) {
|
|
|
144
144
|
async ensureWakeRoot() {
|
|
145
145
|
await mkdir(wakeRoot, { recursive: true });
|
|
146
146
|
},
|
|
147
|
-
async writeConfig(record) {
|
|
148
|
-
const parsed = parseWakeConfig(record);
|
|
149
|
-
await writeJsonFile(paths.configFile, parsed);
|
|
150
|
-
return parsed;
|
|
151
|
-
},
|
|
152
147
|
async writeLedger(record) {
|
|
153
148
|
const parsed = parseLedger(record);
|
|
154
149
|
await writeJsonFile(paths.ledgerFile, parsed);
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { access, chmod, copyFile, mkdir, readFile, readdir, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { basename, dirname, join, resolve } from 'node:path';
|
|
3
3
|
import { createDefaultWakeConfig } from '../config/defaults.js';
|
|
4
|
-
import {
|
|
4
|
+
import { splitWakeConfig } from '../config/split-config.js';
|
|
5
|
+
import { writeYamlFile } from '../lib/yaml-file.js';
|
|
5
6
|
import { createWakePaths } from '../lib/paths.js';
|
|
6
7
|
const promptFileNames = ['refine.md', 'implement.md'];
|
|
7
8
|
function sanitizeContainerName(name) {
|
|
@@ -85,8 +86,10 @@ export async function scaffoldWakeHome(input) {
|
|
|
85
86
|
paths.workspaceRoot,
|
|
86
87
|
];
|
|
87
88
|
await Promise.all(runtimeDirectories.map((directoryPath) => mkdir(directoryPath, { recursive: true })));
|
|
89
|
+
const { infra, workflow } = splitWakeConfig(config);
|
|
88
90
|
await Promise.all([
|
|
89
91
|
copyAssets(repoRoot, 'prompts', join(wakeRoot, 'prompts'), promptFileNames),
|
|
90
|
-
|
|
92
|
+
writeYamlFile(join(wakeRoot, 'config.yaml'), infra),
|
|
93
|
+
writeYamlFile(join(wakeRoot, 'config.workflows.yaml'), workflow),
|
|
91
94
|
]);
|
|
92
95
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { readdir } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
const configFileNamePattern = /^config(\..+)?\.yaml$/;
|
|
4
|
+
export async function discoverConfigFiles(wakeRoot) {
|
|
5
|
+
const entries = await readdir(wakeRoot, { withFileTypes: true }).catch(() => []);
|
|
6
|
+
return entries
|
|
7
|
+
.filter((entry) => entry.isFile() && configFileNamePattern.test(entry.name))
|
|
8
|
+
.map((entry) => entry.name)
|
|
9
|
+
.sort()
|
|
10
|
+
.map((name) => join(wakeRoot, name));
|
|
11
|
+
}
|
|
@@ -1,20 +1,47 @@
|
|
|
1
|
-
import { resolve } from 'node:path';
|
|
1
|
+
import { resolve, join } from 'node:path';
|
|
2
2
|
import { access } from 'node:fs/promises';
|
|
3
3
|
import { readJsonFile } from '../lib/json-file.js';
|
|
4
|
+
import { readYamlFile } from '../lib/yaml-file.js';
|
|
5
|
+
import { deepMergeRaw } from '../lib/deep-merge.js';
|
|
4
6
|
import { parseWakeConfig } from '../domain/schema.js';
|
|
7
|
+
import { discoverConfigFiles } from './discover-config-files.js';
|
|
8
|
+
async function readLegacyConfigIfPresent(wakeRoot) {
|
|
9
|
+
const legacyConfigFile = join(wakeRoot, 'config.json');
|
|
10
|
+
try {
|
|
11
|
+
await access(legacyConfigFile);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
16
|
+
return readJsonFile(legacyConfigFile);
|
|
17
|
+
}
|
|
5
18
|
export async function loadWakeConfig(options) {
|
|
6
19
|
const wakeRoot = options?.wakeRoot ?? resolve(process.cwd(), '.wake');
|
|
7
|
-
const
|
|
8
|
-
let raw
|
|
9
|
-
if (
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
20
|
+
const configFiles = await discoverConfigFiles(wakeRoot);
|
|
21
|
+
let raw;
|
|
22
|
+
if (configFiles.length > 0) {
|
|
23
|
+
raw = {};
|
|
24
|
+
for (const configFile of configFiles) {
|
|
25
|
+
let parsed;
|
|
26
|
+
try {
|
|
27
|
+
// yaml.parse returns null for empty/comment-only files rather than
|
|
28
|
+
// {}, so coalesce before merging.
|
|
29
|
+
parsed = (await readYamlFile(configFile)) ?? {};
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
throw new Error(`Failed to parse ${configFile}: ${error.message}`, {
|
|
33
|
+
cause: error,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
raw = deepMergeRaw(raw, parsed);
|
|
16
37
|
}
|
|
17
38
|
}
|
|
39
|
+
else {
|
|
40
|
+
// Pre-split Wake homes only have a single config.json — Wake reads it
|
|
41
|
+
// directly rather than requiring a migration step. It stays untouched
|
|
42
|
+
// on disk; nothing here writes it back out (see docs/configuration.md).
|
|
43
|
+
raw = await readLegacyConfigIfPresent(wakeRoot);
|
|
44
|
+
}
|
|
18
45
|
// wakeRoot is always the live invocation's --wake-root/cwd, never a value
|
|
19
46
|
// to accept from a (possibly stale, possibly container-context) config
|
|
20
47
|
// file — spread rawPaths first so wakeRoot always wins. promptsRoot and
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { wakeInfraConfigSchema, wakeWorkflowConfigSchema } from '../domain/schema.js';
|
|
2
|
+
const infraKeys = Object.keys(wakeInfraConfigSchema.shape);
|
|
3
|
+
const workflowKeys = Object.keys(wakeWorkflowConfigSchema.shape);
|
|
4
|
+
export function splitWakeConfig(config) {
|
|
5
|
+
const infra = {};
|
|
6
|
+
for (const key of infraKeys) {
|
|
7
|
+
infra[key] = config[key];
|
|
8
|
+
}
|
|
9
|
+
const workflow = {};
|
|
10
|
+
for (const key of workflowKeys) {
|
|
11
|
+
workflow[key] = config[key];
|
|
12
|
+
}
|
|
13
|
+
return { infra, workflow };
|
|
14
|
+
}
|
|
@@ -391,8 +391,7 @@ export const ledgerSchema = z.object({
|
|
|
391
391
|
lastQuotaFailureAt: isoTimestampSchema.optional(),
|
|
392
392
|
runners: z.record(z.string(), runnerHealthEntrySchema).default({}),
|
|
393
393
|
});
|
|
394
|
-
|
|
395
|
-
.object({
|
|
394
|
+
const wakeConfigBaseSchema = z.object({
|
|
396
395
|
schemaVersion: z.literal(1).default(1),
|
|
397
396
|
paths: z.object({
|
|
398
397
|
wakeRoot: z.string(),
|
|
@@ -649,8 +648,28 @@ export const wakeConfigSchema = z
|
|
|
649
648
|
},
|
|
650
649
|
}),
|
|
651
650
|
sinks: z.record(z.string(), sinkEntrySchema).default({}),
|
|
652
|
-
})
|
|
653
|
-
|
|
651
|
+
});
|
|
652
|
+
export const wakeInfraConfigSchema = wakeConfigBaseSchema.pick({
|
|
653
|
+
schemaVersion: true,
|
|
654
|
+
paths: true,
|
|
655
|
+
sandbox: true,
|
|
656
|
+
dev: true,
|
|
657
|
+
scheduler: true,
|
|
658
|
+
transcripts: true,
|
|
659
|
+
ui: true,
|
|
660
|
+
sources: true,
|
|
661
|
+
sinks: true,
|
|
662
|
+
});
|
|
663
|
+
export const wakeWorkflowConfigSchema = wakeConfigBaseSchema.pick({
|
|
664
|
+
runners: true,
|
|
665
|
+
tiers: true,
|
|
666
|
+
defaultTier: true,
|
|
667
|
+
workflows: true,
|
|
668
|
+
workflowSelectors: true,
|
|
669
|
+
commands: true,
|
|
670
|
+
stages: true,
|
|
671
|
+
});
|
|
672
|
+
export const wakeConfigSchema = wakeConfigBaseSchema.superRefine((config, ctx) => {
|
|
654
673
|
const promptsRoot = config.paths.promptsRoot ?? defaultPromptsRoot();
|
|
655
674
|
const workflowEntries = Object.entries(config.workflows);
|
|
656
675
|
const commandEntries = Object.entries(config.commands);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
function isPlainObject(value) {
|
|
2
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
3
|
+
}
|
|
4
|
+
export function deepMergeRaw(target, source) {
|
|
5
|
+
const result = { ...target };
|
|
6
|
+
for (const [key, sourceValue] of Object.entries(source)) {
|
|
7
|
+
const targetValue = result[key];
|
|
8
|
+
result[key] =
|
|
9
|
+
isPlainObject(sourceValue) && isPlainObject(targetValue)
|
|
10
|
+
? deepMergeRaw(targetValue, sourceValue)
|
|
11
|
+
: sourceValue;
|
|
12
|
+
}
|
|
13
|
+
return result;
|
|
14
|
+
}
|
package/dist/src/lib/paths.js
CHANGED
|
@@ -11,7 +11,8 @@ export function createWakePaths(wakeRoot) {
|
|
|
11
11
|
wakeRoot,
|
|
12
12
|
dataRoot,
|
|
13
13
|
containerHomeRoot: join(dataRoot, 'container-home'),
|
|
14
|
-
configFile: join(wakeRoot, 'config.
|
|
14
|
+
configFile: join(wakeRoot, 'config.yaml'),
|
|
15
|
+
workflowsConfigFile: join(wakeRoot, 'config.workflows.yaml'),
|
|
15
16
|
ledgerFile: join(dataRoot, 'ledger.json'),
|
|
16
17
|
pauseFile: join(dataRoot, 'PAUSE'),
|
|
17
18
|
tickRequestFile: join(dataRoot, 'control', 'tick-request.json'),
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { dirname } from 'node:path';
|
|
4
|
+
import { parse, stringify } from 'yaml';
|
|
5
|
+
export async function writeYamlFile(path, value) {
|
|
6
|
+
await mkdir(dirname(path), { recursive: true });
|
|
7
|
+
const tempPath = `${path}.${randomUUID()}.tmp`;
|
|
8
|
+
try {
|
|
9
|
+
await writeFile(tempPath, stringify(value), 'utf8');
|
|
10
|
+
await rename(tempPath, path);
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
await rm(tempPath, { force: true }).catch(() => { });
|
|
14
|
+
throw error;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export async function readYamlFile(path) {
|
|
18
|
+
const raw = await readFile(path, 'utf8');
|
|
19
|
+
return parse(raw);
|
|
20
|
+
}
|
package/dist/src/main.js
CHANGED
|
@@ -441,9 +441,7 @@ export async function buildRuntime(args) {
|
|
|
441
441
|
await stateStore.ensureWakeRoot();
|
|
442
442
|
const config = await loadWakeConfig({
|
|
443
443
|
wakeRoot,
|
|
444
|
-
configFile: stateStore.paths.configFile,
|
|
445
444
|
});
|
|
446
|
-
await stateStore.writeConfig(config);
|
|
447
445
|
const resourceIndex = createResourceIndex({ paths: stateStore.paths });
|
|
448
446
|
const prTrackingEnabled = config.sources.github.enabled && config.sources.github.pullRequests.enabled;
|
|
449
447
|
// Resolved once and shared: resolveGitHubToken shells out to `gh auth
|
|
@@ -610,7 +608,6 @@ async function runUi(args) {
|
|
|
610
608
|
await stateStore.ensureWakeRoot();
|
|
611
609
|
const config = await loadWakeConfig({
|
|
612
610
|
wakeRoot,
|
|
613
|
-
configFile: stateStore.paths.configFile,
|
|
614
611
|
});
|
|
615
612
|
const server = await runUiCommand({
|
|
616
613
|
args,
|
|
@@ -659,7 +656,6 @@ async function runDoctor(args) {
|
|
|
659
656
|
await stateStore.ensureWakeRoot();
|
|
660
657
|
const config = await loadWakeConfig({
|
|
661
658
|
wakeRoot,
|
|
662
|
-
configFile: stateStore.paths.configFile,
|
|
663
659
|
});
|
|
664
660
|
const docker = createHostDockerCli();
|
|
665
661
|
const packageRoot = resolvePackageRoot();
|
|
@@ -811,7 +807,6 @@ async function main() {
|
|
|
811
807
|
await stateStore.ensureWakeRoot();
|
|
812
808
|
const config = await loadWakeConfig({
|
|
813
809
|
wakeRoot,
|
|
814
|
-
configFile: stateStore.paths.configFile,
|
|
815
810
|
});
|
|
816
811
|
const docker = createHostDockerCli();
|
|
817
812
|
const repoRoot = config.dev?.repoRoot;
|
|
@@ -928,7 +923,6 @@ async function main() {
|
|
|
928
923
|
await stateStore.ensureWakeRoot();
|
|
929
924
|
const config = await loadWakeConfig({
|
|
930
925
|
wakeRoot,
|
|
931
|
-
configFile: stateStore.paths.configFile,
|
|
932
926
|
});
|
|
933
927
|
const wakeInvocation = config.dev?.mode === 'source' ? ['node', '/app/dist/src/main.js'] : ['wake'];
|
|
934
928
|
await runSandbox(['exec', '--', ...wakeInvocation, ...rewritten]);
|
package/dist/src/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const wakeVersion = "0.2.
|
|
1
|
+
export const wakeVersion = "0.2.2+g83e9f9a";
|
package/docker/log-command.sh
CHANGED
|
@@ -87,7 +87,7 @@ prepare_codex_home
|
|
|
87
87
|
mirror_stdout "[${label}] begin ts=$(date -u +%FT%TZ) cwd=$(pwd) command=$*"
|
|
88
88
|
mirror_stdout "[${label}] paths hostWakeRoot=${host_wake_root} containerWakeRoot=${container_wake_root} promptsRoot=${prompts_root} containerHome=${container_home} hostContainerHome=${host_container_home} containerMount=${container_mount} containerName=${container_name}"
|
|
89
89
|
|
|
90
|
-
emit_check "wake-config" test -f "${container_mount}/config.json"
|
|
90
|
+
emit_check "wake-config" test -f "${container_mount}/config.yaml" -o -f "${container_mount}/config.json"
|
|
91
91
|
emit_check "prompts-root" test -d "${prompts_root}"
|
|
92
92
|
emit_check "workspaces-root" test -d "${container_mount}/workspaces"
|
|
93
93
|
emit_check "repos-root" test -d "${container_mount}/repos"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atolis-hq/wake",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Local autonomous agent control plane for software development",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
"@octokit/rest": "^22.0.0",
|
|
49
49
|
"handlebars": "^4.7.9",
|
|
50
50
|
"ulid": "^3.0.2",
|
|
51
|
+
"yaml": "^2.9.0",
|
|
51
52
|
"zod": "^4.1.5"
|
|
52
53
|
},
|
|
53
54
|
"devDependencies": {
|