create-loopengine 0.3.1 → 0.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-loopengine",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Scaffold a new loopengine agent project — a runnable HTTP server with one example agent, in your own repo.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -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,
@@ -1925,6 +1928,8 @@ async function executeUnattendedRun(agent: string, message: string, rawSessionId
1925
1928
  async function reportWorkflowRun(run: WorkflowRun): Promise<void> {
1926
1929
  const event: RunCallbackEvent | undefined = run.status === 'done' ? 'run.done' : run.status === 'error' ? 'run.failed' : run.status === 'awaiting_approval' ? 'run.awaiting_approval' : undefined
1927
1930
  if (!event) return
1931
+ // For a host's run observer (core/run-events.ts); a failed run isn't reported.
1932
+ if (run.status !== 'error') reportRun({ agent: run.agent, source: 'workflow', id: run.run_id, via: run.source, usedTool: true })
1928
1933
  const entry = getEntry(run.agent)
1929
1934
  if (run.notify && entry && event !== 'run.awaiting_approval') {
1930
1935
  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 +1947,12 @@ const workflowRunner = new WorkflowRunner({ execute: executeUnattendedRun, onCha
1942
1947
  // A task that runs a workflow is also recorded as a run of that workflow,
1943
1948
  // so the workflow's Recent runs shows its scheduled runs too.
1944
1949
  function fireScheduledTask(task: ScheduledTask, rawSessionId: string): ReturnType<typeof executeUnattendedRun> {
1945
- if (!task.workflow_id) return executeUnattendedRun(task.agent, task.message, rawSessionId)
1950
+ if (!task.workflow_id) {
1951
+ return executeUnattendedRun(task.agent, task.message, rawSessionId).then((outcome) => {
1952
+ reportRun({ agent: task.agent, source: 'schedule', id: randomUUID(), usedTool: true })
1953
+ return outcome
1954
+ })
1955
+ }
1946
1956
  return workflowRunner.runForSchedule({
1947
1957
  agent: task.agent,
1948
1958
  workflow_id: task.workflow_id,
@@ -2917,6 +2927,19 @@ async function raceAndRespond(res: ServerResponse, agentName: string, rawSession
2917
2927
  res.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify(responseBody))
2918
2928
  }
2919
2929
 
2930
+ // Who sent a message, as a trusted proxy says in its signed identity:
2931
+ // an API key (role "api") or a signed-in person. Without a proxy, unknown.
2932
+ function messageCaller(req: IncomingMessage): 'api' | 'console' | 'unknown' {
2933
+ const identity = trustedProxySecret ? verifyProxyIdentity(req.headers[TRUSTED_PROXY_IDENTITY_HEADER], trustedProxySecret) : undefined
2934
+ if (!identity) return 'unknown'
2935
+ return identity.role === 'api' ? 'api' : 'console'
2936
+ }
2937
+
2938
+ // For a host's run observer (core/run-events.ts).
2939
+ function reportMessageRun(req: IncomingMessage, agent: string, newMessages: Message[]): void {
2940
+ reportRun({ agent, source: 'message', id: randomUUID(), caller: messageCaller(req), usedTool: usedTool(newMessages) })
2941
+ }
2942
+
2920
2943
  async function handleMessages(req: IncomingMessage, res: ServerResponse, agentName: string): Promise<void> {
2921
2944
  const parsed = await parseRequest(req, agentName)
2922
2945
  if (!parsed.ok) {
@@ -2982,6 +3005,7 @@ async function handleMessages(req: IncomingMessage, res: ServerResponse, agentNa
2982
3005
  pushSignal(state, { type: 'question', entry: question })
2983
3006
  },
2984
3007
  })
3008
+ reportMessageRun(req, agentName, result.newMessages)
2985
3009
  if (result.pending) {
2986
3010
  await createCheckpointFromPending(agentName, tenant, rawSessionId, result.pending)
2987
3011
  }
@@ -3092,6 +3116,7 @@ async function handleMessagesStream(req: IncomingMessage, res: ServerResponse, a
3092
3116
  pushSignal(state, { type: 'question', entry: question })
3093
3117
  },
3094
3118
  })
3119
+ reportMessageRun(req, agentName, result.newMessages)
3095
3120
  if (result.pending) {
3096
3121
  await createCheckpointFromPending(agentName, tenant, rawSessionId, result.pending)
3097
3122
  }
@@ -3963,7 +3988,7 @@ function jobDeps(agentName: string, entry: RegistryEntry, connector: MessagingCo
3963
3988
  connector,
3964
3989
  plan: process.env.LOOPENGINE_PLAN,
3965
3990
  allowed: (message) => jobAllowed(entry.config, app, message),
3966
- runTurn: (sessionId, text) => runJobTurn(agentName, sessionId, text),
3991
+ runTurn: (sessionId, text) => runJobTurn(agentName, sessionId, text, connector.app === 'telegram' ? 'telegram' : 'slack'),
3967
3992
  decideLink: (pendingId, kind) => {
3968
3993
  const base = publicBaseUrl(connectionPublicUrl(entry.config, app))
3969
3994
  return base ? `${base}${decidePath(pendingId, kind)}` : undefined
@@ -3998,7 +4023,7 @@ function jobTurnOf(text: string, pending: PendingSummary[] | undefined): JobTurn
3998
4023
  }
3999
4024
 
4000
4025
  // 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> {
4026
+ async function runJobTurn(agent: string, rawSessionId: string, message: string, source: 'slack' | 'telegram'): Promise<JobTurn> {
4002
4027
  const entry = getEntry(agent)
4003
4028
  if (!entry) throw new Error(`no agent named "${agent}" in this project`)
4004
4029
  const tenant = 'default'
@@ -4011,6 +4036,8 @@ async function runJobTurn(agent: string, rawSessionId: string, message: string):
4011
4036
  approver: threadApprover,
4012
4037
  questionHandler: threadQuestions,
4013
4038
  })
4039
+ // For a host's run observer (core/run-events.ts).
4040
+ reportRun({ agent, source, id: randomUUID(), usedTool: usedTool(result.newMessages) })
4014
4041
  if (result.pending) await createCheckpointFromPending(agent, tenant, rawSessionId, result.pending)
4015
4042
  return { newMessages: result.newMessages, result: { text: result.text, pending: pendingSummary(result.pending) } }
4016
4043
  })
@@ -9,7 +9,7 @@
9
9
  "cli": "tsx adapters/cli.ts"
10
10
  },
11
11
  "dependencies": {
12
- "loopengine": "^0.3.3",
12
+ "loopengine": "^0.3.4",
13
13
  "actauth": "^0.0.12",
14
14
  "tsx": "^4.19.0",
15
15
  "typescript": "^5.7.0"