create-loopengine 0.3.1 → 0.3.3
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/package.json +1 -1
- package/template/adapters/http.ts +40 -7
- package/template/package.json +1 -1
package/package.json
CHANGED
|
@@ -67,6 +67,7 @@ import {
|
|
|
67
67
|
type AgentConfig,
|
|
68
68
|
type AgentModelConfig,
|
|
69
69
|
type ToolDefinition,
|
|
70
|
+
type Message,
|
|
70
71
|
type ModelContentBlock,
|
|
71
72
|
type RunAgentResult,
|
|
72
73
|
playgroundHtml,
|
|
@@ -120,6 +121,8 @@ import {
|
|
|
120
121
|
agentDirFor,
|
|
121
122
|
verifyProxyIdentity,
|
|
122
123
|
TRUSTED_PROXY_IDENTITY_HEADER,
|
|
124
|
+
reportRun,
|
|
125
|
+
usedTool,
|
|
123
126
|
NotifierFileError,
|
|
124
127
|
verifyDecisionToken,
|
|
125
128
|
decidePath,
|
|
@@ -646,7 +649,11 @@ async function describeAgent(entry: RegistryEntry): Promise<Record<string, unkno
|
|
|
646
649
|
// tab badge; the Apps tab's own GET .../abilities has the detail
|
|
647
650
|
// (and its slower npm version lookups).
|
|
648
651
|
abilityCount: listInstalledAbilities(config.name).length,
|
|
649
|
-
|
|
652
|
+
// A skill that's a workflow is edited as one, on the Workflows tab.
|
|
653
|
+
skills: (() => {
|
|
654
|
+
const workflowIds = new Set(readWorkflows(agentDirFor(config)).map((w) => w.id))
|
|
655
|
+
return skillIndex.map((s) => ({ name: s.name, description: s.description, ...(workflowIds.has(s.name) ? { workflow: true } : {}) }))
|
|
656
|
+
})(),
|
|
650
657
|
systemSkills: systemSkillIndex.map((s) => ({ name: s.name, description: s.description })),
|
|
651
658
|
tools: tools.map(describeTool),
|
|
652
659
|
// createAskUserTool() with no onPending, purely to describe its
|
|
@@ -1925,6 +1932,8 @@ async function executeUnattendedRun(agent: string, message: string, rawSessionId
|
|
|
1925
1932
|
async function reportWorkflowRun(run: WorkflowRun): Promise<void> {
|
|
1926
1933
|
const event: RunCallbackEvent | undefined = run.status === 'done' ? 'run.done' : run.status === 'error' ? 'run.failed' : run.status === 'awaiting_approval' ? 'run.awaiting_approval' : undefined
|
|
1927
1934
|
if (!event) return
|
|
1935
|
+
// For a host's run observer (core/run-events.ts); a failed run isn't reported.
|
|
1936
|
+
if (run.status !== 'error') reportRun({ agent: run.agent, source: 'workflow', id: run.run_id, via: run.source, usedTool: true })
|
|
1928
1937
|
const entry = getEntry(run.agent)
|
|
1929
1938
|
if (run.notify && entry && event !== 'run.awaiting_approval') {
|
|
1930
1939
|
const text = run.status === 'done' ? `Workflow ${run.workflow_id} for ${run.agent} ran:\n${run.result ?? ''}` : `Workflow ${run.workflow_id} for ${run.agent} failed: ${run.error ?? 'unknown error'}`
|
|
@@ -1942,7 +1951,12 @@ const workflowRunner = new WorkflowRunner({ execute: executeUnattendedRun, onCha
|
|
|
1942
1951
|
// A task that runs a workflow is also recorded as a run of that workflow,
|
|
1943
1952
|
// so the workflow's Recent runs shows its scheduled runs too.
|
|
1944
1953
|
function fireScheduledTask(task: ScheduledTask, rawSessionId: string): ReturnType<typeof executeUnattendedRun> {
|
|
1945
|
-
if (!task.workflow_id)
|
|
1954
|
+
if (!task.workflow_id) {
|
|
1955
|
+
return executeUnattendedRun(task.agent, task.message, rawSessionId).then((outcome) => {
|
|
1956
|
+
reportRun({ agent: task.agent, source: 'schedule', id: randomUUID(), usedTool: true })
|
|
1957
|
+
return outcome
|
|
1958
|
+
})
|
|
1959
|
+
}
|
|
1946
1960
|
return workflowRunner.runForSchedule({
|
|
1947
1961
|
agent: task.agent,
|
|
1948
1962
|
workflow_id: task.workflow_id,
|
|
@@ -1999,12 +2013,14 @@ async function handleSchedulesGet(res: ServerResponse, agentName: string): Promi
|
|
|
1999
2013
|
if (!t.workflow_id) return t
|
|
2000
2014
|
const workflow = workflows.find((w) => w.id === t.workflow_id)
|
|
2001
2015
|
// Its start message as this task's runs see it, parameters filled in.
|
|
2002
|
-
|
|
2003
|
-
|
|
2016
|
+
// An older workflow's start message, else its instructions' first line.
|
|
2017
|
+
const lead = workflow && (workflow.startMessage || workflow.instructions.split('\n').find((line) => line.trim()) || '')
|
|
2018
|
+
const startMessage = workflow && fillStartMessage(lead!, t.params ?? {}, Object.keys(workflow.parameters))
|
|
2019
|
+
return workflow ? { ...t, workflow: { id: workflow.id, title: workflow.title, description: workflow.description, start_message: startMessage } } : { ...t, workflow_missing: true }
|
|
2004
2020
|
})
|
|
2005
2021
|
scheduleJson(res, 200, {
|
|
2006
2022
|
tasks,
|
|
2007
|
-
workflows: workflows.map((w) => ({ id: w.id, title: w.title, description: w.description, start_message: w.startMessage, parameters: w.parameters })),
|
|
2023
|
+
workflows: workflows.map((w) => ({ id: w.id, title: w.title, description: w.description, start_message: w.startMessage || w.instructions.split('\n').find((line) => line.trim()) || '', parameters: w.parameters })),
|
|
2008
2024
|
time_zone: workspaceTimeZone(),
|
|
2009
2025
|
})
|
|
2010
2026
|
}
|
|
@@ -2917,6 +2933,19 @@ async function raceAndRespond(res: ServerResponse, agentName: string, rawSession
|
|
|
2917
2933
|
res.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify(responseBody))
|
|
2918
2934
|
}
|
|
2919
2935
|
|
|
2936
|
+
// Who sent a message, as a trusted proxy says in its signed identity:
|
|
2937
|
+
// an API key (role "api") or a signed-in person. Without a proxy, unknown.
|
|
2938
|
+
function messageCaller(req: IncomingMessage): 'api' | 'console' | 'unknown' {
|
|
2939
|
+
const identity = trustedProxySecret ? verifyProxyIdentity(req.headers[TRUSTED_PROXY_IDENTITY_HEADER], trustedProxySecret) : undefined
|
|
2940
|
+
if (!identity) return 'unknown'
|
|
2941
|
+
return identity.role === 'api' ? 'api' : 'console'
|
|
2942
|
+
}
|
|
2943
|
+
|
|
2944
|
+
// For a host's run observer (core/run-events.ts).
|
|
2945
|
+
function reportMessageRun(req: IncomingMessage, agent: string, newMessages: Message[]): void {
|
|
2946
|
+
reportRun({ agent, source: 'message', id: randomUUID(), caller: messageCaller(req), usedTool: usedTool(newMessages) })
|
|
2947
|
+
}
|
|
2948
|
+
|
|
2920
2949
|
async function handleMessages(req: IncomingMessage, res: ServerResponse, agentName: string): Promise<void> {
|
|
2921
2950
|
const parsed = await parseRequest(req, agentName)
|
|
2922
2951
|
if (!parsed.ok) {
|
|
@@ -2982,6 +3011,7 @@ async function handleMessages(req: IncomingMessage, res: ServerResponse, agentNa
|
|
|
2982
3011
|
pushSignal(state, { type: 'question', entry: question })
|
|
2983
3012
|
},
|
|
2984
3013
|
})
|
|
3014
|
+
reportMessageRun(req, agentName, result.newMessages)
|
|
2985
3015
|
if (result.pending) {
|
|
2986
3016
|
await createCheckpointFromPending(agentName, tenant, rawSessionId, result.pending)
|
|
2987
3017
|
}
|
|
@@ -3092,6 +3122,7 @@ async function handleMessagesStream(req: IncomingMessage, res: ServerResponse, a
|
|
|
3092
3122
|
pushSignal(state, { type: 'question', entry: question })
|
|
3093
3123
|
},
|
|
3094
3124
|
})
|
|
3125
|
+
reportMessageRun(req, agentName, result.newMessages)
|
|
3095
3126
|
if (result.pending) {
|
|
3096
3127
|
await createCheckpointFromPending(agentName, tenant, rawSessionId, result.pending)
|
|
3097
3128
|
}
|
|
@@ -3963,7 +3994,7 @@ function jobDeps(agentName: string, entry: RegistryEntry, connector: MessagingCo
|
|
|
3963
3994
|
connector,
|
|
3964
3995
|
plan: process.env.LOOPENGINE_PLAN,
|
|
3965
3996
|
allowed: (message) => jobAllowed(entry.config, app, message),
|
|
3966
|
-
runTurn: (sessionId, text) => runJobTurn(agentName, sessionId, text),
|
|
3997
|
+
runTurn: (sessionId, text) => runJobTurn(agentName, sessionId, text, connector.app === 'telegram' ? 'telegram' : 'slack'),
|
|
3967
3998
|
decideLink: (pendingId, kind) => {
|
|
3968
3999
|
const base = publicBaseUrl(connectionPublicUrl(entry.config, app))
|
|
3969
4000
|
return base ? `${base}${decidePath(pendingId, kind)}` : undefined
|
|
@@ -3998,7 +4029,7 @@ function jobTurnOf(text: string, pending: PendingSummary[] | undefined): JobTurn
|
|
|
3998
4029
|
}
|
|
3999
4030
|
|
|
4000
4031
|
// One turn of a job thread: the same session for the whole thread.
|
|
4001
|
-
async function runJobTurn(agent: string, rawSessionId: string, message: string): Promise<JobTurn> {
|
|
4032
|
+
async function runJobTurn(agent: string, rawSessionId: string, message: string, source: 'slack' | 'telegram'): Promise<JobTurn> {
|
|
4002
4033
|
const entry = getEntry(agent)
|
|
4003
4034
|
if (!entry) throw new Error(`no agent named "${agent}" in this project`)
|
|
4004
4035
|
const tenant = 'default'
|
|
@@ -4011,6 +4042,8 @@ async function runJobTurn(agent: string, rawSessionId: string, message: string):
|
|
|
4011
4042
|
approver: threadApprover,
|
|
4012
4043
|
questionHandler: threadQuestions,
|
|
4013
4044
|
})
|
|
4045
|
+
// For a host's run observer (core/run-events.ts).
|
|
4046
|
+
reportRun({ agent, source, id: randomUUID(), usedTool: usedTool(result.newMessages) })
|
|
4014
4047
|
if (result.pending) await createCheckpointFromPending(agent, tenant, rawSessionId, result.pending)
|
|
4015
4048
|
return { newMessages: result.newMessages, result: { text: result.text, pending: pendingSummary(result.pending) } }
|
|
4016
4049
|
})
|