@rubytech/taskmaster 1.17.10 → 1.17.11

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.
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.17.10",
2
+ "version": "1.17.11",
3
3
  "commit": "09e41f83ec85775a3f01997ed008c699dc6e60e4",
4
- "builtAt": "2026-03-05T16:13:13.586Z"
4
+ "builtAt": "2026-03-05T16:45:44.369Z"
5
5
  }
@@ -86,22 +86,10 @@ export async function executeJob(state, job, nowMs, opts) {
86
86
  job.state.nextRunAtMs = undefined;
87
87
  }
88
88
  }
89
- emit(state, {
90
- jobId: job.id,
91
- action: "finished",
92
- status,
93
- error: err,
94
- summary,
95
- outputText,
96
- runAtMs: startedAt,
97
- durationMs: job.state.lastDurationMs,
98
- nextRunAtMs: job.state.nextRunAtMs,
99
- });
100
- if (shouldDelete && state.store) {
101
- state.store.jobs = state.store.jobs.filter((j) => j.id !== job.id);
102
- deleted = true;
103
- emit(state, { jobId: job.id, action: "removed" });
104
- }
89
+ // For isolated jobs, perform delivery before emitting "finished" so the
90
+ // emitted status reflects what actually happened (web-chat injection
91
+ // counts as "ok", not "skipped").
92
+ let emitStatus = status;
105
93
  if (job.sessionTarget === "isolated") {
106
94
  const prefix = job.isolation?.postToMainPrefix?.trim() || "Cron";
107
95
  const configuredMode = job.isolation?.postToMainMode ?? "summary";
@@ -129,7 +117,12 @@ export async function executeJob(state, job, nowMs, opts) {
129
117
  message: body,
130
118
  label: statusPrefix,
131
119
  });
132
- if (!injected) {
120
+ if (injected) {
121
+ // Successfully delivered to web chat — treat as ok.
122
+ emitStatus = "ok";
123
+ job.state.lastStatus = "ok";
124
+ }
125
+ else {
133
126
  // Session doesn't exist yet — fall back to system event.
134
127
  state.deps.enqueueSystemEvent(`${statusPrefix}: ${body}`, {
135
128
  agentId: job.agentId,
@@ -145,6 +138,22 @@ export async function executeJob(state, job, nowMs, opts) {
145
138
  }
146
139
  }
147
140
  }
141
+ emit(state, {
142
+ jobId: job.id,
143
+ action: "finished",
144
+ status: emitStatus,
145
+ error: err,
146
+ summary,
147
+ outputText,
148
+ runAtMs: startedAt,
149
+ durationMs: job.state.lastDurationMs,
150
+ nextRunAtMs: job.state.nextRunAtMs,
151
+ });
152
+ if (shouldDelete && state.store) {
153
+ state.store.jobs = state.store.jobs.filter((j) => j.id !== job.id);
154
+ deleted = true;
155
+ emit(state, { jobId: job.id, action: "removed" });
156
+ }
148
157
  };
149
158
  try {
150
159
  if (job.sessionTarget === "main") {
@@ -52,15 +52,20 @@ export function buildGatewayCronService(params) {
52
52
  try {
53
53
  const { agentId, cfg: runtimeConfig } = resolveCronAgent(reqAgentId);
54
54
  const sessionKey = resolveAgentMainSessionKey({ cfg: runtimeConfig, agentId });
55
+ cronLogger.info({ reqAgentId, agentId, sessionKey }, "cron: injectToMainSession: attempting");
55
56
  const { storePath: sPath, entry } = loadSessionEntry(sessionKey);
56
57
  const sessionId = entry?.sessionId;
57
- if (!sessionId || !sPath)
58
+ if (!sessionId || !sPath) {
59
+ cronLogger.warn({ agentId, sessionKey, hasEntry: !!entry, sessionId: sessionId ?? null }, "cron: injectToMainSession: no session — falling back to system event");
58
60
  return false;
61
+ }
59
62
  const transcriptPath = entry?.sessionFile
60
63
  ? entry.sessionFile
61
64
  : path.join(path.dirname(sPath), `${sessionId}.jsonl`);
62
- if (!existsSync(transcriptPath))
65
+ if (!existsSync(transcriptPath)) {
66
+ cronLogger.warn({ agentId, sessionKey, sessionId, transcriptPath }, "cron: injectToMainSession: transcript not found — falling back to system event");
63
67
  return false;
68
+ }
64
69
  const now = Date.now();
65
70
  const messageId = randomUUID().slice(0, 8);
66
71
  const labelPrefix = label ? `[${label}]\n\n` : "";
@@ -81,13 +86,14 @@ export function buildGatewayCronService(params) {
81
86
  params.broadcast("chat", {
82
87
  runId: `inject-${messageId}`,
83
88
  sessionKey,
84
- seq: 0,
85
89
  state: "final",
86
90
  message: messageBody,
87
91
  });
92
+ cronLogger.info({ agentId, sessionKey, sessionId, messageId }, "cron: injectToMainSession: injected");
88
93
  return true;
89
94
  }
90
- catch {
95
+ catch (err) {
96
+ cronLogger.warn({ err: String(err) }, "cron: injectToMainSession: exception");
91
97
  return false;
92
98
  }
93
99
  },
@@ -2,7 +2,7 @@ import { resolveChannelDefaultAccountId } from "../../channels/plugins/helpers.j
2
2
  import { getChannelPlugin, listChannelPlugins, normalizeChannelId, } from "../../channels/plugins/index.js";
3
3
  import { buildChannelUiCatalog } from "../../channels/plugins/catalog.js";
4
4
  import { buildChannelAccountSnapshot } from "../../channels/plugins/status.js";
5
- import { loadConfig, readConfigFileSnapshot } from "../../config/config.js";
5
+ import { loadConfig, readConfigFileSnapshot, writeConfigFile } from "../../config/config.js";
6
6
  import { getChannelActivity } from "../../infra/channel-activity.js";
7
7
  import { DEFAULT_ACCOUNT_ID } from "../../routing/session-key.js";
8
8
  import { defaultRuntime } from "../../runtime.js";
@@ -28,6 +28,23 @@ export async function logoutChannelAccount(params) {
28
28
  const loggedOut = typeof result.loggedOut === "boolean" ? result.loggedOut : cleared;
29
29
  if (loggedOut) {
30
30
  params.context.markChannelLoggedOut(params.channelId, true, resolvedAccountId);
31
+ // Remove auto-created paired admin bindings for this channel + account.
32
+ // These are created by ensurePairedAdminBinding() on QR pairing (meta.paired === true).
33
+ // Manual admin bindings (no meta.paired) are untouched.
34
+ const freshCfg = loadConfig();
35
+ const allBindings = freshCfg.bindings ?? [];
36
+ const effectiveAccount = resolvedAccountId || "default";
37
+ const filtered = allBindings.filter((b) => {
38
+ if (b.meta?.paired !== true)
39
+ return true;
40
+ if (b.match.channel !== params.channelId)
41
+ return true;
42
+ const bAccount = b.match.accountId ?? "default";
43
+ return bAccount !== effectiveAccount;
44
+ });
45
+ if (filtered.length < allBindings.length) {
46
+ await writeConfigFile({ ...freshCfg, bindings: filtered });
47
+ }
31
48
  }
32
49
  return {
33
50
  channel: params.channelId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/taskmaster",
3
- "version": "1.17.10",
3
+ "version": "1.17.11",
4
4
  "description": "AI-powered business assistant for small businesses",
5
5
  "publishConfig": {
6
6
  "access": "public"