pi-messenger-swarm 0.22.2 → 0.23.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/CHANGELOG.md CHANGED
@@ -2,6 +2,27 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ## [0.23.0](https://github.com/monotykamary/pi-messenger-swarm/compare/v0.22.3...v0.23.0) (2026-04-05)
6
+
7
+
8
+ ### ⚠ BREAKING CHANGES
9
+
10
+ * None - all exports maintained through index re-exports
11
+
12
+ * modularize large handler and store modules ([1e6c1d5](https://github.com/monotykamary/pi-messenger-swarm/commit/1e6c1d53932543af7f2518e3ccf51605549db4d5))
13
+
14
+ ### [0.22.3](https://github.com/monotykamary/pi-messenger-swarm/compare/v0.22.2...v0.22.3) (2026-04-04)
15
+
16
+
17
+ ### Features
18
+
19
+ * **spawn:** add agentFile parameter to spawn tool registration ([e67df89](https://github.com/monotykamary/pi-messenger-swarm/commit/e67df89cf6c9879e53e0f5c101daa7341e481d2c))
20
+
21
+
22
+ ### Bug Fixes
23
+
24
+ * **swarm:** allow spawn with agentFile only, no message required ([8ed3ad5](https://github.com/monotykamary/pi-messenger-swarm/commit/8ed3ad5e43b68341b1e625b3278f838e10529983))
25
+
5
26
  ### [0.22.2](https://github.com/monotykamary/pi-messenger-swarm/compare/v0.22.1...v0.22.2) (2026-04-04)
6
27
 
7
28
 
@@ -0,0 +1,7 @@
1
+ export { executeJoin } from './join.js';
2
+ export { executeStatus, executeSetStatus } from './status.js';
3
+ export { executeList } from './list.js';
4
+ export { executeWhois } from './whois.js';
5
+ export { executeReserve, executeRelease } from './reservations.js';
6
+ export { executeSend, executeFeed } from './messaging.js';
7
+ export { executeRename } from './rename.js';
@@ -0,0 +1,132 @@
1
+ import { existsSync } from 'node:fs';
2
+ import type { ExtensionContext } from '@mariozechner/pi-coding-agent';
3
+ import type { AgentMailMessage, Dirs, MessengerState, NameThemeConfig } from '../../lib.js';
4
+ import { displaySpecPath, extractFolder, resolveSpecPath } from '../../lib.js';
5
+ import { displayChannelLabel } from '../../channel.js';
6
+ import * as store from '../../store.js';
7
+ import { logFeedEvent, pruneFeed } from '../../feed.js';
8
+ import { result } from '../result.js';
9
+
10
+ export function executeJoin(
11
+ state: MessengerState,
12
+ dirs: Dirs,
13
+ ctx: ExtensionContext,
14
+ _deliverFn: (msg: AgentMailMessage) => void,
15
+ updateStatusFn: (ctx: ExtensionContext) => void,
16
+ specPath?: string,
17
+ nameTheme?: NameThemeConfig,
18
+ feedRetention?: number,
19
+ channel?: string,
20
+ create?: boolean
21
+ ) {
22
+ state.isHuman = ctx.hasUI;
23
+ const cwd = ctx.cwd ?? process.cwd();
24
+
25
+ if (!state.registered) {
26
+ if (!store.register(state, dirs, ctx, nameTheme)) {
27
+ return result('Failed to join the agent mesh. Check logs for details.', {
28
+ mode: 'join',
29
+ error: 'registration_failed',
30
+ });
31
+ }
32
+
33
+ if (channel) {
34
+ const switched = store.joinChannel(state, dirs, channel, { create });
35
+ if (!switched.success) {
36
+ const error = (switched as Extract<typeof switched, { success: false }>).error;
37
+ return result(
38
+ error === 'not_found'
39
+ ? `Channel ${displayChannelLabel(channel)} not found.`
40
+ : `Invalid channel: ${channel}`,
41
+ { mode: 'join', error, channel }
42
+ );
43
+ }
44
+ }
45
+
46
+ updateStatusFn(ctx);
47
+ pruneFeed(cwd, feedRetention ?? 50, state.currentChannel);
48
+ logFeedEvent(cwd, state.agentName, 'join', undefined, undefined, state.currentChannel);
49
+ } else if (channel) {
50
+ const switched = store.joinChannel(state, dirs, channel, { create });
51
+ if (!switched.success) {
52
+ const error = (switched as Extract<typeof switched, { success: false }>).error;
53
+ return result(
54
+ error === 'not_found'
55
+ ? `Channel ${displayChannelLabel(channel)} not found.`
56
+ : `Invalid channel: ${channel}`,
57
+ { mode: 'join', error, channel }
58
+ );
59
+ }
60
+ state.chatHistory.clear();
61
+ state.channelPostHistory = [];
62
+ state.unreadCounts.clear();
63
+ state.seenSenders.clear();
64
+ updateStatusFn(ctx);
65
+
66
+ const label = displayChannelLabel(state.currentChannel);
67
+ const text = switched.switched ? `Switched to ${label}.` : `Already in ${label}.`;
68
+
69
+ return result(text, {
70
+ mode: 'join',
71
+ alreadyJoined: !switched.switched,
72
+ name: state.agentName,
73
+ channel: state.currentChannel,
74
+ joinedChannels: [...state.joinedChannels],
75
+ });
76
+ } else {
77
+ const agents = store.getActiveAgents(state, dirs);
78
+ return result(
79
+ `Already joined as ${state.agentName} in ${displayChannelLabel(state.currentChannel)}. ${agents.length} peer${agents.length === 1 ? '' : 's'} active.`,
80
+ {
81
+ mode: 'join',
82
+ alreadyJoined: true,
83
+ name: state.agentName,
84
+ peerCount: agents.length,
85
+ channel: state.currentChannel,
86
+ }
87
+ );
88
+ }
89
+
90
+ let specWarning = '';
91
+ if (specPath) {
92
+ state.spec = resolveSpecPath(specPath, cwd);
93
+ store.updateRegistration(state, dirs, ctx);
94
+ if (!existsSync(state.spec)) {
95
+ specWarning = `\n\nWarning: Spec file not found at ${displaySpecPath(state.spec, cwd)}.`;
96
+ }
97
+ }
98
+
99
+ const agents = store.getActiveAgents(state, dirs);
100
+ const folder = extractFolder(cwd);
101
+ const locationPart = state.gitBranch ? `${folder} on ${state.gitBranch}` : folder;
102
+ const channelLabel = displayChannelLabel(state.currentChannel);
103
+
104
+ let text = `Joined as ${state.agentName} in ${locationPart} on ${channelLabel}. ${agents.length} peer${agents.length === 1 ? '' : 's'} active.`;
105
+
106
+ if (state.spec) {
107
+ text += `\nSpec: ${displaySpecPath(state.spec, cwd)}`;
108
+ }
109
+
110
+ text += `\nJoined channels: ${state.joinedChannels.map(displayChannelLabel).join(', ')}`;
111
+
112
+ if (agents.length > 0) {
113
+ text += `\n\nActive peers: ${agents.map((a) => a.name).join(', ')}`;
114
+ text +=
115
+ '\n\nUse pi_messenger({ action: "list" }) for details, pi_messenger({ action: "task.list" }) for tasks.';
116
+ }
117
+
118
+ if (specWarning) {
119
+ text += specWarning;
120
+ }
121
+
122
+ return result(text, {
123
+ mode: 'join',
124
+ name: state.agentName,
125
+ location: locationPart,
126
+ peerCount: agents.length,
127
+ peers: agents.map((a) => a.name),
128
+ spec: state.spec ? displaySpecPath(state.spec, cwd) : undefined,
129
+ channel: state.currentChannel,
130
+ joinedChannels: [...state.joinedChannels],
131
+ });
132
+ }
@@ -0,0 +1,111 @@
1
+ import type { Dirs, MessengerState, AgentRegistration } from '../../lib.js';
2
+ import {
3
+ STATUS_INDICATORS,
4
+ agentHasTask,
5
+ buildSelfRegistration,
6
+ computeStatus,
7
+ extractFolder,
8
+ } from '../../lib.js';
9
+ import { displayChannelLabel } from '../../channel.js';
10
+ import * as store from '../../store.js';
11
+ import * as taskStore from '../../swarm/task-store.js';
12
+ import { getEffectiveSessionId } from '../../store/shared.js';
13
+ import { formatFeedLine, readFeedEvents } from '../../feed.js';
14
+ import { notRegisteredError, result } from '../result.js';
15
+
16
+ export function executeList(
17
+ state: MessengerState,
18
+ dirs: Dirs,
19
+ cwd: string,
20
+ config?: { stuckThreshold?: number }
21
+ ) {
22
+ if (!state.registered) {
23
+ return notRegisteredError();
24
+ }
25
+
26
+ const thresholdMs = (config?.stuckThreshold ?? 900) * 1000;
27
+ const peers = store.getActiveAgents(state, dirs);
28
+ const folder = extractFolder(cwd);
29
+ const totalCount = peers.length + 1;
30
+
31
+ const lines: string[] = [];
32
+ lines.push(`# Agents (${totalCount} online - project: ${folder})`, '');
33
+ lines.push(`Current channel: ${displayChannelLabel(state.currentChannel)}`);
34
+ lines.push(`Joined channels: ${state.joinedChannels.map(displayChannelLabel).join(', ')}`, '');
35
+
36
+ function formatAgentLine(a: AgentRegistration, isSelf: boolean, hasTask: boolean): string {
37
+ const computed = computeStatus(
38
+ a.activity?.lastActivityAt ?? a.startedAt,
39
+ hasTask,
40
+ (a.reservations?.length ?? 0) > 0,
41
+ thresholdMs
42
+ );
43
+ const indicator = STATUS_INDICATORS[computed.status];
44
+ const nameLabel = isSelf ? `${a.name} (you)` : a.name;
45
+
46
+ const parts: string[] = [`${indicator} ${nameLabel}`];
47
+
48
+ if (a.activity?.currentActivity) {
49
+ parts.push(a.activity.currentActivity);
50
+ } else if (computed.status === 'idle' && computed.idleFor) {
51
+ parts.push(`idle ${computed.idleFor}`);
52
+ } else if (computed.status === 'away' && computed.idleFor) {
53
+ parts.push(`away ${computed.idleFor}`);
54
+ } else if (computed.status === 'stuck' && computed.idleFor) {
55
+ parts.push(`stuck ${computed.idleFor}`);
56
+ }
57
+
58
+ parts.push(`${a.session?.toolCalls ?? 0} tools`);
59
+
60
+ const tokens = a.session?.tokens ?? 0;
61
+ if (tokens >= 1000) {
62
+ parts.push(`${(tokens / 1000).toFixed(1)}k`);
63
+ } else {
64
+ parts.push(`${tokens}`);
65
+ }
66
+
67
+ const preferredChannel = a.currentChannel ?? a.sessionChannel;
68
+ if (preferredChannel) {
69
+ parts.push(displayChannelLabel(preferredChannel));
70
+ }
71
+
72
+ if (a.reservations && a.reservations.length > 0) {
73
+ const resParts = a.reservations.map((r) => r.pattern).join(', ');
74
+ parts.push(`📁 ${resParts}`);
75
+ }
76
+
77
+ if (a.statusMessage) {
78
+ parts.push(a.statusMessage);
79
+ }
80
+
81
+ return parts.join(' - ');
82
+ }
83
+
84
+ const sessionId = getEffectiveSessionId(cwd, state);
85
+ const sessionTasks = taskStore.getTasks(cwd, sessionId);
86
+
87
+ lines.push(
88
+ formatAgentLine(buildSelfRegistration(state), true, agentHasTask(state.agentName, sessionTasks))
89
+ );
90
+
91
+ for (const a of peers) {
92
+ lines.push(formatAgentLine(a, false, agentHasTask(a.name, sessionTasks)));
93
+ }
94
+
95
+ const recentEvents = readFeedEvents(cwd, 5, state.currentChannel);
96
+ if (recentEvents.length > 0) {
97
+ lines.push('', `# Recent Activity ${displayChannelLabel(state.currentChannel)}`, '');
98
+ for (const event of recentEvents) {
99
+ lines.push(formatFeedLine(event));
100
+ }
101
+ }
102
+
103
+ return result(lines.join('\n').trim(), {
104
+ mode: 'list',
105
+ registered: true,
106
+ agents: peers,
107
+ self: state.agentName,
108
+ totalCount,
109
+ channel: state.currentChannel,
110
+ });
111
+ }
@@ -0,0 +1,133 @@
1
+ import type { Dirs, MessengerState } from '../../lib.js';
2
+ import { displayChannelLabel, normalizeChannelId } from '../../channel.js';
3
+ import { findSpawnedAgentByName } from '../../swarm/spawn.js';
4
+ import { getEffectiveSessionId } from '../../store/shared.js';
5
+ import {
6
+ formatFeedLine,
7
+ isSwarmEvent,
8
+ logFeedEvent,
9
+ readFeedEvents,
10
+ type FeedEvent,
11
+ } from '../../feed.js';
12
+ import { notRegisteredError, result } from '../result.js';
13
+
14
+ export function executeSend(
15
+ state: MessengerState,
16
+ _dirs: Dirs,
17
+ cwd: string,
18
+ to: string | string[] | undefined,
19
+ message?: string,
20
+ _replyTo?: string,
21
+ channel?: string
22
+ ) {
23
+ if (!state.registered) {
24
+ return notRegisteredError();
25
+ }
26
+
27
+ if (!message) {
28
+ return result('Error: message is required when sending.', {
29
+ mode: 'send',
30
+ error: 'missing_message',
31
+ });
32
+ }
33
+
34
+ if (
35
+ !to ||
36
+ (Array.isArray(to) && to.length === 0) ||
37
+ (typeof to === 'string' && to.trim().length === 0)
38
+ ) {
39
+ return result("Error: send requires 'to'. Use an agent name, agent list, or #channel.", {
40
+ mode: 'send',
41
+ error: 'missing_recipient',
42
+ });
43
+ }
44
+
45
+ const isChannelTarget = typeof to === 'string' && to.startsWith('#');
46
+ const targetChannel = isChannelTarget ? normalizeChannelId(to) : channel || state.currentChannel;
47
+
48
+ // Check if targeting a completed/failed/stopped spawned agent
49
+ let spawnWarning = '';
50
+ if (typeof to === 'string' && !isChannelTarget) {
51
+ const sessionId = getEffectiveSessionId(cwd, state);
52
+ const spawnedAgent = findSpawnedAgentByName(cwd, sessionId, to);
53
+ if (spawnedAgent && spawnedAgent.status !== 'running') {
54
+ const statusEmoji =
55
+ spawnedAgent.status === 'completed' ? '✅' : spawnedAgent.status === 'failed' ? '❌' : '🛑';
56
+ spawnWarning = `\n\n⚠️ Warning: ${to} is a spawned agent that has already ${spawnedAgent.status} ${statusEmoji}. The message will be logged to the feed, but the agent process is no longer active.`;
57
+ if (spawnedAgent.status === 'completed') {
58
+ spawnWarning += `\n If you need to continue the work, consider spawning a new agent.`;
59
+ } else if (spawnedAgent.status === 'failed') {
60
+ spawnWarning += `\n The agent failed with errors. Review the task and consider respawning.`;
61
+ }
62
+ }
63
+ }
64
+
65
+ // All messaging is now feed-based
66
+ logFeedEvent(
67
+ cwd,
68
+ state.agentName,
69
+ 'message',
70
+ typeof to === 'string' ? to : undefined,
71
+ message,
72
+ targetChannel
73
+ );
74
+
75
+ const targetLabel = typeof to === 'string' ? to : 'multiple recipients';
76
+ const channelLabel = displayChannelLabel(targetChannel);
77
+ // If the target is already a channel reference, just say "posted to #channel"
78
+ let text = isChannelTarget
79
+ ? `Message posted to ${targetLabel}.`
80
+ : `Message posted to ${targetLabel} on ${channelLabel}.`;
81
+
82
+ // Append warning if targeting a completed/failed/stopped agent
83
+ text += spawnWarning;
84
+
85
+ return result(text, {
86
+ mode: 'send',
87
+ channel: targetChannel,
88
+ to: typeof to === 'string' ? to : undefined,
89
+ warning: spawnWarning ? 'target_agent_completed' : undefined,
90
+ });
91
+ }
92
+
93
+ export function executeFeed(
94
+ cwd: string,
95
+ currentChannel: string,
96
+ limit?: number,
97
+ swarmEventsInFeed: boolean = true,
98
+ requestedChannel?: string
99
+ ) {
100
+ const channelId = requestedChannel ? normalizeChannelId(requestedChannel) : currentChannel;
101
+ const effectiveLimit = limit ?? 20;
102
+ let events: FeedEvent[];
103
+ if (!swarmEventsInFeed) {
104
+ events = readFeedEvents(cwd, effectiveLimit * 2, channelId);
105
+ events = events.filter((e) => !isSwarmEvent(e.type));
106
+ events = events.slice(-effectiveLimit);
107
+ } else {
108
+ events = readFeedEvents(cwd, effectiveLimit, channelId);
109
+ }
110
+
111
+ if (events.length === 0) {
112
+ return result(`# Activity Feed ${displayChannelLabel(channelId)}\n\nNo activity yet.`, {
113
+ mode: 'feed',
114
+ channel: channelId,
115
+ events: [],
116
+ });
117
+ }
118
+
119
+ const lines: string[] = [
120
+ `# Activity Feed ${displayChannelLabel(channelId)} (last ${events.length})`,
121
+ '',
122
+ ];
123
+ for (const event of events) {
124
+ lines.push(formatFeedLine(event));
125
+ }
126
+
127
+ return result(lines.join('\n'), {
128
+ mode: 'feed',
129
+ channel: channelId,
130
+ events: events.map((e) => ({ ...e, preview: e.preview ?? undefined })),
131
+ count: events.length,
132
+ });
133
+ }
@@ -0,0 +1,31 @@
1
+ import type { ExtensionContext } from '@mariozechner/pi-coding-agent';
2
+ import type { AgentMailMessage, Dirs, MessengerState } from '../../lib.js';
3
+ import * as store from '../../store.js';
4
+ import { notRegisteredError, result } from '../result.js';
5
+
6
+ export function executeRename(
7
+ state: MessengerState,
8
+ dirs: Dirs,
9
+ ctx: ExtensionContext,
10
+ newName: string,
11
+ _deliverMessage?: (msg: AgentMailMessage) => void,
12
+ _updateStatus?: (ctx: ExtensionContext) => void
13
+ ) {
14
+ if (!state.registered) {
15
+ return notRegisteredError();
16
+ }
17
+
18
+ const result_data = store.renameAgent(state, dirs, ctx, newName, () => {});
19
+
20
+ if (result_data.success === false) {
21
+ return result(`Error: ${result_data.error}`, { mode: 'rename', error: result_data.error });
22
+ }
23
+
24
+ store.updateRegistration(state, dirs, ctx);
25
+
26
+ return result(`Renamed from ${result_data.oldName} to ${result_data.newName}`, {
27
+ mode: 'rename',
28
+ oldName: result_data.oldName,
29
+ newName: result_data.newName,
30
+ });
31
+ }
@@ -0,0 +1,85 @@
1
+ import type { ExtensionContext } from '@mariozechner/pi-coding-agent';
2
+ import type { Dirs, MessengerState } from '../../lib.js';
3
+ import * as store from '../../store.js';
4
+ import { notRegisteredError, result } from '../result.js';
5
+
6
+ export function executeReserve(
7
+ state: MessengerState,
8
+ dirs: Dirs,
9
+ ctx: ExtensionContext,
10
+ paths: string[],
11
+ reason?: string
12
+ ) {
13
+ if (!state.registered) {
14
+ return notRegisteredError();
15
+ }
16
+
17
+ const conflicts = store.getConflictsWithOtherAgents(paths[0] ?? '', state, dirs);
18
+ if (conflicts.length > 0) {
19
+ const conflictList = conflicts
20
+ .map((c) => ` - ${c.agent}: ${c.pattern}${c.reason ? ` (${c.reason})` : ''}`)
21
+ .join('\n');
22
+ return result(`Cannot reserve: conflicting reservations found:\n${conflictList}`, {
23
+ mode: 'reserve',
24
+ error: 'conflict',
25
+ conflicts,
26
+ });
27
+ }
28
+
29
+ for (const pattern of paths) {
30
+ state.reservations.push({
31
+ pattern,
32
+ reason,
33
+ since: new Date().toISOString(),
34
+ });
35
+ }
36
+
37
+ store.updateRegistration(state, dirs, ctx);
38
+
39
+ const lines = ['Reserved paths:', ...paths.map((p) => ` - ${p}`)];
40
+ if (reason) lines.push(`Reason: ${reason}`);
41
+
42
+ return result(lines.join('\n'), { mode: 'reserve', paths, reason });
43
+ }
44
+
45
+ export function executeRelease(
46
+ state: MessengerState,
47
+ dirs: Dirs,
48
+ ctx: ExtensionContext,
49
+ paths: string[] | true
50
+ ) {
51
+ if (!state.registered) {
52
+ return notRegisteredError();
53
+ }
54
+
55
+ const released: string[] = [];
56
+ const notFound: string[] = [];
57
+
58
+ if (paths === true) {
59
+ // Release all reservations
60
+ released.push(...state.reservations.map((r) => r.pattern));
61
+ state.reservations.length = 0;
62
+ } else {
63
+ for (const pattern of paths) {
64
+ const idx = state.reservations.findIndex((r) => r.pattern === pattern);
65
+ if (idx >= 0) {
66
+ state.reservations.splice(idx, 1);
67
+ released.push(pattern);
68
+ } else {
69
+ notFound.push(pattern);
70
+ }
71
+ }
72
+ }
73
+
74
+ store.updateRegistration(state, dirs, ctx);
75
+
76
+ const lines: string[] = [];
77
+ if (released.length > 0) {
78
+ lines.push('Released paths:', ...released.map((p) => ` - ${p}`));
79
+ }
80
+ if (notFound.length > 0) {
81
+ lines.push('Not found:', ...notFound.map((p) => ` - ${p}`));
82
+ }
83
+
84
+ return result(lines.join('\n'), { mode: 'release', released, notFound });
85
+ }
@@ -0,0 +1,74 @@
1
+ import type { ExtensionContext } from '@mariozechner/pi-coding-agent';
2
+ import type { Dirs, MessengerState } from '../../lib.js';
3
+ import { extractFolder, truncatePathLeft } from '../../lib.js';
4
+ import { displayChannelLabel } from '../../channel.js';
5
+ import * as store from '../../store.js';
6
+ import * as taskStore from '../../swarm/task-store.js';
7
+ import { getEffectiveSessionId } from '../../store/shared.js';
8
+ import { notRegisteredError, result } from '../result.js';
9
+
10
+ export function executeStatus(state: MessengerState, dirs: Dirs, cwd: string = process.cwd()) {
11
+ if (!state.registered) {
12
+ return notRegisteredError();
13
+ }
14
+
15
+ const agents = store.getActiveAgents(state, dirs);
16
+ const folder = extractFolder(cwd);
17
+ const location = state.gitBranch ? `${folder} (${state.gitBranch})` : folder;
18
+ const sessionId = getEffectiveSessionId(cwd, state);
19
+ const myClaim = taskStore
20
+ .getTasks(cwd, sessionId)
21
+ .find((task) => task.status === 'in_progress' && task.claimed_by === state.agentName);
22
+
23
+ let text = `You: ${state.agentName}\n`;
24
+ text += `Location: ${location}\n`;
25
+ text += `On: ${displayChannelLabel(state.currentChannel)}\n`;
26
+ if (myClaim) {
27
+ text += `Claim: ${myClaim.id}${myClaim.blocked_reason ? ` - ${myClaim.blocked_reason}` : ''}\n`;
28
+ }
29
+
30
+ text += `Peers: ${agents.length}\n`;
31
+ if (state.reservations.length > 0) {
32
+ const myRes = state.reservations.map((r) => `🔒 ${truncatePathLeft(r.pattern, 40)}`);
33
+ text += `Reservations: ${myRes.join(', ')}\n`;
34
+ }
35
+ text += `Joined channels: ${state.joinedChannels.map(displayChannelLabel).join(', ')}\n`;
36
+ text +=
37
+ '\nUse pi_messenger({ action: "list" }) for details, pi_messenger({ action: "task.list" }) for tasks.';
38
+
39
+ return result(text, {
40
+ mode: 'status',
41
+ registered: true,
42
+ self: state.agentName,
43
+ folder,
44
+ gitBranch: state.gitBranch,
45
+ peerCount: agents.length,
46
+ channel: state.currentChannel,
47
+ joinedChannels: [...state.joinedChannels],
48
+ claim: myClaim
49
+ ? {
50
+ id: myClaim.id,
51
+ title: myClaim.title,
52
+ claimedBy: myClaim.claimed_by,
53
+ }
54
+ : undefined,
55
+ reservations: state.reservations,
56
+ });
57
+ }
58
+
59
+ export function executeSetStatus(
60
+ state: MessengerState,
61
+ dirs: Dirs,
62
+ ctx: ExtensionContext,
63
+ message: string
64
+ ) {
65
+ if (!state.registered) {
66
+ return notRegisteredError();
67
+ }
68
+
69
+ state.statusMessage = message;
70
+ state.customStatus = true;
71
+ store.updateRegistration(state, dirs, ctx);
72
+
73
+ return result(`Status set to: ${message}`, { mode: 'set_status', message });
74
+ }