diff --git a/nerve.yaml b/nerve.yaml index 308fbde..f7e1416 100644 --- a/nerve.yaml +++ b/nerve.yaml @@ -26,6 +26,9 @@ workflows: pr-summarizer: concurrency: 1 overflow: drop + hello-world: + concurrency: 1 + overflow: drop reflexes: - kind: sense diff --git a/workflows/hello-world/index.ts b/workflows/hello-world/index.ts new file mode 100644 index 0000000..1247b62 --- /dev/null +++ b/workflows/hello-world/index.ts @@ -0,0 +1,86 @@ +import type { + ModeratorContext, + RoleResult, + StartStep, + WorkflowDefinition, + WorkflowMessage, +} from "@uncaged/nerve-core"; +import { END } from "@uncaged/nerve-core"; + +type WorkflowMeta = { + greeter: { + name: string; + error: string | null; + }; +}; + +const DEFAULT_NAME = "friend"; + +function resolveNameFromContent(content: string): { name: string; error: string | null } { + const trimmed = content.trim(); + if (trimmed === "") { + return { name: DEFAULT_NAME, error: "empty_input" }; + } + + let jsonParsed: unknown; + let parseOk: boolean; + try { + jsonParsed = JSON.parse(trimmed); + parseOk = true; + } catch { + parseOk = false; + } + + if (parseOk) { + if (jsonParsed !== null && typeof jsonParsed === "object" && !Array.isArray(jsonParsed)) { + const nameField = (jsonParsed as Record).name; + if (typeof nameField === "string") { + const n = nameField.trim(); + if (n !== "") { + return { name: n, error: null }; + } + return { name: DEFAULT_NAME, error: "name_empty" }; + } + return { name: DEFAULT_NAME, error: "missing_name" }; + } + return { name: DEFAULT_NAME, error: "invalid_json_shape" }; + } + + return { name: trimmed, error: null }; +} + +async function greeter( + start: StartStep, + _messages: WorkflowMessage[], +): Promise> { + try { + const { name, error } = resolveNameFromContent(start.content); + return { + content: `Hello, ${name}!`, + meta: { name, error }, + }; + } catch (unhandled) { + const msg = unhandled instanceof Error ? unhandled.message : String(unhandled); + return { + content: `Hello, ${DEFAULT_NAME}!`, + meta: { name: DEFAULT_NAME, error: `internal_error: ${msg}` }, + }; + } +} + +const workflow: WorkflowDefinition = { + name: "hello-world", + roles: { greeter }, + moderator(context: ModeratorContext) { + if (context.steps.length === 0) { + return "greeter"; + } + const last = context.steps[context.steps.length - 1]; + if (last.role === "greeter") { + return END; + } + return END; + }, +}; + +export default workflow; diff --git a/workflows/hello-world/package.json b/workflows/hello-world/package.json new file mode 100644 index 0000000..f229c46 --- /dev/null +++ b/workflows/hello-world/package.json @@ -0,0 +1,21 @@ +{ + "name": "hello-world-workflow", + "version": "0.0.1", + "private": true, + "type": "module", + "dependencies": { + "@uncaged/nerve-core": "latest", + "@uncaged/nerve-workflow-utils": "latest" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "typescript": "^5.7.0" + }, + "pnpm": { + "overrides": { + "@uncaged/nerve-daemon": "link:../../../repos/nerve/packages/daemon", + "@uncaged/nerve-core": "link:../../../repos/nerve/packages/core", + "@uncaged/nerve-workflow-utils": "link:../../../repos/nerve/packages/workflow-utils" + } + } +} diff --git a/workflows/hello-world/pnpm-lock.yaml b/workflows/hello-world/pnpm-lock.yaml new file mode 100644 index 0000000..eb8e81f --- /dev/null +++ b/workflows/hello-world/pnpm-lock.yaml @@ -0,0 +1,51 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +overrides: + '@uncaged/nerve-daemon': link:../../../repos/nerve/packages/daemon + '@uncaged/nerve-core': link:../../../repos/nerve/packages/core + '@uncaged/nerve-workflow-utils': link:../../../repos/nerve/packages/workflow-utils + +importers: + + .: + dependencies: + '@uncaged/nerve-core': + specifier: link:../../../repos/nerve/packages/core + version: link:../../../repos/nerve/packages/core + '@uncaged/nerve-workflow-utils': + specifier: link:../../../repos/nerve/packages/workflow-utils + version: link:../../../repos/nerve/packages/workflow-utils + devDependencies: + '@types/node': + specifier: ^22.0.0 + version: 22.19.17 + typescript: + specifier: ^5.7.0 + version: 5.9.3 + +packages: + + '@types/node@22.19.17': + resolution: {integrity: sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + +snapshots: + + '@types/node@22.19.17': + dependencies: + undici-types: 6.21.0 + + typescript@5.9.3: {} + + undici-types@6.21.0: {} diff --git a/workflows/hello-world/tsconfig.json b/workflows/hello-world/tsconfig.json new file mode 100644 index 0000000..fc00159 --- /dev/null +++ b/workflows/hello-world/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022"], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "skipLibCheck": true, + "noEmit": true, + "types": ["node"] + }, + "include": ["./**/*.ts"] +} diff --git a/workflows/sense-generator/package.json b/workflows/sense-generator/package.json new file mode 100644 index 0000000..9e4cb60 --- /dev/null +++ b/workflows/sense-generator/package.json @@ -0,0 +1,22 @@ +{ + "name": "sense-generator-workflow", + "version": "0.0.1", + "private": true, + "type": "module", + "dependencies": { + "@uncaged/nerve-core": "latest", + "@uncaged/nerve-workflow-utils": "latest", + "zod": "^4.3.6" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "typescript": "^5.7.0" + }, + "pnpm": { + "overrides": { + "@uncaged/nerve-daemon": "link:../../../repos/nerve/packages/daemon", + "@uncaged/nerve-core": "link:../../../repos/nerve/packages/core", + "@uncaged/nerve-workflow-utils": "link:../../../repos/nerve/packages/workflow-utils" + } + } +} diff --git a/workflows/sense-generator/pnpm-lock.yaml b/workflows/sense-generator/pnpm-lock.yaml new file mode 100644 index 0000000..237146e --- /dev/null +++ b/workflows/sense-generator/pnpm-lock.yaml @@ -0,0 +1,59 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +overrides: + '@uncaged/nerve-daemon': link:../../../repos/nerve/packages/daemon + '@uncaged/nerve-core': link:../../../repos/nerve/packages/core + '@uncaged/nerve-workflow-utils': link:../../../repos/nerve/packages/workflow-utils + +importers: + + .: + dependencies: + '@uncaged/nerve-core': + specifier: link:../../../repos/nerve/packages/core + version: link:../../../repos/nerve/packages/core + '@uncaged/nerve-workflow-utils': + specifier: link:../../../repos/nerve/packages/workflow-utils + version: link:../../../repos/nerve/packages/workflow-utils + zod: + specifier: ^4.3.6 + version: 4.3.6 + devDependencies: + '@types/node': + specifier: ^22.0.0 + version: 22.19.17 + typescript: + specifier: ^5.7.0 + version: 5.9.3 + +packages: + + '@types/node@22.19.17': + resolution: {integrity: sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + +snapshots: + + '@types/node@22.19.17': + dependencies: + undici-types: 6.21.0 + + typescript@5.9.3: {} + + undici-types@6.21.0: {} + + zod@4.3.6: {} diff --git a/workflows/sense-generator/tsconfig.json b/workflows/sense-generator/tsconfig.json new file mode 100644 index 0000000..fc00159 --- /dev/null +++ b/workflows/sense-generator/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022"], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "skipLibCheck": true, + "noEmit": true, + "types": ["node"] + }, + "include": ["./**/*.ts"] +}