@pi-in-go/pigpen-a2a 0.1.0

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.
Files changed (52) hide show
  1. package/CREDITS.md +26 -0
  2. package/LICENSE +21 -0
  3. package/README.md +97 -0
  4. package/extensions/a2a/auth.go +120 -0
  5. package/extensions/a2a/auth_test.go +133 -0
  6. package/extensions/a2a/bench_test.go +70 -0
  7. package/extensions/a2a/binary_test.go +225 -0
  8. package/extensions/a2a/cancelqueued_test.go +142 -0
  9. package/extensions/a2a/card_cache_test.go +42 -0
  10. package/extensions/a2a/client.go +499 -0
  11. package/extensions/a2a/client_test.go +402 -0
  12. package/extensions/a2a/config.go +282 -0
  13. package/extensions/a2a/config_test.go +194 -0
  14. package/extensions/a2a/e2e_test.go +311 -0
  15. package/extensions/a2a/executor.go +318 -0
  16. package/extensions/a2a/extension.go +382 -0
  17. package/extensions/a2a/extension_test.go +396 -0
  18. package/extensions/a2a/fakehost_test.go +548 -0
  19. package/extensions/a2a/fakepig_test.go +212 -0
  20. package/extensions/a2a/gaps_test.go +59 -0
  21. package/extensions/a2a/go.mod +15 -0
  22. package/extensions/a2a/go.sum +14 -0
  23. package/extensions/a2a/interop_test.go +163 -0
  24. package/extensions/a2a/procattr_other.go +25 -0
  25. package/extensions/a2a/procattr_windows.go +26 -0
  26. package/extensions/a2a/resubscribe_test.go +113 -0
  27. package/extensions/a2a/review_test.go +293 -0
  28. package/extensions/a2a/server.go +267 -0
  29. package/extensions/a2a/server_test.go +792 -0
  30. package/extensions/a2a/survivors_test.go +302 -0
  31. package/extensions/a2a/worker.go +388 -0
  32. package/extensions/a2a/worker_test.go +257 -0
  33. package/package.json +41 -0
  34. package/port/PORT.md +126 -0
  35. package/port/a2a-go-LICENSE +201 -0
  36. package/port/golden/flag-without-auth-refused.jsonl +5 -0
  37. package/port/golden/listener-off.jsonl +4 -0
  38. package/port/golden/send-missing-message.jsonl +18 -0
  39. package/port/golden/send-without-remotes.jsonl +18 -0
  40. package/port/golden/task-unknown-action.jsonl +18 -0
  41. package/port/golden/tools-visible-to-model.jsonl +11 -0
  42. package/port/interop/kagent/main.go +73 -0
  43. package/port/mutation-run.txt +222 -0
  44. package/port/mutations.json +656 -0
  45. package/port/red.txt +105 -0
  46. package/port/scenarios/flag-without-auth-refused.json +8 -0
  47. package/port/scenarios/listener-off.json +7 -0
  48. package/port/scenarios/send-missing-message.json +11 -0
  49. package/port/scenarios/send-without-remotes.json +11 -0
  50. package/port/scenarios/task-unknown-action.json +11 -0
  51. package/port/scenarios/tools-visible-to-model.json +8 -0
  52. package/provenance.json +18 -0
@@ -0,0 +1,656 @@
1
+ [
2
+ {
3
+ "name": "listener-starts-without-config",
4
+ "file": "extension.go",
5
+ "find": "\tif !cfg.Enabled() {\n\t\treturn\n\t}",
6
+ "replace": "\tif false {\n\t\treturn\n\t}"
7
+ },
8
+ {
9
+ "name": "worker-child-may-listen",
10
+ "file": "config.go",
11
+ "find": "\t\tcfg.Listen = \"\"\n\t}\n\tif cfg.StateDir",
12
+ "replace": "\t\t_ = 0\n\t}\n\tif cfg.StateDir"
13
+ },
14
+ {
15
+ "name": "flag-not-highest-precedence",
16
+ "file": "config.go",
17
+ "find": "\tif o.FlagListen != \"\" {\n\t\tcfg.Listen = o.FlagListen\n\t}",
18
+ "replace": "\tif o.FlagListen != \"\" && cfg.Listen == \"\" {\n\t\tcfg.Listen = o.FlagListen\n\t}"
19
+ },
20
+ {
21
+ "name": "env-not-over-file",
22
+ "file": "config.go",
23
+ "find": "\tif v := getenv(\"PIG_A2A_LISTEN\"); v != \"\" {",
24
+ "replace": "\tif v := getenv(\"PIG_A2A_LISTEN\"); v != \"\" && cfg.Listen == \"\" {"
25
+ },
26
+ {
27
+ "name": "unknown-keys-and-inline-secrets-accepted",
28
+ "file": "config.go",
29
+ "find": "\t\t\tdec.DisallowUnknownFields()\n",
30
+ "replace": ""
31
+ },
32
+ {
33
+ "name": "listener-without-tokens-allowed",
34
+ "file": "config.go",
35
+ "find": "\t\tcase !c.InsecureNoAuth && len(c.Tokens) == 0:",
36
+ "replace": "\t\tcase false:"
37
+ },
38
+ {
39
+ "name": "insecure-no-auth-off-loopback",
40
+ "file": "config.go",
41
+ "find": "\t\tcase c.InsecureNoAuth && !isLoopbackHost(host):",
42
+ "replace": "\t\tcase false:"
43
+ },
44
+ {
45
+ "name": "insecure-and-tokens-both",
46
+ "file": "config.go",
47
+ "find": "\t\tcase c.InsecureNoAuth && len(c.Tokens) > 0:",
48
+ "replace": "\t\tcase false:"
49
+ },
50
+ {
51
+ "name": "short-tokens-allowed",
52
+ "file": "config.go",
53
+ "find": "\t\tif len(v) < minTokenLength {",
54
+ "replace": "\t\tif false {"
55
+ },
56
+ {
57
+ "name": "duplicate-token-values-allowed",
58
+ "file": "config.go",
59
+ "find": "\t\tif values[v] {",
60
+ "replace": "\t\tif false {"
61
+ },
62
+ {
63
+ "name": "duplicate-token-names-allowed",
64
+ "file": "config.go",
65
+ "find": "\t\tif names[t.Name] || envs[t.TokenEnv] {",
66
+ "replace": "\t\tif false {"
67
+ },
68
+ {
69
+ "name": "tenant-not-validated",
70
+ "file": "config.go",
71
+ "find": "\t\tif t.Tenant != \"\" && !identRE.MatchString(t.Tenant) {",
72
+ "replace": "\t\tif false {"
73
+ },
74
+ {
75
+ "name": "missing-token-env-allowed",
76
+ "file": "config.go",
77
+ "find": "\t\tif v == \"\" {\n\t\t\treturn fmt.Errorf(\"a2a: token %q: environment variable %s is not set\", t.Name, t.TokenEnv)",
78
+ "replace": "\t\tif false {\n\t\t\treturn fmt.Errorf(\"a2a: token %q: environment variable %s is not set\", t.Name, t.TokenEnv)"
79
+ },
80
+ {
81
+ "name": "listen-not-host-port",
82
+ "file": "config.go",
83
+ "find": "\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"a2a: listen %q is not host:port: %w\", c.Listen, err)\n\t\t}",
84
+ "replace": "\t\t_ = err"
85
+ },
86
+ {
87
+ "name": "worker-gets-pig-default-tools",
88
+ "file": "worker.go",
89
+ "find": "\t} else {\n\t\t// PiG's file tools are not confined to the working directory, so a worker gets no tools unless named.\n\t\targs = append(args, \"--no-tools\")\n\t}",
90
+ "replace": "\t}"
91
+ },
92
+ {
93
+ "name": "remote-url-scheme-unchecked",
94
+ "file": "config.go",
95
+ "find": "\tif err != nil || u.Host == \"\" || (u.Scheme != \"http\" && u.Scheme != \"https\") {",
96
+ "replace": "\tif err != nil || u.Host == \"\" {"
97
+ },
98
+ {
99
+ "name": "remote-name-unchecked",
100
+ "file": "config.go",
101
+ "find": "\t\tif !identRE.MatchString(name) {\n\t\t\treturn fmt.Errorf(\"a2a: remote name",
102
+ "replace": "\t\tif false {\n\t\t\treturn fmt.Errorf(\"a2a: remote name"
103
+ },
104
+ {
105
+ "name": "basic-scheme-accepted",
106
+ "file": "auth.go",
107
+ "find": "!strings.EqualFold(fields[0], \"bearer\")",
108
+ "replace": "false"
109
+ },
110
+ {
111
+ "name": "extra-fields-accepted",
112
+ "file": "auth.go",
113
+ "find": "len(fields) != 2 ||",
114
+ "replace": "len(fields) < 2 ||"
115
+ },
116
+ {
117
+ "name": "token-comparison-inverted",
118
+ "file": "auth.go",
119
+ "find": "subtle.ConstantTimeCompare(digest[:], e.digest[:]) == 1",
120
+ "replace": "subtle.ConstantTimeCompare(digest[:], e.digest[:]) != 1"
121
+ },
122
+ {
123
+ "name": "insecure-flag-ignored",
124
+ "file": "auth.go",
125
+ "find": "\tif a.insecure {\n\t\treturn Principal{Name: \"anonymous\"}, true\n\t}\n\tfields",
126
+ "replace": "\tfields"
127
+ },
128
+ {
129
+ "name": "tenant-key-uses-token-name",
130
+ "file": "auth.go",
131
+ "find": "\t\treturn \"tenant:\" + p.Tenant",
132
+ "replace": "\t\treturn \"tenant:\" + p.Name"
133
+ },
134
+ {
135
+ "name": "key-namespaces-collide",
136
+ "file": "auth.go",
137
+ "find": "\treturn \"token:\" + p.Name",
138
+ "replace": "\treturn \"tenant:\" + p.Name"
139
+ },
140
+ {
141
+ "name": "everything-public",
142
+ "file": "auth.go",
143
+ "find": "\t\tif isPublic != nil && isPublic(r) {",
144
+ "replace": "\t\tif true {"
145
+ },
146
+ {
147
+ "name": "no-bearer-challenge",
148
+ "file": "auth.go",
149
+ "find": "\t\t\tw.Header().Set(\"WWW-Authenticate\", \"Bearer\")\n",
150
+ "replace": ""
151
+ },
152
+ {
153
+ "name": "empty-authenticator-allowed",
154
+ "file": "auth.go",
155
+ "find": "\tif len(tokens) == 0 {\n\t\treturn nil, errors.New",
156
+ "replace": "\tif false {\n\t\treturn nil, errors.New"
157
+ },
158
+ {
159
+ "name": "file-parts-dropped",
160
+ "file": "executor.go",
161
+ "find": "\t\tif _, ok := p.Content.(a2a.Text); !ok {",
162
+ "replace": "\t\tif _, ok := p.Content.(a2a.Text); false && !ok {"
163
+ },
164
+ {
165
+ "name": "empty-prompt-accepted",
166
+ "file": "executor.go",
167
+ "find": "\tif strings.TrimSpace(prompt) == \"\" {",
168
+ "replace": "\tif false {"
169
+ },
170
+ {
171
+ "name": "prompt-size-unbounded",
172
+ "file": "executor.go",
173
+ "find": "\tif len(prompt) > maxPromptBytes {",
174
+ "replace": "\tif false {"
175
+ },
176
+ {
177
+ "name": "timeout-never-fires",
178
+ "file": "executor.go",
179
+ "find": "timeoutTimer := time.AfterFunc(e.timeout, func() {",
180
+ "replace": "timeoutTimer := time.AfterFunc(24*time.Hour, func() {"
181
+ },
182
+ {
183
+ "name": "worker-error-detail-leaks",
184
+ "file": "executor.go",
185
+ "find": "yield(e.status(ec, a2a.TaskStateFailed, \"PiG could not complete the task.\"), nil)",
186
+ "replace": "yield(e.status(ec, a2a.TaskStateFailed, \"PiG could not complete the task: \"+ev.err.Error()), nil)"
187
+ },
188
+ {
189
+ "name": "context-not-serialised",
190
+ "file": "executor.go",
191
+ "find": "\t\tunlock, err := e.lockContext(runCtx, principal.Key()+\"\\x00\"+ec.ContextID)",
192
+ "replace": "\t\tunlock, err := e.lockContext(runCtx, string(ec.TaskID))"
193
+ },
194
+ {
195
+ "name": "concurrency-limit-ignored",
196
+ "file": "executor.go",
197
+ "find": "\t\tcase e.sem <- struct{}{}:\n\t\t\tdefer func() { <-e.sem }()",
198
+ "replace": "\t\tcase e.sem <- struct{}{}:\n\t\t\t<-e.sem"
199
+ },
200
+ {
201
+ "name": "cancel-does-not-stop-worker",
202
+ "file": "executor.go",
203
+ "find": "\t\t\tr.reason.CompareAndSwap(int32(stopNone), int32(stopCanceled))\n\t\t\tr.cancel()",
204
+ "replace": "\t\t\tr.reason.CompareAndSwap(int32(stopNone), int32(stopCanceled))"
205
+ },
206
+ {
207
+ "name": "failure-reason-dropped",
208
+ "file": "executor.go",
209
+ "find": "\"PiG could not complete the task: \"+ev.result.Failure",
210
+ "replace": "\"PiG could not complete the task.\""
211
+ },
212
+ {
213
+ "name": "principal-not-checked",
214
+ "file": "executor.go",
215
+ "find": "\tif p.Key() != ec.User.Name {\n\t\treturn Principal{}, a2a.ErrUnauthenticated\n\t}",
216
+ "replace": ""
217
+ },
218
+ {
219
+ "name": "version-not-enforced",
220
+ "file": "server.go",
221
+ "find": "!ok || !versionsOK(v)",
222
+ "replace": "false && (!ok || !versionsOK(v))"
223
+ },
224
+ {
225
+ "name": "absent-version-accepted",
226
+ "file": "server.go",
227
+ "find": "if v, ok := callCtx.ServiceParams().Get(a2a.SvcParamVersion); !ok || !versionsOK(v) {",
228
+ "replace": "if v, ok := callCtx.ServiceParams().Get(a2a.SvcParamVersion); ok && !versionsOK(v) {"
229
+ },
230
+ {
231
+ "name": "minor-versions-accepted",
232
+ "file": "server.go",
233
+ "find": "return v == string(ProtocolVersion) || strings.HasPrefix(v, string(ProtocolVersion)+\".\")",
234
+ "replace": "return strings.HasPrefix(v, \"1\") || strings.HasPrefix(v, \"0\")"
235
+ },
236
+ {
237
+ "name": "mixed-version-values-accepted",
238
+ "file": "server.go",
239
+ "find": "\t\t\tif !versionOK(strings.TrimSpace(one)) {\n\t\t\t\treturn false\n\t\t\t}",
240
+ "replace": "\t\t\tif !versionOK(strings.TrimSpace(one)) {\n\t\t\t\tcontinue\n\t\t\t}"
241
+ },
242
+ {
243
+ "name": "tenant-not-enforced",
244
+ "file": "server.go",
245
+ "find": "t != \"\" && t != p.Tenant",
246
+ "replace": "false && t != \"\" && t != p.Tenant"
247
+ },
248
+ {
249
+ "name": "user-not-attached",
250
+ "file": "server.go",
251
+ "find": "\tcallCtx.User = a2asrv.NewAuthenticatedUser(p.Key(), map[string]any{\"name\": p.Name, \"tenant\": p.Tenant})\n",
252
+ "replace": ""
253
+ },
254
+ {
255
+ "name": "body-size-unbounded",
256
+ "file": "server.go",
257
+ "find": "http.MaxBytesHandler(a2asrv.NewJSONRPCHandler(handler), maxBodyBytes)",
258
+ "replace": "a2asrv.NewJSONRPCHandler(handler)"
259
+ },
260
+ {
261
+ "name": "card-requires-auth",
262
+ "file": "server.go",
263
+ "find": "func(r *http.Request) bool { return r.URL.Path == a2asrv.WellKnownAgentCardPath }",
264
+ "replace": "func(r *http.Request) bool { return false }"
265
+ },
266
+ {
267
+ "name": "card-drops-streaming",
268
+ "file": "server.go",
269
+ "find": "Capabilities: a2a.AgentCapabilities{Streaming: true},",
270
+ "replace": "Capabilities: a2a.AgentCapabilities{Streaming: false},"
271
+ },
272
+ {
273
+ "name": "card-claims-push",
274
+ "file": "server.go",
275
+ "find": "Capabilities: a2a.AgentCapabilities{Streaming: true},",
276
+ "replace": "Capabilities: a2a.AgentCapabilities{Streaming: true, PushNotifications: true},"
277
+ },
278
+ {
279
+ "name": "card-ignores-external-url",
280
+ "file": "server.go",
281
+ "find": "\tif s.cfg.ExternalURL != \"\" {\n\t\treturn strings.TrimRight(s.cfg.ExternalURL, \"/\")\n\t}\n",
282
+ "replace": ""
283
+ },
284
+ {
285
+ "name": "card-without-security-requirement",
286
+ "file": "server.go",
287
+ "find": "\t\t\ta2a.SecurityRequirements{\"bearer\": a2a.SecuritySchemeScopes{}},\n",
288
+ "replace": ""
289
+ },
290
+ {
291
+ "name": "plain-http-on-tls",
292
+ "file": "server.go",
293
+ "find": "\t\tln = tls.NewListener(ln, srv.TLSConfig)\n",
294
+ "replace": ""
295
+ },
296
+ {
297
+ "name": "second-start-allowed",
298
+ "file": "server.go",
299
+ "find": "\tif s.srv != nil {\n\t\treturn errors.New(\"a2a: server already started\")\n\t}\n",
300
+ "replace": ""
301
+ },
302
+ {
303
+ "name": "worker-keeps-extensions",
304
+ "file": "worker.go",
305
+ "find": "\"--no-extensions\", ",
306
+ "replace": ""
307
+ },
308
+ {
309
+ "name": "worker-not-marked",
310
+ "file": "worker.go",
311
+ "find": "\treturn append(env, \"PIG_A2A_WORKER=1\", \"PI_SKIP_VERSION_CHECK=1\")",
312
+ "replace": "\treturn append(env, \"PI_SKIP_VERSION_CHECK=1\")"
313
+ },
314
+ {
315
+ "name": "worker-inherits-secrets",
316
+ "file": "worker.go",
317
+ "find": "\t\"PATH\", \"HOME\", \"USER\",",
318
+ "replace": "\t\"TOKEN_A\", \"PATH\", \"HOME\", \"USER\","
319
+ },
320
+ {
321
+ "name": "passenv-ignored",
322
+ "file": "worker.go",
323
+ "find": "\tfor _, name := range append(append([]string{}, baseEnv...), w.cfg.PassEnv...) {",
324
+ "replace": "\tfor _, name := range append([]string{}, baseEnv...) {"
325
+ },
326
+ {
327
+ "name": "session-id-ignores-principal",
328
+ "file": "worker.go",
329
+ "find": "\treturn keyDigest(\"a2a-session\\x00\" + p.Key() + \"\\x00\" + contextID)[:32]",
330
+ "replace": "\treturn keyDigest(\"a2a-session\\x00\" + contextID)[:32]"
331
+ },
332
+ {
333
+ "name": "session-id-ignores-context",
334
+ "file": "worker.go",
335
+ "find": "\treturn keyDigest(\"a2a-session\\x00\" + p.Key() + \"\\x00\" + contextID)[:32]",
336
+ "replace": "\treturn keyDigest(\"a2a-session\\x00\" + p.Key())[:32]"
337
+ },
338
+ {
339
+ "name": "session-dir-shared",
340
+ "file": "worker.go",
341
+ "find": "keyDigest(\"a2a-dir\\x00\" + p.Key())[:16])\n}\n\nfunc (w *ProcessWorker) cwd",
342
+ "replace": "\"shared\")\n}\n\nfunc (w *ProcessWorker) cwd"
343
+ },
344
+ {
345
+ "name": "worker-cwd-not-set",
346
+ "file": "worker.go",
347
+ "find": "\tcmd.Dir, cmd.Env = w.cwd(t.Principal), w.env()",
348
+ "replace": "\tcmd.Env = w.env()"
349
+ },
350
+ {
351
+ "name": "tools-allowlist-dropped",
352
+ "file": "worker.go",
353
+ "find": "\t\targs = append(args, \"--tools\", strings.Join(w.cfg.Tools, \",\"))",
354
+ "replace": "\t\t_ = args"
355
+ },
356
+ {
357
+ "name": "session-not-kept",
358
+ "file": "worker.go",
359
+ "find": "\targs = append(args, \"--session-dir\", w.sessionDir(t.Principal), \"--session-id\", SessionID(t.Principal, t.ContextID))",
360
+ "replace": "\targs = append(args, \"--no-session\")"
361
+ },
362
+ {
363
+ "name": "abort-not-sent",
364
+ "file": "worker.go",
365
+ "find": "\t\t\t_ = write(map[string]any{\"id\": \"abort\", \"type\": \"abort\"})",
366
+ "replace": "\t\t\t_ = 0"
367
+ },
368
+ {
369
+ "name": "no-kill-after-grace",
370
+ "file": "worker.go",
371
+ "find": "\t\t\t\t_ = killTree(cmd.Process)\n\t\t\t\t<-exited",
372
+ "replace": "\t\t\t\t<-exited"
373
+ },
374
+ {
375
+ "name": "no-terminate",
376
+ "file": "worker.go",
377
+ "find": "\t\t\t_ = terminateTree(cmd.Process)\n",
378
+ "replace": ""
379
+ },
380
+ {
381
+ "name": "model-error-text-copied",
382
+ "file": "worker.go",
383
+ "find": "return Result{Text: text.String(), Failure: \"the model call failed\"}, nil",
384
+ "replace": "return Result{Text: text.String(), Failure: \"the model call failed\" + lastStop}, nil"
385
+ },
386
+ {
387
+ "name": "model-error-not-a-failure",
388
+ "file": "worker.go",
389
+ "find": "\t\t\t\tcase \"error\":\n\t\t\t\t\treturn Result{Text: text.String(), Failure: \"the model call failed\"}, nil",
390
+ "replace": "\t\t\t\tcase \"error\":\n\t\t\t\t\treturn Result{Text: text.String()}, nil"
391
+ },
392
+ {
393
+ "name": "streamed-and-final-text-duplicated",
394
+ "file": "worker.go",
395
+ "find": "if f := final.String(); len(f) > len(msgText) && strings.HasPrefix(f, msgText) {\n\t\t\t\t\t\temit(f[len(msgText):])",
396
+ "replace": "if f := final.String(); len(f) > 0 {\n\t\t\t\t\t\temit(f)"
397
+ },
398
+ {
399
+ "name": "empty-prompt-sent",
400
+ "file": "worker.go",
401
+ "find": "\tif strings.TrimSpace(t.Prompt) == \"\" {",
402
+ "replace": "\tif false {"
403
+ },
404
+ {
405
+ "name": "tool-updates-dropped",
406
+ "file": "worker.go",
407
+ "find": "\t\t\t\tif !aborting && ev.ToolName != \"\" {\n\t\t\t\t\tonUpdate(Update{Tool: ev.ToolName})",
408
+ "replace": "\t\t\t\tif false {\n\t\t\t\t\tonUpdate(Update{Tool: ev.ToolName})"
409
+ },
410
+ {
411
+ "name": "ui-request-hangs",
412
+ "file": "worker.go",
413
+ "find": "\t\t\tcase \"extension_ui_request\":\n\t\t\t\tresult.Failure = \"PiG asked for interactive input, which an A2A task cannot provide\"\n\t\t\t\treturn result, runErr",
414
+ "replace": "\t\t\tcase \"extension_ui_request\":"
415
+ },
416
+ {
417
+ "name": "rejected-prompt-ignored",
418
+ "file": "worker.go",
419
+ "find": "\t\t\t\t\tif !ev.Success {\n\t\t\t\t\t\treturn Result{Failure: \"PiG rejected the prompt\"}, nil\n\t\t\t\t\t}",
420
+ "replace": "\t\t\t\t\t_ = ev.Success"
421
+ },
422
+ {
423
+ "name": "any-protocol-version-accepted",
424
+ "file": "client.go",
425
+ "find": "if i.ProtocolBinding != a2a.TransportProtocolJSONRPC || !versionOK(string(i.ProtocolVersion)) {",
426
+ "replace": "if i.ProtocolBinding != a2a.TransportProtocolJSONRPC {"
427
+ },
428
+ {
429
+ "name": "credentials-follow-the-card",
430
+ "file": "client.go",
431
+ "find": "if ra.hasCredentials() && originOf(i.URL) != originOf(ra.URL) {",
432
+ "replace": "if false {"
433
+ },
434
+ {
435
+ "name": "transport-host-not-checked",
436
+ "file": "client.go",
437
+ "find": "\tif req.URL.Host != t.host {",
438
+ "replace": "\tif false {"
439
+ },
440
+ {
441
+ "name": "redirects-followed-with-credentials",
442
+ "file": "client.go",
443
+ "find": "CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }",
444
+ "replace": "CheckRedirect: nil"
445
+ },
446
+ {
447
+ "name": "bearer-not-sent",
448
+ "file": "client.go",
449
+ "find": "\t\th.Set(\"Authorization\", \"Bearer \"+v)",
450
+ "replace": "\t\t_ = v"
451
+ },
452
+ {
453
+ "name": "missing-bearer-env-tolerated",
454
+ "file": "client.go",
455
+ "find": "\t\tif v == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"a2a: remote %q needs the environment variable %s (bearerTokenEnv), which is not set\", name, ra.BearerTokenEnv)\n\t\t}",
456
+ "replace": ""
457
+ },
458
+ {
459
+ "name": "custom-headers-dropped",
460
+ "file": "client.go",
461
+ "find": "\t\th.Set(header, v)",
462
+ "replace": "\t\t_ = v"
463
+ },
464
+ {
465
+ "name": "remote-not-cancelled-on-abort",
466
+ "file": "client.go",
467
+ "find": "if sum.TaskID != \"\" && (caller.Err() != nil || call.Err() != nil) {",
468
+ "replace": "if false {"
469
+ },
470
+ {
471
+ "name": "timeout-ignored",
472
+ "file": "client.go",
473
+ "find": "\treturn context.WithTimeout(ctx, time.Duration(ra.TimeoutSeconds)*time.Second)",
474
+ "replace": "\treturn context.WithCancel(ctx)"
475
+ },
476
+ {
477
+ "name": "unknown-agent-unnamed",
478
+ "file": "client.go",
479
+ "find": "return RemoteAgent{}, fmt.Errorf(\"a2a: unknown agent %q; configured remotes: %s\", name, strings.Join(r.Names(), \", \"))",
480
+ "replace": "return RemoteAgent{}, fmt.Errorf(\"a2a: unknown agent %q\", name)"
481
+ },
482
+ {
483
+ "name": "names-unsorted",
484
+ "file": "client.go",
485
+ "find": "\tsort.Strings(names)\n",
486
+ "replace": "\t_ = sort.Strings\n"
487
+ },
488
+ {
489
+ "name": "empty-message-sent",
490
+ "file": "client.go",
491
+ "find": "\tif strings.TrimSpace(args.Message) == \"\" {",
492
+ "replace": "\tif false {"
493
+ },
494
+ {
495
+ "name": "input-required-called-terminal",
496
+ "file": "client.go",
497
+ "find": "Terminal: t.Status.State.Terminal()},",
498
+ "replace": "Terminal: true},"
499
+ },
500
+ {
501
+ "name": "failure-text-hidden",
502
+ "file": "client.go",
503
+ "find": "\tif statusText != \"\" && state != a2a.TaskStateCompleted {",
504
+ "replace": "\tif false {"
505
+ },
506
+ {
507
+ "name": "auth-error-shows-token-context",
508
+ "file": "client.go",
509
+ "find": "\tcase strings.Contains(err.Error(), \"401\"):",
510
+ "replace": "\tcase false:"
511
+ },
512
+ {
513
+ "name": "switch-drops-listener",
514
+ "file": "extension.go",
515
+ "find": "\t\tcase \"new\", \"resume\", \"fork\":",
516
+ "replace": "\t\tcase \"resume\", \"fork\":"
517
+ },
518
+ {
519
+ "name": "shutdown-keeps-listener",
520
+ "file": "extension.go",
521
+ "find": "\t\tm.stop()\n\t\treturn nil, nil\n\t})",
522
+ "replace": "\t\treturn nil, nil\n\t})"
523
+ },
524
+ {
525
+ "name": "config-error-silent",
526
+ "file": "extension.go",
527
+ "find": "\tcfg, err := m.load(ctx)\n\tif err != nil {\n\t\tctx.Notify(err.Error(), \"error\")\n\t\treturn\n\t}",
528
+ "replace": "\tcfg, err := m.load(ctx)\n\tif err != nil {\n\t\treturn\n\t}"
529
+ },
530
+ {
531
+ "name": "start-failure-silent",
532
+ "file": "extension.go",
533
+ "find": "\tif err != nil {\n\t\tctx.Notify(err.Error(), \"error\")\n\t\treturn\n\t}\n\tm.mu.Lock()\n\tm.server = srv",
534
+ "replace": "\tif err != nil {\n\t\treturn\n\t}\n\tm.mu.Lock()\n\tm.server = srv"
535
+ },
536
+ {
537
+ "name": "flag-not-read",
538
+ "file": "extension.go",
539
+ "find": "\tif v, err := ctx.GetFlag(\"a2a-listen\"); err == nil {",
540
+ "replace": "\tif v, err := ctx.GetFlag(\"a2a-listen\"); false && err == nil {"
541
+ },
542
+ {
543
+ "name": "agent-dir-ignored",
544
+ "file": "extension.go",
545
+ "find": "\tif d := m.getenv(\"PIG_CODING_AGENT_DIR\"); d != \"\" {\n\t\treturn d\n\t}\n",
546
+ "replace": ""
547
+ },
548
+ {
549
+ "name": "stop-does-not-reload-config",
550
+ "file": "extension.go",
551
+ "find": "\tm.remotes, m.cfgErr = nil, nil // the next use reloads the configuration",
552
+ "replace": "\t_ = 0"
553
+ },
554
+ {
555
+ "name": "status-hides-remotes",
556
+ "file": "extension.go",
557
+ "find": "\t\t\tfmt.Fprintf(&b, \"\\nremotes: %s\", strings.Join(names, \", \"))",
558
+ "replace": "\t\t\t_ = names"
559
+ },
560
+ {
561
+ "name": "tool-required-not-enforced",
562
+ "file": "extension.go",
563
+ "find": "\t\tif required {\n\t\t\treturn \"\", sdk.NewToolError(fmt.Sprintf(\"a2a: %s is required\", name))\n\t\t}",
564
+ "replace": "\t\t_ = required"
565
+ },
566
+ {
567
+ "name": "tool-empty-message-allowed",
568
+ "file": "extension.go",
569
+ "find": "\tif required && strings.TrimSpace(s) == \"\" {",
570
+ "replace": "\tif false {"
571
+ },
572
+ {
573
+ "name": "tool-errors-not-errors",
574
+ "file": "extension.go",
575
+ "find": "\t\treturn nil, sdk.NewToolError(\"a2a: no remotes are configured; add a \\\"remotes\\\" object to a2a.json\")",
576
+ "replace": "\t\treturn nil, nil"
577
+ },
578
+ {
579
+ "name": "unfinished-task-not-flagged",
580
+ "file": "extension.go",
581
+ "find": "\tif !s.Terminal {\n\t\tb.WriteString",
582
+ "replace": "\tif false {\n\t\tb.WriteString"
583
+ },
584
+ {
585
+ "name": "task-action-unchecked",
586
+ "file": "extension.go",
587
+ "find": "\treturn nil, sdk.NewToolError(fmt.Sprintf(\"a2a: unknown action %q (get, cancel or list)\", action))",
588
+ "replace": "\treturn \"ok\", nil"
589
+ },
590
+ {
591
+ "name": "send-drops-context",
592
+ "file": "extension.go",
593
+ "find": "SendArgs{Agent: agent, Message: message, ContextID: contextID, TaskID: taskID}",
594
+ "replace": "SendArgs{Agent: agent, Message: message, ContextID: contextID[:0], TaskID: taskID}"
595
+ },
596
+ {
597
+ "name": "tls-load-failure-ignored",
598
+ "file": "server.go",
599
+ "find": "\t\tcert, err := tls.LoadX509KeyPair(s.cfg.TLS.CertFile, s.cfg.TLS.KeyFile)\n\t\tif err != nil {\n\t\t\t_ = ln.Close()\n\t\t\treturn fmt.Errorf(\"a2a: load TLS key pair: %w\", err)\n\t\t}",
600
+ "replace": "\t\tcert, err := tls.LoadX509KeyPair(s.cfg.TLS.CertFile, s.cfg.TLS.KeyFile)\n\t\tif err != nil {\n\t\t\t_ = ln.Close()\n\t\t\treturn nil\n\t\t}"
601
+ },
602
+ {
603
+ "name": "subscribe-ownership-unchecked",
604
+ "file": "server.go",
605
+ "find": "\t\tif _, err := g.store.Get(ctx, sub.ID); err != nil {",
606
+ "replace": "\t\tif _, err := g.store.Get(ctx, sub.ID); false && err != nil {"
607
+ },
608
+ {
609
+ "name": "queued-cancel-ignored-at-slot",
610
+ "file": "executor.go",
611
+ "find": "\t\tcase <-runCtx.Done():\n\t\t\te.finishStopped(ec, r, yield, runCtx)\n\t\t\treturn\n\t\t}\n\t\tunlock, err",
612
+ "replace": "\t\tcase <-time.After(24 * time.Hour):\n\t\t\te.finishStopped(ec, r, yield, runCtx)\n\t\t\treturn\n\t\t}\n\t\tunlock, err"
613
+ },
614
+ {
615
+ "name": "queued-cancel-ignored-at-context-lock",
616
+ "file": "executor.go",
617
+ "find": "\tcase <-ctx.Done():\n\t\trelease()\n\t\treturn nil, ctx.Err()",
618
+ "replace": "\tcase <-time.After(24 * time.Hour):\n\t\trelease()\n\t\treturn nil, ctx.Err()"
619
+ },
620
+ {
621
+ "name": "shutdown-does-not-close-the-port",
622
+ "file": "server.go",
623
+ "find": "\t\t_ = ln.Close()\n\t\t<-served\n",
624
+ "replace": "\t\t_, _ = ln, served\n"
625
+ },
626
+ {
627
+ "name": "insecure-host-unchecked",
628
+ "file": "auth.go",
629
+ "find": "\t\tif a.insecure && !loopbackHostHeader(r.Host) {",
630
+ "replace": "\t\tif false {"
631
+ },
632
+ {
633
+ "name": "transport-scheme-not-checked",
634
+ "file": "client.go",
635
+ "find": "\tif req.URL.Scheme != t.scheme {",
636
+ "replace": "\tif false {"
637
+ },
638
+ {
639
+ "name": "card-scheme-downgrade-accepted",
640
+ "file": "client.go",
641
+ "find": "if ra.hasCredentials() && originOf(i.URL) != originOf(ra.URL) {",
642
+ "replace": "if ra.hasCredentials() && hostOf(i.URL) != hostOf(ra.URL) {"
643
+ },
644
+ {
645
+ "name": "context-lock-shared-across-tenants",
646
+ "file": "executor.go",
647
+ "find": "e.lockContext(runCtx, principal.Key()+\"\\x00\"+ec.ContextID)",
648
+ "replace": "e.lockContext(runCtx, ec.ContextID)"
649
+ },
650
+ {
651
+ "name": "left-behind-child-survives",
652
+ "file": "worker.go",
653
+ "find": "\t\t// A tool the worker started can outlive it.\n\t\t_ = killTree(cmd.Process)\n",
654
+ "replace": ""
655
+ }
656
+ ]