copilot-tap-extension 1.1.3 → 2.0.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.
- package/README.md +13 -12
- package/bin/install.mjs +70 -25
- package/dist/copilot-instructions.md +1 -1
- package/dist/extension.mjs +46 -14
- package/dist/skills/{create-provider → tap-create-provider}/SKILL.md +1 -1
- package/dist/skills/{loop → tap-loop}/SKILL.md +3 -3
- package/dist/skills/{monitor → tap-monitor}/SKILL.md +10 -27
- package/dist/version.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -62,7 +62,7 @@ npx copilot-tap-extension
|
|
|
62
62
|
npx copilot-tap-extension --local
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
-
This installs the bundled extension, the `/loop` skill, the `/monitor` skill, and the agent instructions to the appropriate Copilot directory. Run `npx copilot-tap-extension --help` for all options.
|
|
65
|
+
This installs the bundled extension, the `/tap-loop` skill, the `/tap-monitor` skill, and the agent instructions to the appropriate Copilot directory. Run `npx copilot-tap-extension --help` for all options.
|
|
66
66
|
|
|
67
67
|
To update to the latest version, re-run the same command with `--force`:
|
|
68
68
|
|
|
@@ -103,13 +103,13 @@ The config file tells the extension which emitters to auto-start. The example de
|
|
|
103
103
|
|
|
104
104
|
This runs the heartbeat script on session start, drops boot messages, injects warnings and errors, and keeps everything else in the stream.
|
|
105
105
|
|
|
106
|
-
Once inside the session, describe what you want in natural language. You can also use `/loop` to set up scheduled prompts directly:
|
|
106
|
+
Once inside the session, describe what you want in natural language. You can also use `/tap-loop` to set up scheduled prompts directly:
|
|
107
107
|
|
|
108
108
|
> _"Watch my build logs and tell me if anything fails"_
|
|
109
109
|
|
|
110
|
-
> _"/loop 5m check for new PR review comments"_
|
|
110
|
+
> _"/tap-loop 5m check for new PR review comments"_
|
|
111
111
|
|
|
112
|
-
> _"/monitor tail -f /var/log/app.log"_
|
|
112
|
+
> _"/tap-monitor tail -f /var/log/app.log"_
|
|
113
113
|
|
|
114
114
|
> _"Tail the API logs, inject errors, drop health checks"_
|
|
115
115
|
|
|
@@ -175,11 +175,11 @@ You keep coding. Twenty minutes later, Copilot interrupts: "Run 48291: deploymen
|
|
|
175
175
|
|
|
176
176
|
**Monitor a command with self-tuning filters**
|
|
177
177
|
|
|
178
|
-
Use `/monitor` to run a shell command continuously while a companion agent periodically reads the output and updates the filter expressions to separate noise from signal automatically.
|
|
178
|
+
Use `/tap-monitor` to run a shell command continuously while a companion agent periodically reads the output and updates the filter expressions to separate noise from signal automatically.
|
|
179
179
|
|
|
180
180
|
```
|
|
181
|
-
/monitor tail -f /var/log/app.log
|
|
182
|
-
/monitor 10m docker logs -f mycontainer
|
|
181
|
+
/tap-monitor tail -f /var/log/app.log
|
|
182
|
+
/tap-monitor 10m docker logs -f mycontainer
|
|
183
183
|
```
|
|
184
184
|
|
|
185
185
|
The command stream starts with a sensible initial `notifyPattern`. Every few minutes (configurable) the companion reviews recent log lines and calls `tap_set_event_filter` if the patterns need adjustment. The filter tightens itself based on real output — no manual tuning required.
|
|
@@ -189,7 +189,7 @@ The command stream starts with a sensible initial `notifyPattern`. Every few min
|
|
|
189
189
|
A PromptEmitter re-runs an agent prompt at a fixed interval. Useful for PR comments, CI status, or ticket queues.
|
|
190
190
|
|
|
191
191
|
```
|
|
192
|
-
/loop 15m Check for new failing CI runs or PR review comments.
|
|
192
|
+
/tap-loop 15m Check for new failing CI runs or PR review comments.
|
|
193
193
|
Summarize only actionable items.
|
|
194
194
|
```
|
|
195
195
|
|
|
@@ -197,10 +197,10 @@ Every 15 minutes the agent scans and reports back. No news means no interruption
|
|
|
197
197
|
|
|
198
198
|
**Run a prompt when idle**
|
|
199
199
|
|
|
200
|
-
Use `/loop idle` to re-run a prompt whenever the session has nothing else to do. Set `maxRuns` to cap iterations.
|
|
200
|
+
Use `/tap-loop idle` to re-run a prompt whenever the session has nothing else to do. Set `maxRuns` to cap iterations.
|
|
201
201
|
|
|
202
202
|
```
|
|
203
|
-
/loop idle Scan for new issues labeled urgent. Summarize what changed.
|
|
203
|
+
/tap-loop idle Scan for new issues labeled urgent. Summarize what changed.
|
|
204
204
|
```
|
|
205
205
|
|
|
206
206
|
The prompt fires immediately, then re-fires after each idle period. It stops after reaching the iteration limit.
|
|
@@ -222,8 +222,9 @@ Rules can be added or changed while the emitter is running. You never need to re
|
|
|
222
222
|
```text
|
|
223
223
|
.github/
|
|
224
224
|
extensions/tap/extension.mjs # extension entry point (loads the runtime)
|
|
225
|
-
skills/loop/ # /loop skill for scheduled and idle prompts
|
|
226
|
-
skills/monitor/ # /monitor skill for self-tuning command monitors
|
|
225
|
+
skills/tap-loop/ # /tap-loop skill for scheduled and idle prompts
|
|
226
|
+
skills/tap-monitor/ # /tap-monitor skill for self-tuning command monitors
|
|
227
|
+
skills/tap-create-provider/ # /tap-create-provider skill for scaffolding external tool providers
|
|
227
228
|
copilot-instructions.md # agent guidance for using this extension
|
|
228
229
|
src/
|
|
229
230
|
emitter/ # supervisor, lifecycle, spawn, line router
|
package/bin/install.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync, mkdirSync, copyFileSync, readFileSync } from "node:fs";
|
|
2
|
+
import { existsSync, mkdirSync, copyFileSync, readFileSync, unlinkSync } from "node:fs";
|
|
3
3
|
import { execFileSync } from "node:child_process";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import path from "node:path";
|
|
@@ -33,22 +33,22 @@ and preserves customizable artifacts. If fresh, does a full install.
|
|
|
33
33
|
Options:
|
|
34
34
|
--global, -g Install to ~/.copilot/ (default)
|
|
35
35
|
--local, -l Install to .github/ (project-scoped)
|
|
36
|
-
--
|
|
36
|
+
--force, -f Force a full reinstall even if already installed
|
|
37
37
|
--help, -h Show this help message
|
|
38
38
|
|
|
39
39
|
Installs:
|
|
40
40
|
extensions/tap/extension.mjs The bundled ※ tap extension
|
|
41
41
|
extensions/tap/version.json Installed version metadata
|
|
42
|
-
skills/loop/SKILL.md The /loop skill for prompt-based loops
|
|
43
|
-
skills/create-provider/SKILL.md The /create-provider skill for scaffolding providers
|
|
44
|
-
skills/monitor/SKILL.md The /monitor skill for self-tuning command monitors
|
|
42
|
+
skills/tap-loop/SKILL.md The /tap-loop skill for prompt-based loops
|
|
43
|
+
skills/tap-create-provider/SKILL.md The /tap-create-provider skill for scaffolding providers
|
|
44
|
+
skills/tap-monitor/SKILL.md The /tap-monitor skill for self-tuning command monitors
|
|
45
45
|
copilot-instructions.md Agent instructions for using ※ tap
|
|
46
46
|
`);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
function parseArgs(argv) {
|
|
50
50
|
const args = argv.slice(2);
|
|
51
|
-
const flags = { scope: "global",
|
|
51
|
+
const flags = { scope: "global", force: false, help: false };
|
|
52
52
|
for (const arg of args) {
|
|
53
53
|
switch (arg) {
|
|
54
54
|
case "--global":
|
|
@@ -59,12 +59,12 @@ function parseArgs(argv) {
|
|
|
59
59
|
case "-l":
|
|
60
60
|
flags.scope = "local";
|
|
61
61
|
break;
|
|
62
|
+
case "--force":
|
|
63
|
+
case "-f":
|
|
62
64
|
case "--full":
|
|
63
|
-
flags.
|
|
65
|
+
flags.force = true;
|
|
64
66
|
break;
|
|
65
67
|
// Keep legacy flags working
|
|
66
|
-
case "--force":
|
|
67
|
-
case "-f":
|
|
68
68
|
case "--update":
|
|
69
69
|
case "-u":
|
|
70
70
|
break;
|
|
@@ -128,6 +128,36 @@ function isCopilotCliInstalled() {
|
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
function removeDeprecatedSkills(targetRoot) {
|
|
132
|
+
const deprecated = ["loop", "monitor", "create-provider"];
|
|
133
|
+
let allOk = true;
|
|
134
|
+
let removedAny = false;
|
|
135
|
+
|
|
136
|
+
for (const name of deprecated) {
|
|
137
|
+
const oldPath = path.join(targetRoot, "skills", name, "SKILL.md");
|
|
138
|
+
if (!existsSync(oldPath)) {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
unlinkSync(oldPath);
|
|
143
|
+
if (!removedAny) {
|
|
144
|
+
console.log();
|
|
145
|
+
removedAny = true;
|
|
146
|
+
}
|
|
147
|
+
console.log(` ✓ Removed deprecated skill: skills/${name}/SKILL.md`);
|
|
148
|
+
} catch {
|
|
149
|
+
allOk = false;
|
|
150
|
+
console.warn(` ⚠ Could not remove deprecated skill at ${oldPath} — remove it manually`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (removedAny) {
|
|
155
|
+
console.log(`\n Use the new namespaced commands: /tap-loop /tap-monitor /tap-create-provider`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return allOk;
|
|
159
|
+
}
|
|
160
|
+
|
|
131
161
|
function install(flags) {
|
|
132
162
|
const targetRoot = getTargetRoot(flags.scope);
|
|
133
163
|
const scopeLabel = flags.scope === "global" ? "global (~/.copilot)" : "local (.github)";
|
|
@@ -141,16 +171,20 @@ function install(flags) {
|
|
|
141
171
|
}
|
|
142
172
|
|
|
143
173
|
const installed = isAlreadyInstalled(targetRoot);
|
|
144
|
-
const isUpdate = installed && !flags.
|
|
174
|
+
const isUpdate = installed && !flags.force;
|
|
175
|
+
const isReinstall = installed && flags.force;
|
|
176
|
+
const installedVersion = installed ? getInstalledVersion(targetRoot) : null;
|
|
145
177
|
|
|
146
178
|
if (isUpdate) {
|
|
147
|
-
const installedVersion = getInstalledVersion(targetRoot);
|
|
148
179
|
if (installedVersion && installedVersion === packageVersion) {
|
|
149
180
|
console.log(`\n${BRAND} — already up to date (v${installedVersion})\n`);
|
|
150
181
|
process.exit(0);
|
|
151
182
|
}
|
|
152
183
|
const fromLabel = installedVersion ? `v${installedVersion}` : "unknown";
|
|
153
184
|
console.log(`\n${BRAND} — updating ${fromLabel} → v${packageVersion} (${scopeLabel})\n`);
|
|
185
|
+
} else if (isReinstall) {
|
|
186
|
+
const fromLabel = installedVersion ? `v${installedVersion}` : "unknown";
|
|
187
|
+
console.log(`\n${BRAND} — reinstalling ${fromLabel} → v${packageVersion} (${scopeLabel})\n`);
|
|
154
188
|
} else {
|
|
155
189
|
console.log(`\n${BRAND} — installing v${packageVersion} (${scopeLabel})\n`);
|
|
156
190
|
}
|
|
@@ -170,19 +204,19 @@ function install(flags) {
|
|
|
170
204
|
|
|
171
205
|
const ancillaryArtifacts = [
|
|
172
206
|
{
|
|
173
|
-
src: path.join(distDir, "skills", "loop", "SKILL.md"),
|
|
174
|
-
dest: path.join(targetRoot, "skills", "loop", "SKILL.md"),
|
|
175
|
-
label: "skills/loop/SKILL.md"
|
|
207
|
+
src: path.join(distDir, "skills", "tap-loop", "SKILL.md"),
|
|
208
|
+
dest: path.join(targetRoot, "skills", "tap-loop", "SKILL.md"),
|
|
209
|
+
label: "skills/tap-loop/SKILL.md"
|
|
176
210
|
},
|
|
177
211
|
{
|
|
178
|
-
src: path.join(distDir, "skills", "create-provider", "SKILL.md"),
|
|
179
|
-
dest: path.join(targetRoot, "skills", "create-provider", "SKILL.md"),
|
|
180
|
-
label: "skills/create-provider/SKILL.md"
|
|
212
|
+
src: path.join(distDir, "skills", "tap-create-provider", "SKILL.md"),
|
|
213
|
+
dest: path.join(targetRoot, "skills", "tap-create-provider", "SKILL.md"),
|
|
214
|
+
label: "skills/tap-create-provider/SKILL.md"
|
|
181
215
|
},
|
|
182
216
|
{
|
|
183
|
-
src: path.join(distDir, "skills", "monitor", "SKILL.md"),
|
|
184
|
-
dest: path.join(targetRoot, "skills", "monitor", "SKILL.md"),
|
|
185
|
-
label: "skills/monitor/SKILL.md"
|
|
217
|
+
src: path.join(distDir, "skills", "tap-monitor", "SKILL.md"),
|
|
218
|
+
dest: path.join(targetRoot, "skills", "tap-monitor", "SKILL.md"),
|
|
219
|
+
label: "skills/tap-monitor/SKILL.md"
|
|
186
220
|
},
|
|
187
221
|
{
|
|
188
222
|
src: path.join(distDir, "copilot-instructions.md"),
|
|
@@ -191,7 +225,12 @@ function install(flags) {
|
|
|
191
225
|
}
|
|
192
226
|
];
|
|
193
227
|
|
|
194
|
-
|
|
228
|
+
// During updates, also install ancillary artifacts that don't yet exist at the destination
|
|
229
|
+
// (e.g. new skills added in a newer version). Existing ones are preserved to keep user customizations.
|
|
230
|
+
const newAncillaryArtifacts = isUpdate
|
|
231
|
+
? ancillaryArtifacts.filter(({ dest }) => !existsSync(dest))
|
|
232
|
+
: ancillaryArtifacts;
|
|
233
|
+
const artifacts = [...coreArtifacts, ...newAncillaryArtifacts];
|
|
195
234
|
|
|
196
235
|
let allOk = true;
|
|
197
236
|
for (const { src, dest, label } of artifacts) {
|
|
@@ -200,14 +239,20 @@ function install(flags) {
|
|
|
200
239
|
}
|
|
201
240
|
}
|
|
202
241
|
|
|
242
|
+
if (installed && !removeDeprecatedSkills(targetRoot)) {
|
|
243
|
+
allOk = false;
|
|
244
|
+
}
|
|
245
|
+
|
|
203
246
|
console.log();
|
|
204
247
|
if (allOk) {
|
|
205
|
-
const verb = isUpdate ? "updated" : "installed";
|
|
248
|
+
const verb = isUpdate ? "updated" : isReinstall ? "reinstalled" : "installed";
|
|
206
249
|
console.log(`✓ ${BRAND} ${verb} to ${targetRoot}`);
|
|
207
|
-
|
|
208
|
-
console.error(`⚠ Some artifacts could not be ${isUpdate ? "updated" : "installed"}.`);
|
|
209
|
-
process.exit(1);
|
|
250
|
+
return;
|
|
210
251
|
}
|
|
252
|
+
|
|
253
|
+
const verb = isUpdate ? "updated" : isReinstall ? "reinstalled" : "installed";
|
|
254
|
+
console.error(`⚠ Some artifacts could not be ${verb}.`);
|
|
255
|
+
process.exit(1);
|
|
211
256
|
}
|
|
212
257
|
|
|
213
258
|
const flags = parseArgs(process.argv);
|
|
@@ -146,7 +146,7 @@ If the work is mostly reasoning rather than data collection, prefer a PromptEmit
|
|
|
146
146
|
- prompt once for a background check (oneTime)
|
|
147
147
|
- prompt + `runInterval` for a fixed maintenance loop (timed)
|
|
148
148
|
|
|
149
|
-
This is the closest analogue to Claude's session-scoped `/loop` behavior in this extension.
|
|
149
|
+
This is the closest analogue to Claude's session-scoped `/tap-loop` behavior in this extension.
|
|
150
150
|
|
|
151
151
|
## Borrow from the official SDK examples
|
|
152
152
|
|
package/dist/extension.mjs
CHANGED
|
@@ -3851,6 +3851,20 @@ function clampLimit(value, fallback = 20) {
|
|
|
3851
3851
|
function nowIso() {
|
|
3852
3852
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
3853
3853
|
}
|
|
3854
|
+
function parseIntervalSchedule(value) {
|
|
3855
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
3856
|
+
return null;
|
|
3857
|
+
}
|
|
3858
|
+
return value.map((item, index) => {
|
|
3859
|
+
const parsed = parseLoopInterval(item);
|
|
3860
|
+
if (parsed === null || parsed.idle === true || !Number.isFinite(parsed.ms) || parsed.ms <= 0) {
|
|
3861
|
+
throw new Error(
|
|
3862
|
+
`Invalid interval schedule entry at index ${index}: '${item}'. Schedule entries must be non-blank intervals greater than 0 and cannot be 'idle'.`
|
|
3863
|
+
);
|
|
3864
|
+
}
|
|
3865
|
+
return parsed;
|
|
3866
|
+
});
|
|
3867
|
+
}
|
|
3854
3868
|
function parseLoopInterval(value) {
|
|
3855
3869
|
if (value === void 0 || value === null || String(value).trim() === "") {
|
|
3856
3870
|
return null;
|
|
@@ -4306,7 +4320,11 @@ function buildEmitterState(spec, baseCwd, defaults = {}) {
|
|
|
4306
4320
|
if (command && prompt) {
|
|
4307
4321
|
throw new Error(`Emitter '${name}' cannot define both command and prompt. Choose one emitter type.`);
|
|
4308
4322
|
}
|
|
4309
|
-
const
|
|
4323
|
+
const schedule = parseIntervalSchedule(spec.everySchedule);
|
|
4324
|
+
if (schedule && spec.every != null && String(spec.every).trim() !== "") {
|
|
4325
|
+
throw new Error(`Emitter '${name}': 'every' and 'everySchedule' are mutually exclusive. Use one or the other.`);
|
|
4326
|
+
}
|
|
4327
|
+
const interval = schedule ? null : parseLoopInterval(spec.every);
|
|
4310
4328
|
const lifespan = normalizeLifespan(spec.scope, defaults.scope ?? LIFESPAN.TEMPORARY);
|
|
4311
4329
|
const ownership = normalizeOwnership(spec.managedBy, defaults.managedBy ?? OWNERSHIP.MODEL_OWNED);
|
|
4312
4330
|
const eventFilter = createEventFilter(
|
|
@@ -4321,7 +4339,7 @@ function buildEmitterState(spec, baseCwd, defaults = {}) {
|
|
|
4321
4339
|
throw new Error(`Emitter '${name}': every='idle' is only valid for prompt emitters, not command emitters.`);
|
|
4322
4340
|
}
|
|
4323
4341
|
runSchedule = RUN_SCHEDULE.IDLE;
|
|
4324
|
-
} else if (interval) {
|
|
4342
|
+
} else if (interval || schedule) {
|
|
4325
4343
|
runSchedule = RUN_SCHEDULE.TIMED;
|
|
4326
4344
|
} else if (prompt) {
|
|
4327
4345
|
runSchedule = RUN_SCHEDULE.ONE_TIME;
|
|
@@ -4336,8 +4354,10 @@ function buildEmitterState(spec, baseCwd, defaults = {}) {
|
|
|
4336
4354
|
prompt: prompt || null,
|
|
4337
4355
|
emitterType,
|
|
4338
4356
|
runSchedule,
|
|
4339
|
-
every: interval?.text ?? null,
|
|
4340
|
-
everyMs: interval?.ms ?? null,
|
|
4357
|
+
every: interval?.text ?? (schedule ? schedule[0].text : null),
|
|
4358
|
+
everyMs: interval?.ms ?? (schedule ? schedule[0].ms : null),
|
|
4359
|
+
everySchedule: schedule ? schedule.map((s) => s.text) : null,
|
|
4360
|
+
everyScheduleMs: schedule ? schedule.map((s) => s.ms) : null,
|
|
4341
4361
|
requestedCwd: spec.cwd ?? null,
|
|
4342
4362
|
cwd: resolveRequestedCwd(baseCwd, spec.cwd),
|
|
4343
4363
|
stream: normalizeName(spec.channel, name),
|
|
@@ -4446,15 +4466,16 @@ function formatRunningEmitter(emitter, stream) {
|
|
|
4446
4466
|
return [
|
|
4447
4467
|
`- ${emitter.name}:`,
|
|
4448
4468
|
` status=${emitter.status}`,
|
|
4449
|
-
` scope=${emitter.
|
|
4450
|
-
` managedBy=${emitter.
|
|
4469
|
+
` scope=${emitter.lifespan}`,
|
|
4470
|
+
` managedBy=${emitter.ownership}`,
|
|
4451
4471
|
` emitterType=${emitter.emitterType}`,
|
|
4452
4472
|
` runSchedule=${emitter.runSchedule}`,
|
|
4453
|
-
` stream=${emitter.
|
|
4473
|
+
` stream=${emitter.stream}`,
|
|
4454
4474
|
` sessionInjector=${stream?.sessionInjector?.enabled ? "on" : "off"}`,
|
|
4455
4475
|
` cwd=${emitter.cwd}`,
|
|
4456
4476
|
` ${describeEmitterWork(emitter)}`,
|
|
4457
|
-
emitter.
|
|
4477
|
+
emitter.everySchedule ? ` everySchedule=[${emitter.everySchedule.join(", ")}]` : null,
|
|
4478
|
+
emitter.every && !emitter.everySchedule ? ` every=${emitter.every}` : null,
|
|
4458
4479
|
emitter.maxRuns ? ` maxRuns=${emitter.maxRuns}` : null,
|
|
4459
4480
|
` autoStart=${emitter.autoStart}`,
|
|
4460
4481
|
` includeStderr=${emitter.includeStderr}`,
|
|
@@ -4636,6 +4657,16 @@ function createLifecycle({ lineRouter, sessionPort }) {
|
|
|
4636
4657
|
void runScheduledIteration(emitter);
|
|
4637
4658
|
}, delayMs);
|
|
4638
4659
|
}
|
|
4660
|
+
function nextDelay(emitter) {
|
|
4661
|
+
if (emitter.runSchedule === RUN_SCHEDULE.IDLE) {
|
|
4662
|
+
return IDLE_PROMPT_DELAY_MS;
|
|
4663
|
+
}
|
|
4664
|
+
if (emitter.everyScheduleMs) {
|
|
4665
|
+
const idx = Math.min(Math.max(0, emitter.runCount - 1), emitter.everyScheduleMs.length - 1);
|
|
4666
|
+
return emitter.everyScheduleMs[idx];
|
|
4667
|
+
}
|
|
4668
|
+
return emitter.everyMs;
|
|
4669
|
+
}
|
|
4639
4670
|
async function runScheduledIteration(emitter) {
|
|
4640
4671
|
if (emitter.stopRequested || emitter.inFlight) {
|
|
4641
4672
|
return;
|
|
@@ -4673,13 +4704,12 @@ function createLifecycle({ lineRouter, sessionPort }) {
|
|
|
4673
4704
|
return;
|
|
4674
4705
|
}
|
|
4675
4706
|
emitter.status = EMITTER_STATUS.WAITING;
|
|
4676
|
-
|
|
4677
|
-
scheduleIteration(emitter, delay);
|
|
4707
|
+
scheduleIteration(emitter, nextDelay(emitter));
|
|
4678
4708
|
return;
|
|
4679
4709
|
}
|
|
4680
4710
|
if (result.deferred) {
|
|
4681
4711
|
emitter.status = EMITTER_STATUS.WAITING;
|
|
4682
|
-
const retryDelay = emitter.runSchedule === RUN_SCHEDULE.IDLE ? IDLE_PROMPT_BACKOFF_MS : emitter
|
|
4712
|
+
const retryDelay = emitter.runSchedule === RUN_SCHEDULE.IDLE ? IDLE_PROMPT_BACKOFF_MS : nextDelay(emitter);
|
|
4683
4713
|
if (emitter.runSchedule !== RUN_SCHEDULE.IDLE) {
|
|
4684
4714
|
lineRouter.appendSystemMessage(
|
|
4685
4715
|
emitter,
|
|
@@ -4705,11 +4735,11 @@ function createLifecycle({ lineRouter, sessionPort }) {
|
|
|
4705
4735
|
return;
|
|
4706
4736
|
}
|
|
4707
4737
|
emitter.status = EMITTER_STATUS.WAITING;
|
|
4708
|
-
const failRetryDelay = emitter.runSchedule === RUN_SCHEDULE.IDLE ? IDLE_PROMPT_BACKOFF_MS : emitter
|
|
4738
|
+
const failRetryDelay = emitter.runSchedule === RUN_SCHEDULE.IDLE ? IDLE_PROMPT_BACKOFF_MS : nextDelay(emitter);
|
|
4709
4739
|
scheduleIteration(emitter, failRetryDelay);
|
|
4710
4740
|
}
|
|
4711
4741
|
function startScheduled(emitter) {
|
|
4712
|
-
const scheduleLabel = emitter.runSchedule === RUN_SCHEDULE.TIMED ? `every ${emitter.every}` : emitter.runSchedule === RUN_SCHEDULE.IDLE ? "when idle" : RUN_SCHEDULE.ONE_TIME;
|
|
4742
|
+
const scheduleLabel = emitter.runSchedule === RUN_SCHEDULE.TIMED ? emitter.everySchedule ? `backoff [${emitter.everySchedule.join(", ")}]` : `every ${emitter.every}` : emitter.runSchedule === RUN_SCHEDULE.IDLE ? "when idle" : RUN_SCHEDULE.ONE_TIME;
|
|
4713
4743
|
const initialDelayMs = 0;
|
|
4714
4744
|
const firstRunLabel = "";
|
|
4715
4745
|
lineRouter.appendSystemMessage(
|
|
@@ -5088,6 +5118,7 @@ function createEmitterTools({ streams, configStore, supervisor, getBaseCwd }) {
|
|
|
5088
5118
|
channel: { type: "string", description: "EventStream to receive accepted events." },
|
|
5089
5119
|
cwd: { type: "string", description: "Optional working directory relative to the session cwd." },
|
|
5090
5120
|
every: { type: "string", description: "Optional repeat interval like 30s, 5m, 2h, or 1d. Use 'idle' for prompts that re-run whenever the session is idle. When omitted, commands run continuously and prompts run once." },
|
|
5121
|
+
everySchedule: { type: "array", minItems: 1, items: { type: "string" }, description: "Optional backoff schedule \u2014 an ordered non-empty list of interval strings (e.g. ['10s','20s','30s','1m','2m','5m','10m']). The emitter uses each interval in sequence, then repeats the last one forever. Overrides 'every' when provided. Cannot be 'idle' entries." },
|
|
5091
5122
|
scope: { type: "string", description: "Use 'temporary' for session-only or 'persistent' to write config." },
|
|
5092
5123
|
managedBy: { type: "string", description: "Ownership label: 'userOwned' or 'modelOwned'." },
|
|
5093
5124
|
autoStart: { type: "boolean", description: "When persistent, whether the emitter should auto-start next session." },
|
|
@@ -5122,7 +5153,8 @@ function createEmitterTools({ streams, configStore, supervisor, getBaseCwd }) {
|
|
|
5122
5153
|
`ownership=${emitter.ownership}`,
|
|
5123
5154
|
`emitterType=${emitter.emitterType}`,
|
|
5124
5155
|
`runSchedule=${emitter.runSchedule}`,
|
|
5125
|
-
emitter.
|
|
5156
|
+
emitter.everySchedule ? `everySchedule=[${emitter.everySchedule.join(", ")}]` : null,
|
|
5157
|
+
emitter.every && !emitter.everySchedule ? `every=${emitter.every}` : null,
|
|
5126
5158
|
emitter.maxRuns ? `maxRuns=${emitter.maxRuns}` : null,
|
|
5127
5159
|
`stream=${emitter.stream}`,
|
|
5128
5160
|
`sessionInjector=${streams.ensure(emitter.stream).sessionInjector.enabled ? "on" : "off"}`,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: create-provider
|
|
2
|
+
name: tap-create-provider
|
|
3
3
|
description: "Create an external tool provider that connects to ※ tap. Use when the user says 'create a provider', 'build a provider', 'scaffold a provider', 'add an external tool', 'connect a service to tap', or wants to extend Copilot with tools written in any language."
|
|
4
4
|
argument-hint: "<what the provider should do>"
|
|
5
5
|
user-invocable: true
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: loop
|
|
2
|
+
name: tap-loop
|
|
3
3
|
description: "Run a prompt on a recurring schedule or when idle. Use when the user says 'loop', 'every 5 minutes', 'check periodically', 'keep watching', 'repeat this', 'run when idle', or wants any prompt to re-run automatically."
|
|
4
4
|
argument-hint: "<interval|idle> <prompt>"
|
|
5
5
|
user-invocable: true
|
|
@@ -17,7 +17,7 @@ Interpret the invocation as:
|
|
|
17
17
|
Example (timed):
|
|
18
18
|
|
|
19
19
|
```text
|
|
20
|
-
/loop 5m check the deploy
|
|
20
|
+
/tap-loop 5m check the deploy
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
means:
|
|
@@ -28,7 +28,7 @@ means:
|
|
|
28
28
|
Example (idle):
|
|
29
29
|
|
|
30
30
|
```text
|
|
31
|
-
/loop idle check the deploy
|
|
31
|
+
/tap-loop idle check the deploy
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
means:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: monitor
|
|
2
|
+
name: tap-monitor
|
|
3
3
|
description: "Start a self-tuning command monitor. Use when the user says 'monitor', 'watch', 'tail', 'track', 'keep an eye on', or wants a shell command to run continuously while Copilot automatically reviews and tunes the output filters over time."
|
|
4
|
-
argument-hint: "
|
|
4
|
+
argument-hint: "<shell-command>"
|
|
5
5
|
user-invocable: true
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -9,33 +9,18 @@ Start a CommandEmitter for the given shell command paired with a companion Promp
|
|
|
9
9
|
|
|
10
10
|
## Expected input
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
The entire invocation is the shell command to run continuously.
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
2. The rest of the input is the shell command to run continuously.
|
|
16
|
-
|
|
17
|
-
Example (with explicit interval):
|
|
14
|
+
Example:
|
|
18
15
|
|
|
19
16
|
```text
|
|
20
|
-
/monitor
|
|
17
|
+
/tap-monitor tail -f /var/log/app.log
|
|
21
18
|
```
|
|
22
19
|
|
|
23
20
|
means:
|
|
24
21
|
|
|
25
|
-
- `reviewInterval = "10m"` — companion reviews the stream every 10 minutes
|
|
26
22
|
- `command = "tail -f /var/log/app.log"`
|
|
27
23
|
|
|
28
|
-
Example (default interval):
|
|
29
|
-
|
|
30
|
-
```text
|
|
31
|
-
/monitor docker logs -f mycontainer
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
means:
|
|
35
|
-
|
|
36
|
-
- `reviewInterval = "5m"` (default)
|
|
37
|
-
- `command = "docker logs -f mycontainer"`
|
|
38
|
-
|
|
39
24
|
If the command is missing, ask the user for it instead of guessing.
|
|
40
25
|
|
|
41
26
|
## What to create
|
|
@@ -57,7 +42,7 @@ Use `tap_start_emitter` to start the CommandEmitter:
|
|
|
57
42
|
Use `tap_start_emitter` to start a second emitter immediately after the command emitter:
|
|
58
43
|
|
|
59
44
|
- `prompt` — a **fully self-contained** instruction (see template below).
|
|
60
|
-
- `
|
|
45
|
+
- `everySchedule: ["10s", "20s", "30s", "1m", "2m", "5m", "10m"]` — backoff schedule: reviews start very frequent to validate the monitor quickly, then space out as it stabilises.
|
|
61
46
|
- `scope = "temporary"`, `managedBy = "modelOwned"`.
|
|
62
47
|
- Name it `<command-emitter-name>-review`.
|
|
63
48
|
- `subscribe = false` — review is internal housekeeping, not user-facing.
|
|
@@ -89,13 +74,13 @@ Substitute the real emitter name and stream name into the prompt before passing
|
|
|
89
74
|
|
|
90
75
|
When this skill is invoked:
|
|
91
76
|
|
|
92
|
-
1. Parse the
|
|
77
|
+
1. Parse the command from the invocation.
|
|
93
78
|
2. Start the CommandEmitter.
|
|
94
|
-
3. Start the companion PromptEmitter using the self-contained prompt template
|
|
79
|
+
3. Start the companion PromptEmitter using the self-contained prompt template and the hardcoded backoff schedule.
|
|
95
80
|
4. Confirm to the user:
|
|
96
81
|
- Command emitter name and stream.
|
|
97
82
|
- Initial filter patterns (or "none set — companion will tune on first review").
|
|
98
|
-
- Companion reviewer name and review
|
|
83
|
+
- Companion reviewer name and its review schedule (first check in 10s, backing off to 10m).
|
|
99
84
|
5. Stop there — do not immediately inspect stream history or simulate a review.
|
|
100
85
|
|
|
101
86
|
## Stopping the monitor
|
|
@@ -124,6 +109,4 @@ Remind the companion (via the prompt) to be conservative:
|
|
|
124
109
|
|
|
125
110
|
## If the input is incomplete
|
|
126
111
|
|
|
127
|
-
If the
|
|
128
|
-
|
|
129
|
-
If only an interval is given with no command, ask for the command.
|
|
112
|
+
If the invocation contains no recognisable shell command, ask the user for it.
|
package/dist/version.json
CHANGED