@tuzelko/opencode-delayed-message 0.1.6 → 0.1.7
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 -10
- package/gui-experimental.cjs +1 -1
- package/index.ts +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
---
|
|
16
16
|
|
|
17
|
-
`/delayed
|
|
17
|
+
`/delayed` schedules a message for a future time and delivers it as a normal user
|
|
18
18
|
message. The trick: the text never enters the model context at scheduling time. The model
|
|
19
19
|
sees it only when it arrives.
|
|
20
20
|
|
|
@@ -50,9 +50,9 @@ Requires OpenCode V2. The pill is part of the desktop app; the command itself wo
|
|
|
50
50
|
## 🚀 Usage
|
|
51
51
|
|
|
52
52
|
```text
|
|
53
|
-
/delayed
|
|
54
|
-
/delayed
|
|
55
|
-
/delayed
|
|
53
|
+
/delayed 03:00 call mom
|
|
54
|
+
/delayed 2026-11-05 03:00 check the deploy
|
|
55
|
+
/delayed +30m stretch
|
|
56
56
|
```
|
|
57
57
|
|
|
58
58
|
| Time | Meaning |
|
|
@@ -82,14 +82,14 @@ Treat this as a prompt-level guarantee, not encryption.
|
|
|
82
82
|
Above the composer, while a message is pending:
|
|
83
83
|
|
|
84
84
|
```text
|
|
85
|
-
⏰
|
|
85
|
+
⏰ 2 delayed
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
-
The label
|
|
89
|
-
Click it for one line per message with the scheduled time, a
|
|
90
|
-
countdown, like the app's own subagent list. `✎` to edit and `×` to cancel appear
|
|
88
|
+
The label carries the pending count, like the subagent pill; the list behind it
|
|
89
|
+
carries the times. Click it for one line per message with the scheduled time, a
|
|
90
|
+
preview, and the countdown, like the app's own subagent list. `✎` to edit and `×` to cancel appear
|
|
91
91
|
when you hover a row. Edit cancels the pending job and puts the whole command back
|
|
92
|
-
into the composer — `/delayed
|
|
92
|
+
into the composer — `/delayed`, the scheduled time, and the full text —
|
|
93
93
|
with the cursor in the prompt. Adjust anything and submit to reschedule. The pill
|
|
94
94
|
disappears when the message is delivered or cancelled.
|
|
95
95
|
|
|
@@ -113,7 +113,7 @@ waits for the next time that directory loads.
|
|
|
113
113
|
|
|
114
114
|
## 🧠 How it works
|
|
115
115
|
|
|
116
|
-
- `ctx.command.transform` registers `/delayed
|
|
116
|
+
- `ctx.command.transform` registers `/delayed`; the executor gets the raw text
|
|
117
117
|
outside the prompt.
|
|
118
118
|
- The job goes to `ctx.storage` (`pending/<uuid>`) and a `setTimeout` chain, split at the
|
|
119
119
|
24.8-day timer cap.
|
package/gui-experimental.cjs
CHANGED
|
@@ -12,4 +12,4 @@ Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{val
|
|
|
12
12
|
.dm-row:hover .dm-actions, .dm-row:has(.dm-action:focus-visible) .dm-actions { opacity: 1; pointer-events: auto; }
|
|
13
13
|
.dm-action { flex: none; width: 22px; height: 22px; border: 0; border-radius: 4px; background: transparent; color: inherit; opacity: .55; cursor: pointer; font: inherit; line-height: 1; }
|
|
14
14
|
.dm-action:hover { opacity: 1; background: var(--v2-overlay-simple-overlay-hover); }
|
|
15
|
-
`,d=r.Plugin.define({i18n:{en:{"pill.
|
|
15
|
+
`,d=r.Plugin.define({i18n:{en:{"pill.count.one":`{{count}} delayed`,"pill.count.other":`{{count}} delayed`,"menu.title":`Scheduled messages`,"row.cancel":`Cancel`,"row.edit":`Edit`,"left.now":`now`,"left.days":`{{days}}d {{hours}}h`,"left.hours":`{{hours}}h {{minutes}}m`,"left.minutes":`{{minutes}}m`},ru:{"pill.count.one":`{{count}} отложено`,"pill.count.other":`{{count}} отложено`,"menu.title":`Отложенные сообщения`,"row.cancel":`Отменить`,"row.edit":`Изменить`,"left.now":`сейчас`,"left.days":`{{days}} д {{hours}} ч`,"left.hours":`{{hours}} ч {{minutes}} мин`,"left.minutes":`{{minutes}} мин`},de:{"pill.count.one":`{{count}} geplant`,"pill.count.other":`{{count}} geplant`,"menu.title":`Geplante Nachrichten`,"row.cancel":`Abbrechen`,"row.edit":`Bearbeiten`,"left.now":`jetzt`,"left.days":`{{days}} T {{hours}} Std.`,"left.hours":`{{hours}} Std. {{minutes}} Min.`,"left.minutes":`{{minutes}} Min.`},fr:{"pill.count.one":`{{count}} programmé`,"pill.count.other":`{{count}} programmés`,"menu.title":`Messages programmés`,"row.cancel":`Annuler`,"row.edit":`Modifier`,"left.now":`maintenant`,"left.days":`{{days}} j {{hours}} h`,"left.hours":`{{hours}} h {{minutes}} min`,"left.minutes":`{{minutes}} min`},zh:{"pill.count.one":`{{count}} 个已安排`,"pill.count.other":`{{count}} 个已安排`,"menu.title":`已安排的消息`,"row.cancel":`取消`,"row.edit":`编辑`,"left.now":`现在`,"left.days":`{{days}} 天 {{hours}} 小时`,"left.hours":`{{hours}} 小时 {{minutes}} 分钟`,"left.minutes":`{{minutes}} 分钟`}},setup(n){let[s,c]=(0,t.createSignal)({}),d=(e,t)=>c(n=>({...n,[e]:[...(n[e]??[]).filter(e=>e.id!==t.id),t].sort((e,t)=>e.fireAt-t.fireAt)})),f=(e,t)=>c(n=>({...n,[e]:(n[e]??[]).filter(e=>e.id!==t)})),p=e=>new Date(e).toLocaleTimeString(n.locale.locale(),{hour:`2-digit`,minute:`2-digit`}),m=e=>new Date(e).toLocaleDateString(n.locale.locale(),{day:`2-digit`,month:`2-digit`}),h=e=>{let t=new Date(e),n=new Date,r=p(e);return t.toDateString()===n.toDateString()?r:`${m(e)} ${r}`},g=e=>{let t=e-Date.now();if(t<=0)return n.t(`left.now`);let r=Math.ceil(t/6e4),i=Math.floor(r/60),a=Math.floor(i/24);return a>0?n.t(`left.days`,{days:a,hours:i%24}):i>0?n.t(`left.hours`,{hours:i,minutes:r%60}):n.t(`left.minutes`,{minutes:r})},_=n.client.rpc(i);_.events.on(`scheduled`,e=>{d(e.data.sessionID,{id:e.data.id,fireAt:e.data.fireAt,preview:e.data.preview})}),_.events.on(`fired`,e=>{f(e.data.sessionID,e.data.id)}),_.events.on(`cancelled`,e=>{f(e.data.sessionID,e.data.id)});let v=async(e,t)=>{try{let{ok:n}=await _.cancel({sessionID:e,id:t});n&&f(e,t)}catch{}};(0,r.createKeyed)(()=>n.sessions.current()?.id,e=>{_.pending({sessionID:e}).then(({jobs:t})=>c(n=>({...n,[e]:t}))).catch(()=>{})}),n.add(r.Style,u),n.add(r.ComposerPill,{id:`pending`,show(e){let t=s()[e.session.id]??[];if(t.length!==0)return{label:n.plural(`pill.count`,t.length),icon:`notifications`,title:n.t(`menu.title`)}},menu:r=>(()=>{var i=a();return(0,e.insert)(i,(0,e.createComponent)(t.For,{get each(){return s()[r.session.id]??[]},children:t=>(()=>{var i=o(),a=i.firstChild,s=a.nextSibling,c=s.nextSibling,u=c.nextSibling.firstChild,d=u.nextSibling;return(0,e.insert)(a,()=>h(t.fireAt)),(0,e.insert)(s,()=>t.preview),(0,e.insert)(c,()=>g(t.fireAt)),u.$$click=()=>{_.edit({sessionID:r.session.id,id:t.id}).then(({ok:e,text:n,fireAt:i})=>{e&&n!==void 0&&i!==void 0&&(f(r.session.id,t.id),r.screen.composer.setText(`/delayed ${l(i)} ${n}`),r.close(),r.screen.composer.focus())}).catch(()=>{})},d.$$click=()=>void v(r.session.id,t.id),(0,e.effect)(t=>{var r=n.t(`row.edit`),i=n.t(`row.edit`),a=n.t(`row.cancel`),o=n.t(`row.cancel`);return r!==t.e&&(0,e.setAttribute)(u,`title`,t.e=r),i!==t.t&&(0,e.setAttribute)(u,`aria-label`,t.t=i),a!==t.a&&(0,e.setAttribute)(d,`title`,t.a=a),o!==t.o&&(0,e.setAttribute)(d,`aria-label`,t.o=o),t},{e:void 0,t:void 0,a:void 0,o:void 0}),i})()})),i})()})},main(e){let t=n.Schema.Struct({id:n.Schema.String,sessionID:n.Schema.String,directory:n.Schema.String,fireAt:n.Schema.Number}),r=e.storage.store(`pending`,{schema:n.Schema.Array(t),initial:[]}),a=new Map,o=new Map,c=e=>{let t=a.get(e);t&&clearTimeout(t),a.delete(e)},l=e=>{c(e),o.delete(e),r.value.some(t=>t.id===e)&&r.set(r.value.filter(t=>t.id!==e))},u=e=>{c(e.id);let t=e.fireAt-Date.now();t>s-1?a.set(e.id,setTimeout(()=>u(e),s-1)):a.set(e.id,setTimeout(()=>void d(e),Math.max(0,t)))},d=async t=>{if(c(t.id),Date.now()<t.fireAt)u(t);else try{let{status:n}=await e.client.rpc(i).deliver({sessionID:t.sessionID,id:t.id},{location:{directory:t.directory}});if(n===`delivered`||n===`missing`){o.delete(t.id),l(t.id);return}if(n===`failed`){let e=(o.get(t.id)??0)+1;if(e>=5){o.delete(t.id),l(t.id);return}o.set(t.id,e)}a.set(t.id,setTimeout(()=>void d(t),n===`busy`?2e3:3e4))}catch{a.set(t.id,setTimeout(()=>void d(t),15e3))}},f=e=>{r.set([...r.value.filter(t=>t.id!==e.id),e]),u(e)},p=e=>{if(e.type===`rpc.delayed-message.scheduled`){let t=e.data;if(!t?.id||!t.sessionID||!t.fireAt||!t.directory)return;f({id:t.id,sessionID:t.sessionID,directory:t.directory,fireAt:t.fireAt})}else if(e.type===`rpc.delayed-message.fired`||e.type===`rpc.delayed-message.cancelled`){let t=e.data;t?.id&&l(t.id)}};for(let e of r.value)u(e);(async()=>{for(;!e.signal.aborted;){try{for await(let t of e.client.event.subscribe({signal:e.signal}))p(t)}catch{}if(e.signal.aborted)break;await new Promise(e=>setTimeout(e,3e3))}})(),e.onCleanup(()=>{for(let e of a.values())clearTimeout(e);a.clear()})}});(0,e.delegateEvents)([`click`]),exports.default=d;
|
package/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Plugin } from "@opencode/plugin"
|
|
2
2
|
import { Delayed } from "./rpc"
|
|
3
3
|
|
|
4
|
-
// /delayed
|
|
4
|
+
// /delayed <time> <message>
|
|
5
5
|
//
|
|
6
6
|
// The command is registered by the plugin, so the message never becomes a user
|
|
7
7
|
// prompt at scheduling time: it stays inside `execute` and the model sees it
|
|
@@ -166,7 +166,7 @@ export default Plugin.define({
|
|
|
166
166
|
|
|
167
167
|
await ctx.command.transform((editor) => {
|
|
168
168
|
editor.add({
|
|
169
|
-
name: "delayed
|
|
169
|
+
name: "delayed",
|
|
170
170
|
description: "Wake this session later with a message",
|
|
171
171
|
execute: async ({ sessionID, prompt }) => {
|
|
172
172
|
try {
|
|
@@ -186,7 +186,7 @@ export default Plugin.define({
|
|
|
186
186
|
if (!time || !message) {
|
|
187
187
|
await alert(
|
|
188
188
|
sessionID,
|
|
189
|
-
"Usage: /delayed
|
|
189
|
+
"Usage: /delayed <time> <message>\n" +
|
|
190
190
|
"Time: HH:MM, YYYY-MM-DDTHH:MM, or +30m / +2h / +1d.",
|
|
191
191
|
)
|
|
192
192
|
return
|