@jigging/agent-method 0.1.0-alpha.3 → 0.1.0-alpha.5

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/AGENTS.md CHANGED
@@ -73,6 +73,8 @@ ordinary Flow, preserving operator ownership of Agent execution.
73
73
  - The ordinary ACP package imports this same method. `checkAgentResult` lets
74
74
  consumers independently check any selected
75
75
  Agent's dynamic structured result; it grants no authority or provenance.
76
+ Its validated `AgentResult` is assignable to portable FLOW JSON without a
77
+ cast; arbitrary exceptions and non-JSON values are not.
76
78
 
77
79
  ## Work Guidance
78
80
 
package/dist/index.d.ts CHANGED
@@ -51,14 +51,14 @@ export interface PreparedAgent {
51
51
  readonly request: AgentTransportInput;
52
52
  readonly session?: AgentSessionRequest;
53
53
  }
54
- export interface AgentResult {
54
+ export type AgentResult = {
55
55
  readonly outcome: 'done' | 'blocked' | 'limit';
56
56
  readonly output: {
57
57
  readonly text: string;
58
58
  readonly structured?: JsonValue;
59
59
  readonly session?: AgentSessionReceipt;
60
60
  };
61
- }
61
+ };
62
62
  /** Independently check an Agent Flow result at its consumer's boundary. */
63
63
  export declare function checkAgentResult(value: unknown, responseSchema?: JsonObject): AgentResult;
64
64
  /** Prepare one bounded request. The returned data confers no execution authority. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jigging/agent-method",
3
- "version": "0.1.0-alpha.3",
3
+ "version": "0.1.0-alpha.5",
4
4
  "description": "A reusable bounded Agent method with an ordinary FLOW entrypoint",
5
5
  "license": "MPL-2.0",
6
6
  "repository": {
@@ -47,8 +47,8 @@
47
47
  "test"
48
48
  ],
49
49
  "devDependencies": {
50
- "@jigging/flow": "0.1.0-alpha.12",
50
+ "@jigging/flow": "0.1.0-alpha.13",
51
51
  "@types/node": "24.13.3",
52
52
  "typescript": "7.0.2"
53
53
  }
54
- }
54
+ }
package/src/index.ts CHANGED
@@ -64,7 +64,7 @@ export interface PreparedAgent {
64
64
  readonly session?: AgentSessionRequest
65
65
  }
66
66
 
67
- export interface AgentResult {
67
+ export type AgentResult = {
68
68
  readonly outcome: 'done' | 'blocked' | 'limit'
69
69
  readonly output: {
70
70
  readonly text: string
@@ -1,5 +1,5 @@
1
1
  import { handle, type JsonValue } from '@jigging/flow'
2
- import { withAgentConversation, AgentConversationError } from '../src/conversation.js'
2
+ import { AgentConversationError, withAgentConversation } from '../src/conversation.js'
3
3
 
4
4
  await handle(async (run) => {
5
5
  const displayed: string[] = []
@@ -32,9 +32,12 @@ await handle(async (run) => {
32
32
  const first = await conversation.initial
33
33
  if (run.input === 'callback-error') throw new Error('application rejected draft')
34
34
  const second = conversation.prompt({ instructions: 'Apply this correction' })
35
- if (run.input === 'interrupt' || run.input === 'completion-race')
36
- await conversation.interrupt()
37
- return { first, second: await second }
35
+ const control = ['interrupt', 'completion-race', 'not-running-race'].includes(
36
+ run.input as string,
37
+ )
38
+ ? await conversation.interrupt()
39
+ : undefined
40
+ return { first, second: await second, ...(control === undefined ? {} : { control }) }
38
41
  },
39
42
  )
40
43
  return {
@@ -136,14 +136,27 @@ async function exercise(mode: string) {
136
136
  } else if (request.method === 'channel/send') {
137
137
  ok(request, null)
138
138
  const command = p.value
139
- item({ type: 'accepted', command: command.type, turn: command.turn })
140
- if (command.type === 'prompt') {
141
- if (mode === 'late-error') {
142
- nativeSettled = true
143
- fail(call, 'EXECUTION_FAILED')
144
- } else if (!['interrupt', 'completion-race'].includes(mode)) terminal(command.turn)
145
- } else if (command.type === 'interrupt')
146
- terminal(command.turn, mode === 'interrupt' ? 'cancelled' : 'result')
139
+ if (command.type === 'interrupt' && mode === 'not-running-race') {
140
+ // The turn finishes as the interruption reaches the Agent. The
141
+ // terminal result must remain distinct from the control rejection.
142
+ terminal(command.turn)
143
+ item({
144
+ type: 'rejected',
145
+ command: 'interrupt',
146
+ turn: command.turn,
147
+ code: 'NOT_RUNNING',
148
+ })
149
+ } else {
150
+ item({ type: 'accepted', command: command.type, turn: command.turn })
151
+ if (command.type === 'prompt') {
152
+ if (mode === 'late-error') {
153
+ nativeSettled = true
154
+ fail(call, 'EXECUTION_FAILED')
155
+ } else if (!['interrupt', 'completion-race', 'not-running-race'].includes(mode))
156
+ terminal(command.turn)
157
+ } else if (command.type === 'interrupt')
158
+ terminal(command.turn, mode === 'interrupt' ? 'cancelled' : 'result')
159
+ }
147
160
  } else if (request.method === 'channel/close') {
148
161
  expect(p.endpoint).toBe('commands:s')
149
162
  closed = true
@@ -230,11 +243,21 @@ async function exercise(mode: string) {
230
243
  }
231
244
  }
232
245
 
233
- for (const mode of ['normal', 'interrupt', 'completion-race']) {
246
+ for (const mode of ['normal', 'interrupt', 'completion-race', 'not-running-race']) {
234
247
  test(`public conversation helper: ${mode}`, async () => {
235
- const { output } = await exercise(mode)
248
+ const { output, operations } = await exercise(mode)
236
249
  expect(output.turns).toHaveLength(2)
237
250
  expect(output.value.second.type).toBe(mode === 'interrupt' ? 'cancelled' : 'result')
251
+ if (mode === 'interrupt' || mode === 'completion-race')
252
+ expect(output.value.control).toBe('accepted')
253
+ if (mode === 'not-running-race') {
254
+ expect(output.value.control).toBe('not-running')
255
+ expect(
256
+ operations
257
+ .filter((operation) => operation.method === 'channel/send')
258
+ .map((operation) => operation.params.value.type),
259
+ ).toEqual(['prompt', 'interrupt', 'close'])
260
+ }
238
261
  expect(output.settlement).toEqual({ outcome: 'done', output: { turns: 2 } })
239
262
  })
240
263
  }
@@ -0,0 +1,17 @@
1
+ import { type AgentResult, checkAgentResult } from '@jigging/agent-method'
2
+ import type { JsonValue, RunHandler } from '@jigging/flow'
3
+
4
+ const result: AgentResult = checkAgentResult({ outcome: 'done', output: { text: 'answer' } })
5
+ const direct: JsonValue = result
6
+ const nested: JsonValue = { agent: result }
7
+ const handler: RunHandler = async () => ({ outcome: 'done', output: result })
8
+
9
+ void [direct, nested, handler]
10
+
11
+ // @ts-expect-error Functions are not JSON values.
12
+ const functionOutput: JsonValue = () => 'answer'
13
+ // @ts-expect-error Undefined is not a JSON value.
14
+ const undefinedOutput: JsonValue = undefined
15
+ // @ts-expect-error Error objects are not JSON values.
16
+ const errorOutput: JsonValue = new Error('unhandled')
17
+ void [functionOutput, undefinedOutput, errorOutput]
@@ -0,0 +1,41 @@
1
+ import { test } from 'bun:test'
2
+ import { execFileSync } from 'node:child_process'
3
+ import { copyFile, mkdir, mkdtemp, rm, symlink } from 'node:fs/promises'
4
+ import { tmpdir } from 'node:os'
5
+ import { join } from 'node:path'
6
+ import { fileURLToPath } from 'node:url'
7
+
8
+ test('public Agent results compose with FLOW JSON without casts', async () => {
9
+ const method = fileURLToPath(new URL('../', import.meta.url))
10
+ const flow = fileURLToPath(new URL('../../flow-sdk/', import.meta.url))
11
+ const consumer = await mkdtemp(join(tmpdir(), 'agent-public-types-'))
12
+ try {
13
+ await mkdir(join(consumer, 'node_modules/@jigging'), { recursive: true })
14
+ await symlink(method, join(consumer, 'node_modules/@jigging/agent-method'))
15
+ await symlink(flow, join(consumer, 'node_modules/@jigging/flow'))
16
+ await copyFile(
17
+ new URL('./fixtures/public-result-types.ts', import.meta.url),
18
+ join(consumer, 'consumer.ts'),
19
+ )
20
+ execFileSync(
21
+ process.execPath,
22
+ [
23
+ join(method, 'node_modules/typescript/bin/tsc'),
24
+ '--ignoreConfig',
25
+ '--noEmit',
26
+ '--strict',
27
+ '--exactOptionalPropertyTypes',
28
+ '--target',
29
+ 'ES2022',
30
+ '--module',
31
+ 'NodeNext',
32
+ '--moduleResolution',
33
+ 'NodeNext',
34
+ 'consumer.ts',
35
+ ],
36
+ { cwd: consumer, stdio: 'pipe' },
37
+ )
38
+ } finally {
39
+ await rm(consumer, { recursive: true, force: true })
40
+ }
41
+ })