copilot-tap-extension 2.0.4 → 2.0.6
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 +10 -0
- package/bin/install.mjs +148 -72
- package/dist/copilot-instructions.md +5 -0
- package/dist/extension.mjs +10931 -10892
- package/dist/skills/tap-monitor/SKILL.md +7 -7
- package/dist/version.json +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -30,6 +30,7 @@ Background commands and agent prompts produce output line by line. An EventFilte
|
|
|
30
30
|
- You poll an API or dashboard and want the agent to react when something changes.
|
|
31
31
|
- You re-ask the same prompt periodically and want it on a timer or running whenever idle.
|
|
32
32
|
- You build external tools in any language and want them available inside Copilot without touching the SDK.
|
|
33
|
+
- You want a live visual flight recorder for tap streams, emitters, provider state, logs, and session events.
|
|
33
34
|
|
|
34
35
|
## Get started
|
|
35
36
|
|
|
@@ -115,6 +116,8 @@ Once inside the session, describe what you want in natural language. You can als
|
|
|
115
116
|
|
|
116
117
|
> _"Tail the API logs, inject errors, drop health checks"_
|
|
117
118
|
|
|
119
|
+
> _"Open the tap diagnostics canvas"_
|
|
120
|
+
|
|
118
121
|
The agent translates these into emitter and filter configurations behind the scenes.
|
|
119
122
|
|
|
120
123
|
## How it works
|
|
@@ -239,6 +242,12 @@ The recommended approach is a **keep-all bootstrap**: start with no EventFilter
|
|
|
239
242
|
|
|
240
243
|
Rules can be added or changed while the emitter is running. You never need to restart it to adjust filtering.
|
|
241
244
|
|
|
245
|
+
**Inspect tap with a live diagnostics canvas**
|
|
246
|
+
|
|
247
|
+
Use the `tap_open_diagnostics_canvas` tool to open a local canvas that shows tap's retained EventStreams, emitter state, provider gateway state, injection queue, runtime logs, and recent session events in one place.
|
|
248
|
+
|
|
249
|
+
The canvas is bounded and redacted: it keeps recent diagnostic evidence without exposing provider auth tokens or unbounded transcript payloads.
|
|
250
|
+
|
|
242
251
|
## Repo layout
|
|
243
252
|
|
|
244
253
|
```text
|
|
@@ -276,6 +285,7 @@ PLAN.md # ubiquitous language and design decisions
|
|
|
276
285
|
| [Reference](./docs/reference.md) | Look up tool parameters, config fields, or the event pipeline |
|
|
277
286
|
| [Provider guide](./docs/providers.md) | Add external tools to Copilot via the WebSocket provider interface |
|
|
278
287
|
| [Use cases and patterns](./docs/use-cases.md) | Recipes for deploy watchers, PR monitors, log tailers, and more |
|
|
288
|
+
| [Copilot SDK canvas surfaces](./docs/recipes/copilot-sdk-canvas.md) | Local SDK findings for extension-owned canvas UI surfaces |
|
|
279
289
|
| [Evals](./docs/evals.md) | Run or extend the automated test suite |
|
|
280
290
|
| [Copilot instructions](./src/copilot-instructions.md) | Understand or customize how the agent uses this extension |
|
|
281
291
|
| [Implementation plan](./PLAN.md) | Ubiquitous language and naming conventions for contributors |
|
package/bin/install.mjs
CHANGED
|
@@ -47,37 +47,38 @@ Installs:
|
|
|
47
47
|
`);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
const OPTION_ACTIONS = new Map([
|
|
51
|
+
["--global", (flags) => { flags.scope = "global"; }],
|
|
52
|
+
["-g", (flags) => { flags.scope = "global"; }],
|
|
53
|
+
["--local", (flags) => { flags.scope = "local"; }],
|
|
54
|
+
["-l", (flags) => { flags.scope = "local"; }],
|
|
55
|
+
["--force", (flags) => { flags.force = true; }],
|
|
56
|
+
["-f", (flags) => { flags.force = true; }],
|
|
57
|
+
["--full", (flags) => { flags.force = true; }],
|
|
58
|
+
// Keep legacy flags working as no-ops.
|
|
59
|
+
["--update", () => {}],
|
|
60
|
+
["-u", () => {}],
|
|
61
|
+
["--help", (flags) => { flags.help = true; }],
|
|
62
|
+
["-h", (flags) => { flags.help = true; }]
|
|
63
|
+
]);
|
|
64
|
+
|
|
65
|
+
function applyOption(flags, arg) {
|
|
66
|
+
const action = OPTION_ACTIONS.get(arg);
|
|
67
|
+
if (action) {
|
|
68
|
+
action(flags);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
console.error(`Unknown option: ${arg}`);
|
|
73
|
+
usage();
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
|
|
50
77
|
function parseArgs(argv) {
|
|
51
78
|
const args = argv.slice(2);
|
|
52
79
|
const flags = { scope: "global", force: false, help: false };
|
|
53
80
|
for (const arg of args) {
|
|
54
|
-
|
|
55
|
-
case "--global":
|
|
56
|
-
case "-g":
|
|
57
|
-
flags.scope = "global";
|
|
58
|
-
break;
|
|
59
|
-
case "--local":
|
|
60
|
-
case "-l":
|
|
61
|
-
flags.scope = "local";
|
|
62
|
-
break;
|
|
63
|
-
case "--force":
|
|
64
|
-
case "-f":
|
|
65
|
-
case "--full":
|
|
66
|
-
flags.force = true;
|
|
67
|
-
break;
|
|
68
|
-
// Keep legacy flags working
|
|
69
|
-
case "--update":
|
|
70
|
-
case "-u":
|
|
71
|
-
break;
|
|
72
|
-
case "--help":
|
|
73
|
-
case "-h":
|
|
74
|
-
flags.help = true;
|
|
75
|
-
break;
|
|
76
|
-
default:
|
|
77
|
-
console.error(`Unknown option: ${arg}`);
|
|
78
|
-
usage();
|
|
79
|
-
process.exit(1);
|
|
80
|
-
}
|
|
81
|
+
applyOption(flags, arg);
|
|
81
82
|
}
|
|
82
83
|
return flags;
|
|
83
84
|
}
|
|
@@ -131,66 +132,107 @@ function isCopilotCliInstalled() {
|
|
|
131
132
|
|
|
132
133
|
function removeDeprecatedSkills(targetRoot) {
|
|
133
134
|
const deprecated = ["loop", "monitor", "create-provider"];
|
|
134
|
-
|
|
135
|
-
let removedAny = false;
|
|
135
|
+
const state = { allOk: true, removedAny: false };
|
|
136
136
|
|
|
137
137
|
for (const name of deprecated) {
|
|
138
|
-
|
|
139
|
-
if (!existsSync(oldPath)) {
|
|
140
|
-
continue;
|
|
141
|
-
}
|
|
142
|
-
try {
|
|
143
|
-
unlinkSync(oldPath);
|
|
144
|
-
if (!removedAny) {
|
|
145
|
-
console.log();
|
|
146
|
-
removedAny = true;
|
|
147
|
-
}
|
|
148
|
-
console.log(` ✓ Removed deprecated skill: skills/${name}/SKILL.md`);
|
|
149
|
-
} catch {
|
|
150
|
-
allOk = false;
|
|
151
|
-
console.warn(` ⚠ Could not remove deprecated skill at ${oldPath} — remove it manually`);
|
|
152
|
-
}
|
|
138
|
+
applyDeprecatedSkillRemoval(targetRoot, name, state);
|
|
153
139
|
}
|
|
154
140
|
|
|
155
|
-
if (removedAny) {
|
|
141
|
+
if (state.removedAny) {
|
|
156
142
|
console.log(`\n Use the new namespaced commands: /tap-loop /tap-monitor /tap-create-provider`);
|
|
157
143
|
}
|
|
158
144
|
|
|
159
|
-
return allOk;
|
|
145
|
+
return state.allOk;
|
|
160
146
|
}
|
|
161
147
|
|
|
162
|
-
function
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
148
|
+
function applyDeprecatedSkillRemoval(targetRoot, name, state) {
|
|
149
|
+
const result = removeDeprecatedSkill(targetRoot, name);
|
|
150
|
+
if (result.removed) {
|
|
151
|
+
logDeprecatedSkillRemoved(name, state);
|
|
152
|
+
}
|
|
153
|
+
if (!result.ok) {
|
|
154
|
+
state.allOk = false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function logDeprecatedSkillRemoved(name, state) {
|
|
159
|
+
if (!state.removedAny) {
|
|
160
|
+
console.log();
|
|
161
|
+
state.removedAny = true;
|
|
162
|
+
}
|
|
163
|
+
console.log(` ✓ Removed deprecated skill: skills/${name}/SKILL.md`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function removeDeprecatedSkill(targetRoot, name) {
|
|
167
|
+
const oldPath = path.join(targetRoot, "skills", name, "SKILL.md");
|
|
168
|
+
if (!existsSync(oldPath)) {
|
|
169
|
+
return { ok: true, removed: false };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
unlinkSync(oldPath);
|
|
174
|
+
return { ok: true, removed: true };
|
|
175
|
+
} catch {
|
|
176
|
+
console.warn(` ⚠ Could not remove deprecated skill at ${oldPath} — remove it manually`);
|
|
177
|
+
return { ok: false, removed: false };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
166
180
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
181
|
+
function getScopeLabel(scope) {
|
|
182
|
+
return scope === "global" ? "global (~/.copilot)" : "local (.github)";
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function ensureGlobalInstallSupported(scope) {
|
|
186
|
+
if (scope !== "global" || isCopilotCliInstalled()) {
|
|
187
|
+
return;
|
|
172
188
|
}
|
|
173
189
|
|
|
190
|
+
console.log(`\n⚠ Copilot CLI does not appear to be installed.`);
|
|
191
|
+
console.log(` Install it first: https://docs.github.com/en/copilot/github-copilot-in-the-cli`);
|
|
192
|
+
console.log(` Then re-run: npx copilot-tap-extension\n`);
|
|
193
|
+
process.exit(1);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function getInstallState(targetRoot, flags) {
|
|
174
197
|
const installed = isAlreadyInstalled(targetRoot);
|
|
175
198
|
const isUpdate = installed && !flags.force;
|
|
176
199
|
const isReinstall = installed && flags.force;
|
|
177
200
|
const installedVersion = installed ? getInstalledVersion(targetRoot) : null;
|
|
178
201
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
202
|
+
return { installed, isUpdate, isReinstall, installedVersion };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function exitIfAlreadyCurrent(state, packageVersion) {
|
|
206
|
+
if (!state.isUpdate || !state.installedVersion || state.installedVersion !== packageVersion) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
console.log(`\n${BRAND} — already up to date (v${state.installedVersion})\n`);
|
|
211
|
+
process.exit(0);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function getVersionLabel(version) {
|
|
215
|
+
return version ? `v${version}` : "unknown";
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function announceInstall(state, packageVersion, scopeLabel) {
|
|
219
|
+
if (state.isUpdate) {
|
|
220
|
+
const fromLabel = getVersionLabel(state.installedVersion);
|
|
185
221
|
console.log(`\n${BRAND} — updating ${fromLabel} → v${packageVersion} (${scopeLabel})\n`);
|
|
186
|
-
|
|
187
|
-
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (state.isReinstall) {
|
|
226
|
+
const fromLabel = getVersionLabel(state.installedVersion);
|
|
188
227
|
console.log(`\n${BRAND} — reinstalling ${fromLabel} → v${packageVersion} (${scopeLabel})\n`);
|
|
189
|
-
|
|
190
|
-
console.log(`\n${BRAND} — installing v${packageVersion} (${scopeLabel})\n`);
|
|
228
|
+
return;
|
|
191
229
|
}
|
|
192
230
|
|
|
193
|
-
|
|
231
|
+
console.log(`\n${BRAND} — installing v${packageVersion} (${scopeLabel})\n`);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function buildCoreArtifacts(targetRoot) {
|
|
235
|
+
return [
|
|
194
236
|
{
|
|
195
237
|
src: path.join(distDir, "extension.mjs"),
|
|
196
238
|
dest: path.join(targetRoot, "extensions", EXT_DIR_NAME, "extension.mjs"),
|
|
@@ -202,8 +244,10 @@ function install(flags) {
|
|
|
202
244
|
label: "extensions/tap/version.json"
|
|
203
245
|
}
|
|
204
246
|
];
|
|
247
|
+
}
|
|
205
248
|
|
|
206
|
-
|
|
249
|
+
function buildAncillaryArtifacts(targetRoot) {
|
|
250
|
+
return [
|
|
207
251
|
{
|
|
208
252
|
src: path.join(distDir, "skills", "tap-loop", "SKILL.md"),
|
|
209
253
|
dest: path.join(targetRoot, "skills", "tap-loop", "SKILL.md"),
|
|
@@ -230,37 +274,69 @@ function install(flags) {
|
|
|
230
274
|
label: "copilot-instructions.md"
|
|
231
275
|
}
|
|
232
276
|
];
|
|
277
|
+
}
|
|
233
278
|
|
|
279
|
+
function buildInstallArtifacts(targetRoot, isUpdate) {
|
|
280
|
+
const coreArtifacts = buildCoreArtifacts(targetRoot);
|
|
281
|
+
const ancillaryArtifacts = buildAncillaryArtifacts(targetRoot);
|
|
234
282
|
// During updates, also install ancillary artifacts that don't yet exist at the destination
|
|
235
283
|
// (e.g. new skills added in a newer version). Existing ones are preserved to keep user customizations.
|
|
236
284
|
const newAncillaryArtifacts = isUpdate
|
|
237
285
|
? ancillaryArtifacts.filter(({ dest }) => !existsSync(dest))
|
|
238
286
|
: ancillaryArtifacts;
|
|
239
|
-
|
|
287
|
+
return [...coreArtifacts, ...newAncillaryArtifacts];
|
|
288
|
+
}
|
|
240
289
|
|
|
290
|
+
function copyArtifacts(artifacts) {
|
|
241
291
|
let allOk = true;
|
|
242
292
|
for (const { src, dest, label } of artifacts) {
|
|
243
293
|
if (!copyArtifact(src, dest, label)) {
|
|
244
294
|
allOk = false;
|
|
245
295
|
}
|
|
246
296
|
}
|
|
297
|
+
return allOk;
|
|
298
|
+
}
|
|
247
299
|
|
|
248
|
-
|
|
249
|
-
|
|
300
|
+
function getInstallVerb(state) {
|
|
301
|
+
if (state.isUpdate) {
|
|
302
|
+
return "updated";
|
|
250
303
|
}
|
|
304
|
+
return state.isReinstall ? "reinstalled" : "installed";
|
|
305
|
+
}
|
|
251
306
|
|
|
307
|
+
function finishInstall(allOk, state, targetRoot) {
|
|
252
308
|
console.log();
|
|
309
|
+
const verb = getInstallVerb(state);
|
|
253
310
|
if (allOk) {
|
|
254
|
-
const verb = isUpdate ? "updated" : isReinstall ? "reinstalled" : "installed";
|
|
255
311
|
console.log(`✓ ${BRAND} ${verb} to ${targetRoot}`);
|
|
256
312
|
return;
|
|
257
313
|
}
|
|
258
314
|
|
|
259
|
-
const verb = isUpdate ? "updated" : isReinstall ? "reinstalled" : "installed";
|
|
260
315
|
console.error(`⚠ Some artifacts could not be ${verb}.`);
|
|
261
316
|
process.exit(1);
|
|
262
317
|
}
|
|
263
318
|
|
|
319
|
+
function install(flags) {
|
|
320
|
+
const targetRoot = getTargetRoot(flags.scope);
|
|
321
|
+
const scopeLabel = getScopeLabel(flags.scope);
|
|
322
|
+
const packageVersion = getPackageVersion();
|
|
323
|
+
|
|
324
|
+
ensureGlobalInstallSupported(flags.scope);
|
|
325
|
+
|
|
326
|
+
const state = getInstallState(targetRoot, flags);
|
|
327
|
+
exitIfAlreadyCurrent(state, packageVersion);
|
|
328
|
+
announceInstall(state, packageVersion, scopeLabel);
|
|
329
|
+
|
|
330
|
+
const artifacts = buildInstallArtifacts(targetRoot, state.isUpdate);
|
|
331
|
+
let allOk = copyArtifacts(artifacts);
|
|
332
|
+
|
|
333
|
+
if (state.installed && !removeDeprecatedSkills(targetRoot)) {
|
|
334
|
+
allOk = false;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
finishInstall(allOk, state, targetRoot);
|
|
338
|
+
}
|
|
339
|
+
|
|
264
340
|
const flags = parseArgs(process.argv);
|
|
265
341
|
|
|
266
342
|
if (flags.help) {
|
|
@@ -170,6 +170,10 @@ When working on the extension itself, not just using its emitter tools, prefer t
|
|
|
170
170
|
- use `session.send()` for asynchronous follow-up prompts and `session.sendAndWait()` only when the extension must wait for an answer
|
|
171
171
|
- use `onPermissionRequest` and `onUserInputRequest` for guarded flows instead of custom ad hoc prompting
|
|
172
172
|
- use `fs.watch` or `watchFile` when the extension should react to manual file edits or workspace artifacts such as `plan.md`
|
|
173
|
+
- use `createCanvas` with `joinSession({ canvases: [...] })` for extension-owned UI panels when text-only EventStreams are not enough; `open()` returns `title`, `status`, and a renderer `url`, actions power `invoke_canvas_action`, and per-instance state should be keyed by `instanceId`
|
|
174
|
+
- treat canvas support as experimental: action names must not start with `canvas.`, guard optional host canvas capabilities, prefer loopback HTTP renderers on ephemeral ports, and clean them up in `onClose`
|
|
175
|
+
- remember that external tap providers cannot declare Copilot SDK canvases over the current WebSocket protocol; implement canvases in the extension layer or explicitly extend the gateway protocol first
|
|
176
|
+
- open the built-in `tap-diagnostics` canvas with `tap_open_diagnostics_canvas` when users ask to inspect tap internals, diagnostics, logs, stream history, provider state, or "everything tap is doing"
|
|
173
177
|
|
|
174
178
|
Good non-emitter examples to adapt into this repo:
|
|
175
179
|
|
|
@@ -177,6 +181,7 @@ Good non-emitter examples to adapt into this repo:
|
|
|
177
181
|
- watch a config file and refresh the corresponding emitter when the user edits it
|
|
178
182
|
- add a helper tool that fetches one-shot data from an API while emitters continue to watch background streams
|
|
179
183
|
- log EventFilter updates and emitter lifecycle events to the timeline for observability
|
|
184
|
+
- add a canvas dashboard for stream/emitter inspection when a workflow benefits from a persistent visual surface
|
|
180
185
|
|
|
181
186
|
## What not to do
|
|
182
187
|
|