copilot-tap-extension 2.0.7 → 2.0.9
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/README.md +4 -1
- package/SOUL.md +51 -0
- package/bin/install.mjs +7 -1
- package/dist/copilot-instructions.md +15 -0
- package/dist/extension.mjs +823 -29
- package/dist/skills/tap-goal/SKILL.md +13 -2
- package/dist/skills/tap-loop/SKILL.md +6 -0
- package/dist/skills/tap-monitor/SKILL.md +19 -3
- package/dist/skills/tap-orchestrate/SKILL.md +81 -0
- package/dist/version.json +1 -1
- package/docs/adr/0001-persistent-config-default-ownership.md +33 -0
- package/docs/adr/0002-local-provider-gateway-runtime-security.md +36 -0
- package/docs/adr/0003-emitter-delivery-lifecycle.md +68 -0
- package/docs/adr/0004-persistent-config-canonical-streams.md +86 -0
- package/docs/adr/0005-provider-sdk-push-and-dynamic-tools.md +48 -0
- package/docs/adr/0006-command-emitter-cwd-workspace-boundary.md +46 -0
- package/docs/adr/0007-runtime-session-workspace-context.md +62 -0
- package/docs/evals.md +41 -0
- package/docs/evolution-of-tap-icon.html +989 -0
- package/docs/providers.md +242 -0
- package/docs/recipes/adaptive-agent.md +303 -0
- package/docs/recipes/agent-brainstorm/100-extension-ideas.md +288 -0
- package/docs/recipes/agent-brainstorm/deep-ideas.md +216 -0
- package/docs/recipes/ambient-guardian.md +314 -0
- package/docs/recipes/browser-bridge.md +162 -0
- package/docs/recipes/codex-goals-for-tap-goal.md +136 -0
- package/docs/recipes/copilot-sdk-canvas.md +147 -0
- package/docs/recipes/deferred-cognition.md +310 -0
- package/docs/recipes/provider-integration-patterns.md +93 -0
- package/docs/recipes/provider-interface-advanced.md +1364 -0
- package/docs/recipes/provider-interface-core-profile.md +568 -0
- package/docs/recipes/tap-control-plane-roadmap.md +60 -0
- package/docs/recipes/universal-tool-gateway.md +202 -0
- package/docs/reference.md +229 -0
- package/docs/use-cases.md +348 -0
- package/package.json +4 -1
- package/providers/detour/README.md +84 -0
- package/providers/detour/bridge.js +219 -0
- package/providers/detour/index.mjs +322 -0
- package/providers/detour/package-lock.json +577 -0
- package/providers/detour/package.json +19 -0
- package/providers/detour/scripts/build.mjs +31 -0
- package/providers/detour/src/bridge.js +256 -0
- package/providers/detour/src/contracts.js +40 -0
- package/providers/detour/src/inspector.js +260 -0
- package/providers/detour/src/inspector.test.mjs +53 -0
- package/providers/detour/src/panel.js +465 -0
- package/providers/detour/src/provider-core.js +233 -0
- package/providers/detour/src/provider-core.test.mjs +185 -0
- package/providers/detour/src/react-context-core.js +143 -0
- package/providers/detour/src/react-context.js +44 -0
- package/providers/detour/src/react-context.test.mjs +41 -0
- package/providers/templates/README.md +23 -0
- package/providers/templates/ci-review-provider.mjs +46 -0
- package/providers/templates/detour-workflow-provider.mjs +41 -0
- package/providers/templates/jira-github-provider.mjs +42 -0
- package/providers/templates/provider-utils.mjs +45 -0
- package/providers/templates/sast-triage-provider.mjs +51 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# Copilot SDK canvas surfaces
|
|
2
|
+
|
|
3
|
+
These notes reflect the canvas API surface found in the local Copilot CLI 1.0.61 SDK. The canvas APIs are marked experimental in the SDK types, so treat exact names and runtime behavior as subject to change.
|
|
4
|
+
|
|
5
|
+
## What a canvas is
|
|
6
|
+
|
|
7
|
+
A canvas is an extension-owned UI surface declared through the Copilot SDK:
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
import { joinSession, createCanvas } from "@github/copilot-sdk/extension";
|
|
11
|
+
|
|
12
|
+
await joinSession({
|
|
13
|
+
canvases: [
|
|
14
|
+
createCanvas({
|
|
15
|
+
id: "tap-dashboard",
|
|
16
|
+
displayName: "Tap Dashboard",
|
|
17
|
+
description: "Inspect live tap streams and emitter state.",
|
|
18
|
+
open: async (ctx) => ({
|
|
19
|
+
title: "Tap Dashboard",
|
|
20
|
+
status: `Instance ${ctx.instanceId}`,
|
|
21
|
+
url: "http://127.0.0.1:5173/",
|
|
22
|
+
}),
|
|
23
|
+
}),
|
|
24
|
+
],
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The extension process is the live provider for that canvas. The runtime routes `canvas.open`, `canvas.close`, and `canvas.action.invoke` requests back into the SDK process, and the SDK dispatches them to the handlers bound by `createCanvas`.
|
|
29
|
+
|
|
30
|
+
Use canvases when a workflow needs a persistent visual panel, inspector, or control surface instead of plain text tool output. Examples that fit tap well: stream dashboards, emitter graphs, PR-review status boards, browser-debug panels, and interactive incident timelines.
|
|
31
|
+
|
|
32
|
+
## Declaration shape
|
|
33
|
+
|
|
34
|
+
`createCanvas(options)` returns a `Canvas` that can be passed in `joinSession({ canvases })`.
|
|
35
|
+
|
|
36
|
+
| Field | Required | Notes |
|
|
37
|
+
| --- | --- | --- |
|
|
38
|
+
| `id` | yes | Provider-local canvas id, unique within the declaring extension connection. |
|
|
39
|
+
| `displayName` | yes | Human-readable name shown in discovery/UI chrome. |
|
|
40
|
+
| `description` | yes | Short single-sentence description shown to the agent. |
|
|
41
|
+
| `inputSchema` | no | JSON Schema for the `open` input payload. |
|
|
42
|
+
| `actions` | no | Agent/host-invocable actions for an open instance. |
|
|
43
|
+
| `open(ctx)` | yes | Called when the host or agent opens/focuses an instance. |
|
|
44
|
+
| `onClose(ctx)` | no | Called when an instance is closed; use it to release resources. |
|
|
45
|
+
|
|
46
|
+
Action names are unique within the canvas and must not start with `canvas.` because that prefix is reserved for lifecycle verbs.
|
|
47
|
+
|
|
48
|
+
## Open lifecycle
|
|
49
|
+
|
|
50
|
+
The host or agent opens a canvas with a `canvasId`, stable caller-supplied `instanceId`, and optional `extensionId` when multiple providers declare the same `canvasId`. The SDK calls:
|
|
51
|
+
|
|
52
|
+
```js
|
|
53
|
+
open: async ({ sessionId, extensionId, canvasId, instanceId, input, host }) => {
|
|
54
|
+
return {
|
|
55
|
+
title: "Rendered title",
|
|
56
|
+
status: "ready",
|
|
57
|
+
url: "http://127.0.0.1:49152/",
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The response may include:
|
|
63
|
+
|
|
64
|
+
| Field | Meaning |
|
|
65
|
+
| --- | --- |
|
|
66
|
+
| `url` | Web renderer URL for the host to render. Optional for native canvases. |
|
|
67
|
+
| `title` | Title shown in host chrome. |
|
|
68
|
+
| `status` | Provider-supplied status text shown in host chrome. |
|
|
69
|
+
|
|
70
|
+
Re-opening the same `instanceId` is idempotent and focuses/reuses the existing panel. Open snapshots and `session.canvas.opened` events include `reopen: true` when the notification represents such a reopen.
|
|
71
|
+
|
|
72
|
+
The CLI canvas scaffold uses a practical default for web canvases: start a loopback HTTP server on port `0`, let the OS choose a free ephemeral port, keep one server per `instanceId`, and close that server from `onClose` so ports do not leak.
|
|
73
|
+
|
|
74
|
+
## Actions
|
|
75
|
+
|
|
76
|
+
Canvas actions let the agent or host interact with an already-open instance:
|
|
77
|
+
|
|
78
|
+
```js
|
|
79
|
+
createCanvas({
|
|
80
|
+
id: "tap-dashboard",
|
|
81
|
+
displayName: "Tap Dashboard",
|
|
82
|
+
description: "Inspect live tap streams and emitter state.",
|
|
83
|
+
actions: [
|
|
84
|
+
{
|
|
85
|
+
name: "refresh_stream",
|
|
86
|
+
description: "Refresh the stream snapshot shown in the canvas.",
|
|
87
|
+
inputSchema: {
|
|
88
|
+
type: "object",
|
|
89
|
+
properties: {
|
|
90
|
+
limit: { type: "integer", minimum: 1, maximum: 100 },
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
handler: async ({ instanceId, input }) => {
|
|
94
|
+
return { ok: true, instanceId, limit: input?.limit ?? 25 };
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
open: async () => ({ title: "Tap Dashboard", url: "http://127.0.0.1:5173/" }),
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
At the model/tool layer, actions are discovered through `list_canvas_capabilities` and invoked through `invoke_canvas_action`. At the SDK RPC layer, renderer-capable clients can call `session.rpc.canvas.invokeAction(...)`.
|
|
103
|
+
|
|
104
|
+
## Renderer and RPC APIs
|
|
105
|
+
|
|
106
|
+
The SDK exposes an experimental `session.rpc.canvas` API for SDK clients that can render or coordinate canvases:
|
|
107
|
+
|
|
108
|
+
| Method | Purpose |
|
|
109
|
+
| --- | --- |
|
|
110
|
+
| `list()` | List canvas declarations available in the session. |
|
|
111
|
+
| `listOpen()` | List currently open canvas instances. |
|
|
112
|
+
| `open({ canvasId, instanceId, input, extensionId? })` | Open or focus an instance. |
|
|
113
|
+
| `close({ instanceId })` | Close an open instance. |
|
|
114
|
+
| `invokeAction({ instanceId, actionName, input })` | Invoke an action on an open instance. |
|
|
115
|
+
|
|
116
|
+
Set `requestCanvasRenderer: true` only for SDK session clients that can display canvases. That opt-in surfaces the model tools `list_canvas_capabilities`, `open_canvas`, and `invoke_canvas_action`. Extension canvases generated by `extensions_manage({ kind: "canvas" })` only declare `canvases`; they do not set `requestCanvasRenderer`.
|
|
117
|
+
|
|
118
|
+
For resumed SDK sessions, `openCanvases` can be supplied with the prior open-instance snapshot so the runtime can rehydrate canvas state without re-opening everything manually.
|
|
119
|
+
|
|
120
|
+
## Events and capability signals
|
|
121
|
+
|
|
122
|
+
Canvas-related session events are transient (`ephemeral: true`):
|
|
123
|
+
|
|
124
|
+
| Event | Key payload |
|
|
125
|
+
| --- | --- |
|
|
126
|
+
| `capabilities.changed` | `data.ui.canvases` indicates whether canvas rendering is supported. |
|
|
127
|
+
| `session.canvas.registry_changed` | Current canvas declarations: ids, display names, descriptions, input schemas, and actions. |
|
|
128
|
+
| `session.canvas.opened` | Open instance snapshot: `instanceId`, `canvasId`, `extensionId`, `url`, `title`, `status`, `input`, `reopen`, and `availability`. |
|
|
129
|
+
|
|
130
|
+
Open instances report `availability: "ready"` while the provider connection is live and `"stale"` if the provider has gone away. In stale/unavailable cases, action routing can fail until the agent re-opens the canvas or the provider reconnects.
|
|
131
|
+
|
|
132
|
+
## Tap integration notes
|
|
133
|
+
|
|
134
|
+
Tap's external provider protocol is intentionally not the Copilot SDK. External tap providers can register tools, update tools, and push/keep/surface/inject events, but they cannot declare Copilot SDK canvases over the WebSocket provider protocol today.
|
|
135
|
+
|
|
136
|
+
To add a canvas-backed tap experience, implement the canvas in the tap extension layer with `createCanvas`, or add an explicit gateway/protocol extension that maps provider UI declarations into SDK canvases. Keep provider-side browser or local services behind loopback URLs and pass only the URL/title/status through the canvas `open` response.
|
|
137
|
+
|
|
138
|
+
Tap now includes a built-in example: the `tap-diagnostics` canvas. It is declared by the extension and can be opened with `tap_open_diagnostics_canvas`. The canvas serves a loopback-only renderer and streams bounded diagnostics snapshots over SSE.
|
|
139
|
+
|
|
140
|
+
## Best practices
|
|
141
|
+
|
|
142
|
+
1. Keep renderer servers on loopback (`127.0.0.1`) and use ephemeral ports unless the port is user-configured.
|
|
143
|
+
2. Key per-instance resources by `instanceId` and release them in `onClose`.
|
|
144
|
+
3. Validate `open` and action inputs with JSON Schema; avoid broad casts or unstructured payloads.
|
|
145
|
+
4. Keep action names stable, descriptive, and free of the reserved `canvas.` prefix.
|
|
146
|
+
5. Treat all canvas APIs as experimental and guard optional host capabilities such as `host?.capabilities?.canvases`.
|
|
147
|
+
6. Use `session.log()` for extension diagnostics; do not write diagnostics to stdout because stdout is reserved for JSON-RPC.
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
# Recipe: Deferred Cognition — The AI Schedules Work for Its Future Self
|
|
2
|
+
|
|
3
|
+
## The insight
|
|
4
|
+
|
|
5
|
+
You're investigating a problem. You hit a wall — the data you need doesn't exist yet (logs rotate at midnight, a deploy hasn't finished, a test suite is running). Today you'd set a calendar reminder, context-switch, and come back later having forgotten half of what you figured out.
|
|
6
|
+
|
|
7
|
+
Deferred cognition means the AI captures its current investigation state — hypothesis, evidence gathered, what was ruled out, what to check next — and creates a persistent PromptEmitter that fires on the next session start. When you open Copilot tomorrow, it picks up mid-thought.
|
|
8
|
+
|
|
9
|
+
This isn't session resumption (which replays the conversation). It's the AI **deliberately planning future work**, with a specific prompt about what to do when it wakes up.
|
|
10
|
+
|
|
11
|
+
## Why skills can't do this
|
|
12
|
+
|
|
13
|
+
1. A skill can't write itself. Deferred cognition generates a prompt at runtime based on the current investigation state.
|
|
14
|
+
2. A skill can't schedule itself. The persistent PromptEmitter fires automatically on next session start.
|
|
15
|
+
3. A skill doesn't carry state. The deferred prompt includes specific hypothesis, file paths, line numbers, evidence collected — all from the current session.
|
|
16
|
+
4. A skill can't span sessions. This bridges the gap between "what I know now" and "what I need to do later."
|
|
17
|
+
|
|
18
|
+
## Architecture
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
Session N (today)
|
|
22
|
+
│
|
|
23
|
+
▼
|
|
24
|
+
You say: "check the rotated logs tomorrow and continue"
|
|
25
|
+
│
|
|
26
|
+
▼
|
|
27
|
+
tap captures current investigation state:
|
|
28
|
+
• Files examined (from onPostToolUse history)
|
|
29
|
+
• Hypothesis (from assistant.message history)
|
|
30
|
+
• What was ruled out (from conversation)
|
|
31
|
+
• What to check next (from your instruction)
|
|
32
|
+
│
|
|
33
|
+
▼
|
|
34
|
+
Creates persistent PromptEmitter:
|
|
35
|
+
name: "deferred-investigation-auth-timeout"
|
|
36
|
+
schedule: oneTime (fires once on next session start)
|
|
37
|
+
autoStart: true
|
|
38
|
+
prompt: <generated investigation prompt>
|
|
39
|
+
│
|
|
40
|
+
▼
|
|
41
|
+
Saved to tap.config.json
|
|
42
|
+
|
|
43
|
+
═══════════════════════════════════════════════
|
|
44
|
+
|
|
45
|
+
Session N+1 (tomorrow)
|
|
46
|
+
│
|
|
47
|
+
▼
|
|
48
|
+
onSessionStart → persistent emitters auto-start
|
|
49
|
+
│
|
|
50
|
+
▼
|
|
51
|
+
PromptEmitter fires immediately:
|
|
52
|
+
"Continue investigating the auth-timeout issue from yesterday.
|
|
53
|
+
|
|
54
|
+
State from previous session:
|
|
55
|
+
- Hypothesis: Connection pool exhaustion in auth-service
|
|
56
|
+
- Evidence: Error rate correlates with batch job at 23:45
|
|
57
|
+
- Checked: src/auth/pool.ts (pool size is 10, seems low)
|
|
58
|
+
- Ruled out: DNS resolution (dig shows normal latency)
|
|
59
|
+
- Next step: Check the rotated logs at /var/log/auth/
|
|
60
|
+
for connection refused errors between 23:45-00:15
|
|
61
|
+
- Also check: Has the batch job's connection count changed
|
|
62
|
+
since PR #289 merged last Tuesday?
|
|
63
|
+
|
|
64
|
+
Read the relevant files, check the logs, and report
|
|
65
|
+
what you find."
|
|
66
|
+
│
|
|
67
|
+
▼
|
|
68
|
+
Copilot picks up the investigation mid-thought.
|
|
69
|
+
The PromptEmitter is oneTime → auto-removes after firing.
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Components
|
|
73
|
+
|
|
74
|
+
### 1. State capture tool
|
|
75
|
+
|
|
76
|
+
A new tap tool that captures the current investigation state:
|
|
77
|
+
|
|
78
|
+
```js
|
|
79
|
+
{
|
|
80
|
+
name: "tap_defer",
|
|
81
|
+
description:
|
|
82
|
+
"Defer work to a future session. Captures the current investigation " +
|
|
83
|
+
"state and creates a persistent prompt that fires on next session start. " +
|
|
84
|
+
"Use when the user wants to continue later, wait for something, or " +
|
|
85
|
+
"schedule future work.",
|
|
86
|
+
parameters: {
|
|
87
|
+
type: "object",
|
|
88
|
+
properties: {
|
|
89
|
+
what: {
|
|
90
|
+
type: "string",
|
|
91
|
+
description:
|
|
92
|
+
"What to do in the future session — the specific task or check"
|
|
93
|
+
},
|
|
94
|
+
when: {
|
|
95
|
+
type: "string",
|
|
96
|
+
description:
|
|
97
|
+
"When to fire: 'next-session' (default), or a delay like '6h', '1d'"
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
required: ["what"]
|
|
101
|
+
},
|
|
102
|
+
handler: async ({ what, when }, invocation) => {
|
|
103
|
+
// Gather context from the current session
|
|
104
|
+
const sessionHistory = await session.getMessages();
|
|
105
|
+
const recentFiles = extractRecentFiles(sessionHistory);
|
|
106
|
+
const hypothesis = extractHypothesis(sessionHistory);
|
|
107
|
+
const ruledOut = extractRuledOut(sessionHistory);
|
|
108
|
+
|
|
109
|
+
// Generate the deferred prompt
|
|
110
|
+
const deferredPrompt = await generateDeferredPrompt({
|
|
111
|
+
task: what,
|
|
112
|
+
files: recentFiles,
|
|
113
|
+
hypothesis,
|
|
114
|
+
ruledOut,
|
|
115
|
+
sessionTranscriptSummary: summarizeRecent(sessionHistory, 20)
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Create a persistent one-time emitter
|
|
119
|
+
const emitterName = `deferred-${normalizeName(what).slice(0, 30)}`;
|
|
120
|
+
await supervisor.start({
|
|
121
|
+
name: emitterName,
|
|
122
|
+
prompt: deferredPrompt,
|
|
123
|
+
scope: "persistent",
|
|
124
|
+
runSchedule: "oneTime",
|
|
125
|
+
autoStart: true,
|
|
126
|
+
managedBy: "modelOwned"
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
return `Deferred to ${when || "next session"}: "${what}"\n` +
|
|
130
|
+
`Emitter '${emitterName}' will fire on next session start ` +
|
|
131
|
+
`with full investigation context.`;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### 2. Prompt generation
|
|
137
|
+
|
|
138
|
+
The deferred prompt is generated by a PromptEmitter that summarizes the current session:
|
|
139
|
+
|
|
140
|
+
```js
|
|
141
|
+
async function generateDeferredPrompt({ task, files, hypothesis, ruledOut, sessionTranscriptSummary }) {
|
|
142
|
+
const prompt = `Continue this investigation from a previous session.
|
|
143
|
+
|
|
144
|
+
## Task
|
|
145
|
+
${task}
|
|
146
|
+
|
|
147
|
+
## State from previous session
|
|
148
|
+
${hypothesis ? `- Hypothesis: ${hypothesis}` : ""}
|
|
149
|
+
${files.length ? `- Files examined: ${files.join(", ")}` : ""}
|
|
150
|
+
${ruledOut.length ? `- Ruled out: ${ruledOut.join("; ")}` : ""}
|
|
151
|
+
|
|
152
|
+
## Previous session summary
|
|
153
|
+
${sessionTranscriptSummary}
|
|
154
|
+
|
|
155
|
+
## Instructions
|
|
156
|
+
Pick up where this left off. Read the relevant files, perform the
|
|
157
|
+
check described in the task, and report findings. If the hypothesis
|
|
158
|
+
is confirmed, suggest a fix. If not, update the hypothesis.`;
|
|
159
|
+
|
|
160
|
+
return prompt;
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### 3. Auto-cleanup
|
|
165
|
+
|
|
166
|
+
The emitter is `oneTime` with `autoStart: true`. After it fires:
|
|
167
|
+
- The PromptEmitter runs once on session start
|
|
168
|
+
- Copilot processes it and responds
|
|
169
|
+
- The emitter marks itself completed and removes from persistent config
|
|
170
|
+
- No cleanup needed
|
|
171
|
+
|
|
172
|
+
### 4. Deferred chain (optional)
|
|
173
|
+
|
|
174
|
+
If the future session also hits a wall, the AI can defer again:
|
|
175
|
+
|
|
176
|
+
```
|
|
177
|
+
Session N: "check logs tomorrow" → deferred
|
|
178
|
+
Session N+1: checks logs, finds a clue, needs metric data that's being aggregated
|
|
179
|
+
→ "check the hourly aggregation after it runs at 6am" → deferred again
|
|
180
|
+
Session N+2: checks aggregation, finds root cause, suggests fix
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Each deferral carries forward the accumulated context from all previous sessions. The investigation builds across days without losing state.
|
|
184
|
+
|
|
185
|
+
## Use cases
|
|
186
|
+
|
|
187
|
+
### "Wait for the deploy to finish"
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
> I need to check if the new config works in production.
|
|
191
|
+
> The deploy won't finish for another 20 minutes and
|
|
192
|
+
> I need to switch to something else.
|
|
193
|
+
|
|
194
|
+
Copilot calls tap_defer({
|
|
195
|
+
what: "Check if auth-service in production is using the new
|
|
196
|
+
Redis connection pool settings from PR #312. Verify
|
|
197
|
+
by checking the pod env vars and the connection count
|
|
198
|
+
in the Redis dashboard.",
|
|
199
|
+
when: "next-session"
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
> Deferred to next session. I'll check the deploy results
|
|
203
|
+
> when you come back.
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### "Monitor overnight"
|
|
207
|
+
|
|
208
|
+
```
|
|
209
|
+
> The memory leak reproduces after ~8 hours of uptime.
|
|
210
|
+
> The service was restarted at 4pm. Defer checking the
|
|
211
|
+
> heap stats to tomorrow morning.
|
|
212
|
+
|
|
213
|
+
Copilot calls tap_defer({
|
|
214
|
+
what: "Check heap memory stats for auth-service. It was
|
|
215
|
+
restarted at 4pm yesterday. If uptime is now ~16h,
|
|
216
|
+
check if RSS has grown linearly. Compare to the
|
|
217
|
+
baseline of 512MB at startup. Check /debug/pprof/heap
|
|
218
|
+
if available.",
|
|
219
|
+
when: "next-session"
|
|
220
|
+
})
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### "Continue this research"
|
|
224
|
+
|
|
225
|
+
```
|
|
226
|
+
> I'm evaluating whether to migrate from Express to Fastify.
|
|
227
|
+
> I've compared routing and middleware so far. Defer the
|
|
228
|
+
> rest — I need to check plugin ecosystem and benchmarks.
|
|
229
|
+
|
|
230
|
+
Copilot calls tap_defer({
|
|
231
|
+
what: "Continue the Express→Fastify migration evaluation.
|
|
232
|
+
Already compared: routing (Fastify wins on speed),
|
|
233
|
+
middleware (Express has more ecosystem).
|
|
234
|
+
Still need to evaluate: plugin compatibility for
|
|
235
|
+
our 12 Express middlewares, benchmark with our
|
|
236
|
+
actual API routes, and migration effort estimate.",
|
|
237
|
+
when: "next-session"
|
|
238
|
+
})
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Protocol
|
|
242
|
+
|
|
243
|
+
### Deferred emitter in tap.config.json
|
|
244
|
+
|
|
245
|
+
```json
|
|
246
|
+
{
|
|
247
|
+
"emitters": [
|
|
248
|
+
{
|
|
249
|
+
"name": "deferred-check-auth-deploy",
|
|
250
|
+
"prompt": "Continue investigating the auth-timeout issue...",
|
|
251
|
+
"runSchedule": "oneTime",
|
|
252
|
+
"autoStart": true,
|
|
253
|
+
"ownership": "modelOwned",
|
|
254
|
+
"lifespan": "persistent",
|
|
255
|
+
"metadata": {
|
|
256
|
+
"deferredAt": "2026-04-26T14:30:00Z",
|
|
257
|
+
"deferredFrom": "session-abc123",
|
|
258
|
+
"task": "Check if auth-service is using new Redis pool settings"
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
]
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Lifecycle
|
|
266
|
+
|
|
267
|
+
```
|
|
268
|
+
tap_defer() called
|
|
269
|
+
│
|
|
270
|
+
▼
|
|
271
|
+
Generate prompt from session state
|
|
272
|
+
│
|
|
273
|
+
▼
|
|
274
|
+
Write to tap.config.json as persistent oneTime emitter
|
|
275
|
+
│
|
|
276
|
+
▼
|
|
277
|
+
Session ends normally
|
|
278
|
+
│
|
|
279
|
+
═══ time passes ═══
|
|
280
|
+
│
|
|
281
|
+
▼
|
|
282
|
+
New session starts → onSessionStart loads persistent config
|
|
283
|
+
│
|
|
284
|
+
▼
|
|
285
|
+
Deferred emitter fires (session.send with the prompt)
|
|
286
|
+
│
|
|
287
|
+
▼
|
|
288
|
+
Copilot processes the deferred work
|
|
289
|
+
│
|
|
290
|
+
▼
|
|
291
|
+
Emitter completes → removed from persistent config
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
## Phased delivery
|
|
295
|
+
|
|
296
|
+
| Phase | Scope |
|
|
297
|
+
|---|---|
|
|
298
|
+
| **1. tap_defer tool** | Capture task + basic context, create persistent oneTime emitter |
|
|
299
|
+
| **2. State extraction** | Use session.getMessages() to extract hypothesis, files, ruled-out from conversation |
|
|
300
|
+
| **3. Prompt generation** | PromptEmitter that distills session into a rich deferred prompt |
|
|
301
|
+
| **4. Deferred chains** | Carry forward accumulated context across multiple deferrals |
|
|
302
|
+
| **5. Scheduled deferral** | Support `when: "6h"` with delayed autoStart (timed persistent emitter) |
|
|
303
|
+
|
|
304
|
+
## Open questions
|
|
305
|
+
|
|
306
|
+
- **Context budget** — how much session history to include in the deferred prompt? Too little loses context, too much wastes tokens.
|
|
307
|
+
- **Relevance decay** — a deferral from 2 weeks ago may no longer be relevant. Auto-expire?
|
|
308
|
+
- **Multiple deferrals** — what if you defer 5 things? Queue them? Fire all at session start?
|
|
309
|
+
- **User confirmation** — should deferred prompts fire silently or announce themselves? "You deferred 2 tasks from yesterday. Running them now."
|
|
310
|
+
- **Cross-repo** — deferred work is stored in tap.config.json which is repo-scoped. What about repo-independent deferrals?
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Provider integration patterns
|
|
2
|
+
|
|
3
|
+
These recipes turn external workflow systems into tap-operable signals and tools.
|
|
4
|
+
They are intentionally provider-shaped: each integration exposes a small local
|
|
5
|
+
service or WebSocket provider, then tap uses EventStreams, EventFilters, goals,
|
|
6
|
+
and diagnostics to keep the agent connected to the workflow.
|
|
7
|
+
|
|
8
|
+
## Shared shape
|
|
9
|
+
|
|
10
|
+
1. Provider authenticates to the external system.
|
|
11
|
+
2. Provider registers focused tools through the tap provider gateway.
|
|
12
|
+
3. Provider exposes or emits normalized events with stable fields.
|
|
13
|
+
4. tap filters events into `keep`, `surface`, or `inject`.
|
|
14
|
+
5. High-value events can start or steer a `/tap-goal` or `/tap-orchestrate`
|
|
15
|
+
workflow.
|
|
16
|
+
|
|
17
|
+
Prefer structured JSON lines for provider output:
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{"type":"ci.failure","repo":"owner/name","runUrl":"...","branch":"main","severity":"high"}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
This makes EventFilter rules stable and auditable.
|
|
24
|
+
|
|
25
|
+
Dependency-free template providers live in `providers/templates/`:
|
|
26
|
+
|
|
27
|
+
- `ci-review-provider.mjs`
|
|
28
|
+
- `jira-github-provider.mjs`
|
|
29
|
+
- `sast-triage-provider.mjs`
|
|
30
|
+
- `detour-workflow-provider.mjs`
|
|
31
|
+
|
|
32
|
+
## Jira + GitHub
|
|
33
|
+
|
|
34
|
+
Inspired by the Codex Jira/GitHub automation pattern:
|
|
35
|
+
|
|
36
|
+
- Jira label or automation rule triggers a provider event.
|
|
37
|
+
- Provider tools:
|
|
38
|
+
- `jira_get_issue`
|
|
39
|
+
- `jira_transition_issue`
|
|
40
|
+
- `jira_post_comment`
|
|
41
|
+
- `github_create_pr`
|
|
42
|
+
- A goal completes only when the EventStream ledger includes:
|
|
43
|
+
- Jira issue key
|
|
44
|
+
- branch name
|
|
45
|
+
- commit SHA
|
|
46
|
+
- PR URL
|
|
47
|
+
- Jira status transition
|
|
48
|
+
|
|
49
|
+
## CI auto-fix
|
|
50
|
+
|
|
51
|
+
Inspired by the Codex GitHub Actions auto-fix pattern:
|
|
52
|
+
|
|
53
|
+
- CommandEmitter or provider watches failed workflow runs.
|
|
54
|
+
- Failure events surface or inject with run URL and failing job.
|
|
55
|
+
- A repair goal uses the failing output as the verification surface.
|
|
56
|
+
- Completion requires a successful verification command and a traceable branch
|
|
57
|
+
or PR.
|
|
58
|
+
|
|
59
|
+
## Code review
|
|
60
|
+
|
|
61
|
+
Inspired by the Codex SDK code-review pattern:
|
|
62
|
+
|
|
63
|
+
- Provider or skill runs a structured review command.
|
|
64
|
+
- Findings use stable fields:
|
|
65
|
+
- title
|
|
66
|
+
- body
|
|
67
|
+
- confidence score
|
|
68
|
+
- priority
|
|
69
|
+
- file path
|
|
70
|
+
- line range
|
|
71
|
+
- P0/P1 findings should inject; P2/P3 findings should surface or keep.
|
|
72
|
+
|
|
73
|
+
## SAST triage
|
|
74
|
+
|
|
75
|
+
Inspired by the GitLab security-quality pattern:
|
|
76
|
+
|
|
77
|
+
- Ingest SAST JSON as structured provider events.
|
|
78
|
+
- Deduplicate by `(CWE, sink/function, file:line)`.
|
|
79
|
+
- Rank by exploitability and business risk.
|
|
80
|
+
- Process one finding per goal iteration.
|
|
81
|
+
- Completion requires either a validated patch or an explicit blocked reason.
|
|
82
|
+
|
|
83
|
+
## Browser / Detour workflows
|
|
84
|
+
|
|
85
|
+
Use Detour for browser-page instrumentation and tap for agent-side orchestration:
|
|
86
|
+
|
|
87
|
+
- Detour injects browser bridge code.
|
|
88
|
+
- Provider exposes a local API for page events.
|
|
89
|
+
- CommandEmitter polls the provider and normalizes events.
|
|
90
|
+
- tap goals or monitors react to stable event types.
|
|
91
|
+
|
|
92
|
+
Do not mutate Detour source for tap-specific workflows; use injectable scripts
|
|
93
|
+
and provider-side adapters.
|