chore: kanban template

This commit is contained in:
e2e
2026-06-21 20:31:53 +02:00
commit a197174b13
209 changed files with 215578 additions and 0 deletions

161
src/scaffold/agent.gen.ts Normal file
View File

@@ -0,0 +1,161 @@
/**
* This file was generated by @pikku/cli@0.12.23
*/
import { MissingServiceError } from '@pikku/core/errors'
import { pikkuSessionlessFunc, defineHTTPRoutes, wireHTTPRoutes } from '@project/functions/.pikku/pikku-types.gen.js'
export const agentCaller = pikkuSessionlessFunc<
{ agentName: string; message: string; threadId: string; resourceId: string },
unknown
>({
tags: ['pikku'],
auth: false,
func: async (_services, data, { rpc }) => {
return await rpc.agent.run(data.agentName as any, {
message: data.message,
threadId: data.threadId,
resourceId: data.resourceId,
})
},
})
export const agentStreamCaller = pikkuSessionlessFunc<
{ agentName: string; message: string; threadId: string; resourceId: string },
void
>({
tags: ['pikku'],
auth: false,
func: async (_services, data, { rpc }) => {
await rpc.agent.stream(data.agentName as any, {
message: data.message,
threadId: data.threadId,
resourceId: data.resourceId,
})
},
})
export const agentApproveCaller = pikkuSessionlessFunc<
{ agentName: string; runId: string; approvals: { toolCallId: string; approved: boolean }[] },
unknown
>({
tags: ['pikku'],
auth: false,
func: async (_services, { runId, approvals, agentName }, { rpc }) => {
return await rpc.agent.approve(runId, approvals, agentName)
},
})
export const agentResumeCaller = pikkuSessionlessFunc<
{ agentName: string; runId: string; toolCallId: string; approved: boolean },
void
>({
tags: ['pikku'],
auth: false,
func: async (_services, data, { rpc }) => {
await rpc.agent.resume(data.runId, {
toolCallId: data.toolCallId,
approved: data.approved,
})
},
})
export const getAgentThreads = pikkuSessionlessFunc<
{ agentName?: string; resourceId?: string; limit?: number; offset?: number },
any[]
>({
tags: ['pikku', 'pikku:agent'],
title: 'Get Agent Threads',
description:
'Returns a list of AI agent threads from storage. Accepts optional filters: agentName, resourceId, limit, and offset for pagination.',
expose: true,
auth: false,
func: async ({ agentRunService }, input) => {
if (!agentRunService) throw new MissingServiceError('agentRunService is not available')
return await agentRunService.listThreads({
agentName: input?.agentName,
resourceId: input?.resourceId,
limit: input?.limit,
offset: input?.offset,
})
},
})
export const getAgentThreadMessages = pikkuSessionlessFunc<
{ threadId: string; resourceId?: string },
any[]
>({
tags: ['pikku', 'pikku:agent'],
title: 'Get Agent Thread Messages',
description:
'Returns all messages for a given AI agent thread, ordered by creation time.',
expose: true,
auth: false,
func: async ({ agentRunService }, input) => {
if (!agentRunService) throw new MissingServiceError('agentRunService is not available')
return await agentRunService.getThreadMessages(input.threadId)
},
})
export const getAgentThreadRuns = pikkuSessionlessFunc<
{ threadId: string; resourceId?: string },
any[]
>({
tags: ['pikku', 'pikku:agent'],
title: 'Get Agent Thread Runs',
description:
'Returns the run history for a given AI agent thread, ordered by creation time.',
expose: true,
auth: false,
func: async ({ agentRunService }, input) => {
if (!agentRunService) throw new MissingServiceError('agentRunService is not available')
return await agentRunService.getThreadRuns(input.threadId)
},
})
export const deleteAgentThread = pikkuSessionlessFunc<
{ threadId: string; resourceId?: string },
{ deleted: boolean }
>({
tags: ['pikku', 'pikku:agent'],
title: 'Delete Agent Thread',
description:
'Deletes an AI agent thread and all of its persisted state.',
expose: true,
auth: false,
func: async ({ agentRunService }, input) => {
if (!agentRunService) throw new MissingServiceError('agentRunService is not available')
const deleted = await agentRunService.deleteThread(input.threadId)
return { deleted }
},
})
export const agentRoutes = defineHTTPRoutes({
auth: false,
tags: ['pikku:public'],
routes: {
agentRun: {
route: '/rpc/agent/:agentName',
method: 'post',
func: agentCaller,
},
agentStream: {
route: '/rpc/agent/:agentName/stream',
method: 'post',
sse: true,
func: agentStreamCaller,
},
agentApprove: {
route: '/rpc/agent/:agentName/approve',
method: 'post',
func: agentApproveCaller,
},
agentResume: {
route: '/rpc/agent/:agentName/resume',
method: 'post',
sse: true,
func: agentResumeCaller,
},
},
})
wireHTTPRoutes({ routes: { agent: agentRoutes } })

108
src/scaffold/console.gen.ts Normal file
View File

@@ -0,0 +1,108 @@
/**
* This file was generated by @pikku/cli@0.12.23
*/
import { pikkuSessionlessFunc, defineHTTPRoutes, wireHTTPRoutes, ref, wireAddon } from '@project/functions/.pikku/pikku-types.gen.js'
export const pikkuConsoleSetSecret = pikkuSessionlessFunc<{
secretId: string
value: unknown
}, {
success: boolean
}>({
tags: ['pikku'],
description: 'Set the value of a secret',
expose: true,
auth: false,
func: async ({ secrets }, { secretId, value }) => {
await secrets.setSecretJSON(secretId, value)
return { success: true }
},
})
export const pikkuConsoleGetVariable = pikkuSessionlessFunc<
{ variableId: string },
{ exists: boolean; value: unknown | null }
>({
tags: ['pikku'],
description: 'Get the current value of a variable',
expose: true,
auth: false,
func: async ({ variables }, { variableId }) => {
const exists = await variables.has(variableId)
if (!exists) {
return { exists: false, value: null }
}
try {
const value = await variables.getJSON(variableId)
return { exists: true, value }
} catch {
const value = await variables.get(variableId)
return { exists: true, value }
}
},
})
export const pikkuConsoleSetVariable = pikkuSessionlessFunc<
{ variableId: string; value: unknown },
{ success: boolean }
>({
tags: ['pikku'],
description: 'Set the value of a variable',
expose: true,
auth: false,
func: async ({ variables }, { variableId, value }) => {
if (typeof value === 'string') {
await variables.set(variableId, value)
} else {
await variables.setJSON(variableId, value)
}
return { success: true }
},
})
export const pikkuConsoleHasSecret = pikkuSessionlessFunc<
{ secretId: string },
{ exists: boolean }
>({
tags: ['pikku'],
description: 'Check if a secret exists without reading its value',
expose: true,
auth: false,
func: async ({ secrets }, { secretId }) => {
const exists = await secrets.hasSecret(secretId)
return { exists }
},
})
export const pikkuConsoleGetSecret = pikkuSessionlessFunc<
{ secretId: string },
{ exists: boolean; value: unknown | null }
>({
tags: ['pikku'],
description: 'Get the current value of a secret',
expose: true,
auth: false,
func: async ({ secrets }, { secretId }) => {
const exists = await secrets.hasSecret(secretId)
if (!exists) {
return { exists: false, value: null }
}
const value = await secrets.getSecretJSON(secretId)
return { exists: true, value }
},
})
export const consoleRoutes = defineHTTPRoutes({
auth: false,
routes: {
workflowRunStream: {
route: '/workflow-run/:runId/stream',
method: 'get',
sse: true,
func: ref('console:streamWorkflowRun'),
},
},
})
wireAddon({ name: 'console', package: '@pikku/addon-console' })
wireHTTPRoutes({ basePath: '', routes: { console: consoleRoutes } })

View File

@@ -0,0 +1,26 @@
/**
* This file was generated by @pikku/cli@0.12.23
*/
/**
* Auto-generated public RPC HTTP endpoint
* Do not edit manually - regenerate with 'npx pikku'
*/
import { pikkuSessionlessFunc, wireHTTP } from '@project/functions/.pikku/pikku-types.gen.js'
export const rpcCaller = pikkuSessionlessFunc<
{ rpcName: string; data?: unknown },
unknown
>({
tags: ['pikku'],
auth: false,
func: async (_services, { rpcName, data }, { rpc }) => {
return await rpc.exposed(rpcName, data)
},
})
wireHTTP({
route: '/rpc/:rpcName',
method: 'post',
auth: false,
func: rpcCaller,
})

View File

@@ -0,0 +1,33 @@
/**
* This file was generated by @pikku/cli@0.12.23
*/
/**
* Auto-generated remote internal RPC queue worker and HTTP endpoint
* Do not edit manually - regenerate with 'npx pikku'
*/
import { pikkuSessionlessFunc, wireHTTP, wireQueueWorker } from '@project/functions/.pikku/pikku-types.gen.js'
import { pikkuRemoteAuthMiddleware } from '@pikku/core/middleware'
export const remoteRPCHandler = pikkuSessionlessFunc<
{ rpcName: string, data?: unknown },
unknown
>({
tags: ['pikku'],
func: async (_services, { rpcName, data }, { rpc }) => {
return await (rpc.invoke as any)(rpcName, data)
},
remote: true,
})
wireQueueWorker({
name: 'pikku-remote-internal-rpc',
func: remoteRPCHandler,
})
wireHTTP({
route: '/remote/rpc/:rpcName',
method: 'post',
auth: false,
middleware: [pikkuRemoteAuthMiddleware],
func: remoteRPCHandler,
})

View File

@@ -0,0 +1,288 @@
/**
* This file was generated by @pikku/cli@0.12.23
*/
/**
* Workflow HTTP catch-all routes
* Do not edit manually - regenerate with 'npx pikku'
*/
import { pikkuSessionlessFunc, wireHTTPRoutes } from '@project/functions/.pikku/pikku-types.gen.js'
import { MissingServiceError } from '@pikku/core/errors'
import type { WorkflowRunStatus } from '@pikku/core/workflow'
function assertWorkflowService(workflowService: unknown): asserts workflowService {
if (!workflowService) throw new MissingServiceError('workflowService is required')
}
function assertWorkflowRunService(workflowRunService: unknown): asserts workflowRunService {
if (!workflowRunService) throw new MissingServiceError('workflowRunService is required')
}
export const workflowStarter = pikkuSessionlessFunc<
{ workflowName: string; data?: unknown },
{ runId: string }
>({
tags: ['pikku'],
auth: false,
// workflowService is destructured (even though we delegate via rpc) so the
// analyzer assigns workflow-state capability to this unit — without it,
// rpc.startWorkflow() runs against a container missing workflowService.
func: async ({ workflowService }, { workflowName, data }, { rpc }) => {
assertWorkflowService(workflowService)
return await rpc.startWorkflow(workflowName as any, (data ?? {}) as any)
},
})
export const workflowRunner = pikkuSessionlessFunc<
{ workflowName: string; data?: unknown },
unknown
>({
tags: ['pikku'],
auth: false,
func: async ({ workflowService }, { workflowName, data }, { rpc }) => {
assertWorkflowService(workflowService)
return await workflowService.runToCompletion(workflowName, data ?? {}, rpc)
},
})
export const workflowStatusChecker = pikkuSessionlessFunc<
{ workflowName: string; runId: string },
WorkflowRunStatus
>({
tags: ['pikku'],
auth: false,
func: async ({ workflowService }, { runId }) => {
assertWorkflowService(workflowService)
const status = await workflowService.getRunStatus(runId)
if (!status) throw new Error(`Run not found: ${runId}`)
return status
},
})
/**
* Minimal workflow status stream — sends step names and statuses only.
* Use this for user-facing frontends where internal details should not be exposed.
*/
export const workflowStatusStream = pikkuSessionlessFunc<
{ workflowName: string; runId: string },
unknown
>({
tags: ['pikku'],
auth: false,
func: async ({ workflowRunService }, { runId }, { channel }) => {
assertWorkflowRunService(workflowRunService)
if (!channel) return
const terminalStatuses = new Set(['completed', 'failed', 'cancelled'])
let lastHash = ''
let initSent = false
const poll = async () => {
const run = await workflowRunService.getRun(runId)
if (!run) {
channel.close()
return false
}
const steps = await workflowRunService.getRunSteps(runId)
if (!initSent && run.deterministic) {
const statusByStep = new Map(
steps.map((s: { stepName: string; status: string }) => [
s.stepName,
s.status,
])
)
channel.send({
type: 'init',
deterministic: true,
steps: (run.plannedSteps ?? []).map(
(s: { stepName: string }) => ({
stepName: s.stepName,
status: statusByStep.get(s.stepName) ?? 'pending',
})
),
})
initSent = true
}
const hash = JSON.stringify({
s: run.status,
steps: steps.map((s: { stepName: string; status: string }) => [s.stepName, s.status]),
})
if (hash !== lastHash) {
lastHash = hash
channel.send({
type: 'update',
status: run.status,
steps: steps.map((s: { stepName: string; status: string }) => ({
stepName: s.stepName,
status: s.status,
})),
})
}
if (terminalStatuses.has(run.status)) {
channel.send({ type: 'done' })
channel.close()
return false
}
return true
}
const shouldContinue = await poll()
if (!shouldContinue) return
await new Promise<void>((resolve) => {
const interval = setInterval(async () => {
const cont = await poll()
if (!cont) {
clearInterval(interval)
resolve()
}
}, 500)
})
},
})
/**
* Full workflow status stream — includes output, error, and child run IDs.
* Use this for admin consoles and internal tooling.
*/
export const workflowStatusStreamFull = pikkuSessionlessFunc<
{ workflowName: string; runId: string },
unknown
>({
tags: ['pikku'],
auth: false,
func: async ({ workflowRunService }, { runId }, { channel }) => {
assertWorkflowRunService(workflowRunService)
if (!channel) return
const terminalStatuses = new Set(['completed', 'failed', 'cancelled'])
let lastHash = ''
let initSent = false
const poll = async () => {
const run = await workflowRunService.getRun(runId)
if (!run) {
channel.close()
return false
}
const steps = await workflowRunService.getRunSteps(runId)
if (!initSent && run.deterministic) {
const statusByStep = new Map(
steps.map((s: { stepName: string; status: string }) => [
s.stepName,
s.status,
])
)
channel.send({
type: 'init',
deterministic: true,
steps: (run.plannedSteps ?? []).map(
(s: { stepName: string }) => ({
stepName: s.stepName,
status: statusByStep.get(s.stepName) ?? 'pending',
})
),
})
initSent = true
}
const hash = JSON.stringify({
s: run.status,
o: run.output,
steps: steps.map((s: { stepName: string; status: string }) => [s.stepName, s.status]),
})
if (hash !== lastHash) {
lastHash = hash
channel.send({
type: 'update',
status: run.status,
output: run.output,
error: run.error,
steps: steps.map((s: { stepName: string; status: string; childRunId?: string }) => ({
stepName: s.stepName,
status: s.status,
...(s.childRunId ? { childRunId: s.childRunId } : {}),
})),
})
}
if (terminalStatuses.has(run.status)) {
channel.send({ type: 'done' })
channel.close()
return false
}
return true
}
const shouldContinue = await poll()
if (!shouldContinue) return
await new Promise<void>((resolve) => {
const interval = setInterval(async () => {
const cont = await poll()
if (!cont) {
clearInterval(interval)
resolve()
}
}, 500)
})
},
})
export const graphStarter = pikkuSessionlessFunc<
{ workflowName: string; nodeId: string; data?: unknown },
{ runId: string }
>({
auth: false,
// See workflowStarter — destructure workflowService so the analyzer
// assigns workflow-state capability to this unit.
func: async ({ workflowService }, { workflowName, nodeId, data }, { rpc }) => {
assertWorkflowService(workflowService)
return await rpc.startWorkflow(workflowName as any, (data ?? {}) as any, { startNode: nodeId })
},
})
wireHTTPRoutes({
auth: false,
routes: {
workflowStart: {
route: '/workflow/:workflowName/start',
method: 'post',
func: workflowStarter,
},
workflowRun: {
route: '/workflow/:workflowName/run',
method: 'post',
func: workflowRunner,
},
workflowStatus: {
route: '/workflow/:workflowName/status/:runId',
method: 'get',
func: workflowStatusChecker,
},
workflowStatusStream: {
route: '/workflow/:workflowName/status/:runId/stream',
method: 'get',
sse: true,
func: workflowStatusStream,
},
workflowStatusStreamFull: {
route: '/workflow/:workflowName/status/:runId/stream/full',
method: 'get',
sse: true,
func: workflowStatusStreamFull,
},
graphStart: {
route: '/workflow/:workflowName/graph/:nodeId',
method: 'post',
func: graphStarter,
},
},
})