@cmodjs/core 1.0.0 → 1.5.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.
Files changed (122) hide show
  1. package/docs/commands.md +22 -11
  2. package/docs/dependencies.md +6 -4
  3. package/docs/hooks.md +26 -3
  4. package/docs/index.md +1 -1
  5. package/docs/install-steps.md +5 -3
  6. package/docs/mod.md +29 -3
  7. package/docs/permissions.md +4 -4
  8. package/docs/session.md +126 -63
  9. package/docs/testing.md +7 -5
  10. package/docs/ui.md +9 -1
  11. package/jobs/program.d.ts.map +1 -1
  12. package/jobs/program.js +7 -2
  13. package/mod.d.ts +30 -6
  14. package/mod.d.ts.map +1 -1
  15. package/mod.js +34 -1
  16. package/package.json +1 -1
  17. package/records.d.ts +4 -1
  18. package/records.d.ts.map +1 -1
  19. package/records.js +6 -1
  20. package/register.d.ts.map +1 -1
  21. package/register.js +17 -0
  22. package/runtime/announce.d.ts.map +1 -1
  23. package/runtime/announce.js +2 -0
  24. package/runtime/before.d.ts +32 -0
  25. package/runtime/before.d.ts.map +1 -0
  26. package/runtime/before.js +214 -0
  27. package/runtime/claude.d.ts +2 -1
  28. package/runtime/claude.d.ts.map +1 -1
  29. package/runtime/conversations.d.ts +1 -0
  30. package/runtime/conversations.d.ts.map +1 -1
  31. package/runtime/conversations.js +333 -61
  32. package/runtime/dependencies.js +1 -1
  33. package/runtime/engine.d.ts +2 -6
  34. package/runtime/engine.d.ts.map +1 -1
  35. package/runtime/grants.d.ts.map +1 -1
  36. package/runtime/grants.js +8 -8
  37. package/runtime/hooks.d.ts +1 -1
  38. package/runtime/hooks.d.ts.map +1 -1
  39. package/runtime/hooks.js +19 -4
  40. package/runtime/installer.d.ts +1 -1
  41. package/runtime/installer.d.ts.map +1 -1
  42. package/runtime/installer.js +6 -5
  43. package/runtime/lifecycle.d.ts.map +1 -1
  44. package/runtime/lifecycle.js +129 -58
  45. package/runtime/metadata.d.ts.map +1 -1
  46. package/runtime/metadata.js +5 -4
  47. package/runtime/sessions.d.ts +26 -4
  48. package/runtime/sessions.d.ts.map +1 -1
  49. package/runtime/sessions.js +315 -117
  50. package/runtime/toaster.d.ts +5 -12
  51. package/runtime/toaster.d.ts.map +1 -1
  52. package/runtime/toaster.js +133 -23
  53. package/runtime/ui.d.ts.map +1 -1
  54. package/runtime/ui.js +26 -6
  55. package/session/context/context.d.ts +3 -1
  56. package/session/context/context.d.ts.map +1 -1
  57. package/session/context/turn/message/message.d.ts +1 -0
  58. package/session/context/turn/message/message.d.ts.map +1 -1
  59. package/session/context/turn/message/message.js +42 -1
  60. package/session/context/turn/turn.d.ts +2 -1
  61. package/session/context/turn/turn.d.ts.map +1 -1
  62. package/session/conversation.d.ts +20 -11
  63. package/session/conversation.d.ts.map +1 -1
  64. package/session/conversation.js +50 -27
  65. package/session/goal/goal.d.ts +3 -2
  66. package/session/goal/goal.d.ts.map +1 -1
  67. package/session/goal/task.d.ts +4 -4
  68. package/session/goal/task.d.ts.map +1 -1
  69. package/session/session.d.ts +80 -21
  70. package/session/session.d.ts.map +1 -1
  71. package/session/session.js +1 -0
  72. package/src/jobs/program.ts +7 -2
  73. package/src/mod.ts +48 -5
  74. package/src/records.ts +6 -1
  75. package/src/register.ts +17 -0
  76. package/src/runtime/announce.ts +2 -0
  77. package/src/runtime/before.ts +200 -0
  78. package/src/runtime/claude.ts +3 -0
  79. package/src/runtime/conversations.ts +337 -60
  80. package/src/runtime/dependencies.ts +1 -1
  81. package/src/runtime/engine.ts +2 -1
  82. package/src/runtime/grants.ts +8 -10
  83. package/src/runtime/hooks.ts +20 -4
  84. package/src/runtime/installer.ts +6 -5
  85. package/src/runtime/lifecycle.ts +135 -55
  86. package/src/runtime/metadata.ts +5 -4
  87. package/src/runtime/sessions.ts +316 -111
  88. package/src/runtime/toaster.ts +160 -44
  89. package/src/runtime/ui.ts +23 -4
  90. package/src/session/context/context.ts +3 -1
  91. package/src/session/context/turn/message/message.ts +39 -1
  92. package/src/session/context/turn/turn.ts +2 -1
  93. package/src/session/conversation.ts +56 -31
  94. package/src/session/goal/goal.ts +3 -2
  95. package/src/session/goal/task.ts +4 -4
  96. package/src/session/session.ts +62 -15
  97. package/src/testing/fake-claude.ts +4 -2
  98. package/src/testing/fake-sessions.ts +94 -20
  99. package/src/testing.ts +27 -3
  100. package/src/utils/call-effects.ts +2 -2
  101. package/src/utils/disposable.ts +3 -0
  102. package/src/utils/matcher.ts +1 -1
  103. package/src/utils/paths.ts +6 -3
  104. package/testing/fake-claude.d.ts +1 -0
  105. package/testing/fake-claude.d.ts.map +1 -1
  106. package/testing/fake-claude.js +3 -1
  107. package/testing/fake-sessions.d.ts +8 -1
  108. package/testing/fake-sessions.d.ts.map +1 -1
  109. package/testing/fake-sessions.js +97 -18
  110. package/testing.d.ts +1 -0
  111. package/testing.d.ts.map +1 -1
  112. package/testing.js +23 -2
  113. package/utils/call-effects.d.ts +1 -0
  114. package/utils/call-effects.d.ts.map +1 -1
  115. package/utils/call-effects.js +1 -1
  116. package/utils/disposable.d.ts +4 -0
  117. package/utils/disposable.d.ts.map +1 -0
  118. package/utils/disposable.js +1 -0
  119. package/utils/matcher.js +1 -1
  120. package/utils/paths.d.ts +2 -1
  121. package/utils/paths.d.ts.map +1 -1
  122. package/utils/paths.js +6 -3
@@ -1,11 +1,14 @@
1
+ import { ToolResult } from '../session/context/turn/message/tool-result.js';
1
2
  import type { SessionChange, SessionData } from '../session/conversation.js';
2
- import { type SessionEvent, type Sessions, type Usage } from '../session/session.js';
3
+ import { type BeforeEvent, type SessionEvent, type Sessions, type Usage } from '../session/session.js';
3
4
  import { type SentMatcher } from '../utils/matcher.js';
5
+ import { type Place } from './before.js';
4
6
  import type { Claude } from './claude.js';
5
7
  export type Subscription = {
6
8
  readonly event: SessionEvent;
7
9
  readonly matcher: SentMatcher | undefined;
8
10
  };
11
+ export declare const everySession = "*";
9
12
  export declare const conversationMethods: {
10
13
  readonly sessions: 'cmod:sessions';
11
14
  readonly search: 'cmod:search';
@@ -15,22 +18,41 @@ export declare const conversationMethods: {
15
18
  readonly tasks: 'cmod:tasks';
16
19
  readonly goal: 'cmod:goal';
17
20
  readonly updateGoal: 'cmod:updateGoal';
21
+ readonly usage: 'cmod:usage';
18
22
  readonly subscribe: 'cmod:subscribe';
19
- readonly send: 'cmod:send';
23
+ readonly watchBefore: 'cmod:watchBefore';
24
+ readonly act: 'cmod:act';
20
25
  readonly changed: 'cmod:changed';
26
+ readonly before: 'cmod:before';
27
+ };
28
+ export type Act = 'send' | 'startTask' | 'callTool' | 'usage' | 'clients';
29
+ export declare const beforePermission: (event: BeforeEvent) => string;
30
+ export declare const isAct: (action: string) => action is Act;
31
+ export declare function toolPermission(tool: string): string | undefined;
32
+ export declare function actPermission(action: Act, input: unknown): string | undefined;
33
+ export type Scope = {
34
+ readonly session?: string;
35
+ readonly context?: number;
36
+ readonly turn?: string;
37
+ readonly task?: string;
38
+ readonly isGoal?: true;
21
39
  };
22
40
  export type SessionsClient = {
23
41
  readonly sessions: Sessions;
24
- changed(session: string, change: SessionChange): void;
42
+ changed(session: string, change: SessionChange, isInProject: boolean): void;
43
+ answerBefore(session: string, event: BeforeEvent, change: SessionChange, isInProject: boolean): Promise<unknown>;
25
44
  /** An empty title keeps the title the session already has. */
26
45
  follow(current: Omit<SessionData, 'isRunning' | 'lastAt'>): void;
27
46
  };
28
47
  export declare function usageOf(claude: Pick<Claude, 'session'>): Promise<Usage>;
29
48
  export declare function payloadOf(change: SessionChange): unknown;
30
49
  export declare function isHeardBy(event: SessionEvent, matcher: unknown, change: SessionChange): boolean;
31
- export declare function createSessionsClient({ name, claude, report }: {
50
+ export declare function isInScope(scope: Scope, session: string, change: SessionChange, isUnder: (task: string, ancestor: string) => boolean): boolean;
51
+ export declare function createSessionsClient({ name, claude, report, isGranted }: {
32
52
  readonly name: string;
33
53
  readonly claude: Claude;
34
54
  readonly report: (id: string, message: string, error: unknown) => void;
55
+ readonly isGranted: (permission: string) => boolean;
35
56
  }): SessionsClient;
57
+ export declare function resultOf(where: Pick<Place, 'session' | 'context' | 'turn'>, answered: Awaited<ReturnType<Claude['tool']['call']>>): ToolResult;
36
58
  //# sourceMappingURL=sessions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../src/runtime/sessions.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAyB,aAAa,EAAE,WAAW,EAAsB,MAAM,4BAA4B,CAAA;AAGvH,OAAO,EAAW,KAAK,YAAY,EAAuC,KAAK,QAAQ,EAAsB,KAAK,KAAK,EAAE,MAAM,uBAAuB,CAAA;AACtJ,OAAO,EAAwB,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAC5E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAGzC,MAAM,MAAM,YAAY,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,WAAW,GAAG,SAAS,CAAA;CAAE,CAAA;AAEtG,eAAO,MAAM,mBAAmB;aAC9B,QAAQ,EAAE,eAAe;aACzB,MAAM,EAAE,aAAa;aACrB,QAAQ,EAAE,eAAe;aACzB,KAAK,EAAE,YAAY;aACnB,QAAQ,EAAE,eAAe;aACzB,KAAK,EAAE,YAAY;aACnB,IAAI,EAAE,WAAW;aACjB,UAAU,EAAE,iBAAiB;aAC7B,SAAS,EAAE,gBAAgB;aAC3B,IAAI,EAAE,WAAW;aACjB,OAAO,EAAE,cAAc;CACf,CAAA;AAEV,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAA;IAC3B,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,IAAI,CAAA;IACrD,8DAA8D;IAC9D,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,WAAW,GAAG,QAAQ,CAAC,GAAG,IAAI,CAAA;CACjE,CAAA;AAID,wBAAsB,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAI7E;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAMxD;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO,CAG/F;AAED,wBAAgB,oBAAoB,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;CAAE,GAAG,cAAc,CAuLzM"}
1
+ {"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../src/runtime/sessions.ts"],"names":[],"mappings":"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"}
@@ -1,9 +1,16 @@
1
+ import { ContextEnd, ContextStart } from '../session/context/context.js';
1
2
  import { Message } from '../session/context/turn/message/message.js';
2
3
  import { Note } from '../session/context/turn/message/note.js';
3
4
  import { Person } from '../session/context/turn/message/sender.js';
5
+ import { ToolResult } from '../session/context/turn/message/tool-result.js';
6
+ import { TurnEnd, TurnStart } from '../session/context/turn/turn.js';
7
+ import { GoalChange } from '../session/goal/goal.js';
8
+ import { TaskEnd, TaskStart } from '../session/goal/task.js';
4
9
  import { Session } from '../session/session.js';
5
10
  import { matches, sentMatcher } from '../utils/matcher.js';
11
+ import { foldAnswers } from './before.js';
6
12
  import { cmodPluginName } from './dependencies.js';
13
+ export const everySession = '*';
7
14
  export const conversationMethods = {
8
15
  sessions: 'cmod:sessions',
9
16
  search: 'cmod:search',
@@ -13,10 +20,25 @@ export const conversationMethods = {
13
20
  tasks: 'cmod:tasks',
14
21
  goal: 'cmod:goal',
15
22
  updateGoal: 'cmod:updateGoal',
23
+ usage: 'cmod:usage',
16
24
  subscribe: 'cmod:subscribe',
17
- send: 'cmod:send',
25
+ watchBefore: 'cmod:watchBefore',
26
+ act: 'cmod:act',
18
27
  changed: 'cmod:changed',
28
+ before: 'cmod:before',
19
29
  };
30
+ const actPermissions = { send: 'prompt', startTask: 'tasks', usage: 'conversation', clients: 'conversation' };
31
+ const startsTurns = ['CronCreate'];
32
+ const ownSchedule = ['CronDelete', 'CronList'];
33
+ export const beforePermission = (event) => (event === 'toolCall' ? 'tools' : event === TaskStart || event === TaskEnd ? 'tasks' : 'prompt');
34
+ export const isAct = (action) => action === 'callTool' || action in actPermissions;
35
+ export function toolPermission(tool) {
36
+ return startsTurns.includes(tool) ? 'prompt' : ownSchedule.includes(tool) ? undefined : 'tools';
37
+ }
38
+ export function actPermission(action, input) {
39
+ return action === 'callTool' ? (toolPermission(String(input?.name)) ?? 'tools') : actPermissions[action];
40
+ }
41
+ const asked = (action) => `cmod: ${action} runs only on a session's own main loop, and Claude Code gives a subagent's tasks no way to start a task or run a tool.`;
20
42
  export async function usageOf(claude) {
21
43
  const [model, usage] = await Promise.all([claude.session.model(), claude.session.usage()]);
22
44
  const { tokens, window, percent } = usage.context;
@@ -29,65 +51,233 @@ export function payloadOf(change) {
29
51
  return change.turn;
30
52
  if ('context' in change)
31
53
  return change.context;
32
- if ('task' in change)
33
- return change.task;
54
+ if ('task' in change) {
55
+ const { id, title, agentType, model, openedBy, openedAt, closedAt } = change.task;
56
+ return { id, title, openedBy, openedAt, ...(closedAt === undefined ? {} : { closedAt }), owner: { id, title, agentType, ...(model === undefined ? {} : { model }), startedAt: openedAt } };
57
+ }
58
+ if ('usage' in change)
59
+ return change.usage;
34
60
  return change.goal;
35
61
  }
36
62
  export function isHeardBy(event, matcher, change) {
37
- const named = change.type === Message ? (event === Message || event === change.message.type) : event === change.type;
63
+ const named = change.type === Message ? event === Message || event === change.message.type : event === change.type;
38
64
  return named && (matcher === undefined || matches(payloadOf(change), matcher));
39
65
  }
40
- export function createSessionsClient({ name, claude, report }) {
66
+ function taskOfChange(change) {
67
+ if (change.type === Message)
68
+ return change.message.task;
69
+ if (change.type === TaskStart || change.type === TaskEnd)
70
+ return change.task.id === '' ? change.task.parent : change.task.id;
71
+ if (change.type === TurnStart || change.type === TurnEnd)
72
+ return change.turn.prompt.task;
73
+ if (change.type === ContextStart || change.type === ContextEnd)
74
+ return change.context.task;
75
+ if (change.type === GoalChange)
76
+ return change.goal.task;
77
+ return undefined;
78
+ }
79
+ function ownerOfChange(change) {
80
+ if (change.type === TaskStart || change.type === TaskEnd)
81
+ return change.task.parent;
82
+ return taskOfChange(change);
83
+ }
84
+ function contextOfChange(change) {
85
+ if (change.type === Message)
86
+ return change.message.context;
87
+ if (change.type === TurnStart || change.type === TurnEnd)
88
+ return change.turn.context;
89
+ if (change.type === ContextStart || change.type === ContextEnd)
90
+ return change.context.number;
91
+ if (change.type === TaskStart || change.type === TaskEnd)
92
+ return change.task.context;
93
+ return undefined;
94
+ }
95
+ export function isInScope(scope, session, change, isUnder) {
96
+ if (scope.session !== undefined && scope.session !== session)
97
+ return false;
98
+ const task = taskOfChange(change);
99
+ if (scope.task !== undefined && (task === undefined || !isUnder(task, scope.task)))
100
+ return false;
101
+ if (scope.isGoal === true && change.type !== GoalChange && change.type !== TaskStart && (task === undefined || task === scope.task))
102
+ return false;
103
+ if (scope.context !== undefined && (contextOfChange(change) !== scope.context || ownerOfChange(change) !== scope.task))
104
+ return false;
105
+ if (scope.turn === undefined)
106
+ return true;
107
+ if (change.type === Message)
108
+ return change.message.turn === scope.turn;
109
+ return (change.type === TurnStart || change.type === TurnEnd) && change.turn.prompt.id === scope.turn;
110
+ }
111
+ export function createSessionsClient({ name, claude, report, isGranted }) {
41
112
  const call = (method, input) => claude.cmod.call({ to: cmodPluginName, method, input });
42
113
  const listening = new Map();
114
+ const deciding = new Map();
43
115
  const queued = new Set();
116
+ const parents = new Map();
117
+ const known = new Map();
44
118
  let current = { id: '', title: '', project: '', cwd: '', model: '', startedAt: '', lastAt: '', isRunning: true };
45
- const subscribe = (session) => {
46
- if (queued.has(session))
119
+ const keyOf = (scope) => scope.session ?? everySession;
120
+ const subscribe = (key) => {
121
+ if (queued.has(key))
47
122
  return;
48
- queued.add(session);
123
+ queued.add(key);
49
124
  void Promise.resolve().then(async () => {
50
- queued.delete(session);
51
- const subscriptions = [...(listening.get(session) ?? [])].map(({ event, matcher }) => ({ event, matcher: matcher === undefined ? undefined : sentMatcher(matcher) }));
52
- await call(conversationMethods.subscribe, { session, subscriptions }).catch((error) => report(`sessions:${session}`, `Hears nothing from session ${session}`, error));
125
+ queued.delete(key);
126
+ const heard = (from) => [...(from.get(key) ?? [])].map(({ event, matcher }) => ({ event, matcher: matcher === undefined ? undefined : sentMatcher(matcher) }));
127
+ const tracksTasks = [...(listening.get(key) ?? []), ...(deciding.get(key) ?? [])].some(({ scope }) => scope.task !== undefined || scope.isGoal === true);
128
+ const subscriptions = [...heard(listening), ...(tracksTasks ? [{ event: TaskStart }] : [])];
129
+ await call(conversationMethods.subscribe, { session: key, subscriptions }).catch((error) => report(`sessions:${key}`, `Hears nothing from ${key === everySession ? 'the other sessions' : `session ${key}`}`, error));
130
+ await call(conversationMethods.watchBefore, { session: key, subscriptions: heard(deciding) }).catch((error) => report(`before:${key}`, `Changes nothing in ${key === everySession ? 'the other sessions' : `session ${key}`}`, error));
53
131
  });
54
132
  };
55
- const isCurrent = (id) => id === current.id;
56
- const refuseElsewhere = (id, what) => {
57
- throw new Error(`${name}: ${what} works on the session this mod runs in, and ${id} runs elsewhere or has ended.`);
133
+ const learnTasks = async (session) => {
134
+ if (parents.has(session))
135
+ return;
136
+ const tree = new Map();
137
+ parents.set(session, tree);
138
+ for (const task of await call(conversationMethods.tasks, { session, all: true }))
139
+ tree.set(task.id, task.parent);
140
+ };
141
+ const isUnderIn = (session) => (task, ancestor) => {
142
+ const tree = parents.get(session);
143
+ for (let at = task, steps = 0; at !== undefined && steps < 64; at = tree?.get(at), steps += 1)
144
+ if (at === ancestor)
145
+ return true;
146
+ return false;
58
147
  };
59
- const turnOf = (session, data) => ({ ...data, messages: () => call(conversationMethods.messages, { session, turn: data.prompt.id }) });
60
- const contextOf = (session, data) => ({
61
- ...data,
62
- turns: async () => (await call(conversationMethods.turns, { session, context: data.number })).map((turn) => turnOf(session, turn)),
148
+ const remember = (session, change) => {
149
+ if (change.type === TaskStart)
150
+ parents.get(session)?.set(change.task.id, change.task.parent);
151
+ };
152
+ const add = (into, scope, event, matcherOrListener, maybeListener, onChange) => {
153
+ const listener = (maybeListener ?? matcherOrListener);
154
+ const matcher = maybeListener === undefined ? undefined : matcherOrListener;
155
+ const key = keyOf(scope);
156
+ const entry = { scope, event, matcher, listener };
157
+ const set = into.get(key) ?? new Set();
158
+ set.add(entry);
159
+ into.set(key, set);
160
+ if (scope.task !== undefined || scope.isGoal === true)
161
+ void learnTasks(scope.session ?? current.id).catch((error) => report(`tasks:${key}`, 'Could not read the tasks it listens under', error));
162
+ onChange(key);
163
+ return {
164
+ dispose() {
165
+ for (const [at, held] of into) {
166
+ if (!held.delete(entry))
167
+ continue;
168
+ if (held.size === 0)
169
+ into.delete(at);
170
+ onChange(at);
171
+ return;
172
+ }
173
+ },
174
+ };
175
+ };
176
+ const combined = async (session, event, change, isInProject) => {
177
+ const askers = [...(deciding.get(session) ?? []), ...(isInProject ? (deciding.get(everySession) ?? []) : [])]
178
+ .filter((entry) => entry.event === event)
179
+ .map((entry) => async (latest) => {
180
+ if (!isInScope(entry.scope, session, latest, isUnderIn(session)))
181
+ return undefined;
182
+ if (entry.matcher !== undefined && !matches(payloadOf(latest), entry.matcher))
183
+ return undefined;
184
+ const answer = await entry.listener(payloadFor(session, latest));
185
+ if (answer === undefined || isGranted(beforePermission(event)))
186
+ return answer;
187
+ throw new Error(`Its before(${event}) answer changes nothing until ${beforePermission(event)} is turned on for ${name} in /mods.`);
188
+ });
189
+ return foldAnswers(event, change, askers, (error) => report(`before:${event}`, `The before(${event}) listener failed`, error));
190
+ };
191
+ const recordPart = (scope) => ({
192
+ search: (text, options = {}) => call(conversationMethods.search, { text, ...scope, ...optionsOf(options) }),
193
+ on: (event, matcherOrListener, maybeListener) => add(listening, scope, event, matcherOrListener, maybeListener, subscribe),
194
+ before: (event, matcherOrListener, maybeListener) => add(deciding, scope, event, matcherOrListener, maybeListener, subscribe),
63
195
  });
196
+ const turnOf = (scope, data) => {
197
+ const task = data.prompt.task ?? scope.task;
198
+ return {
199
+ ...data,
200
+ ...recordPart({ ...scope, ...(task === undefined ? {} : { task }), turn: data.prompt.id }),
201
+ messages: () => call(conversationMethods.messages, { session: scope.session, turn: data.prompt.id, ...(task === undefined ? {} : { task }) }),
202
+ };
203
+ };
204
+ const contextOf = (scope, data) => {
205
+ const owned = { ...(scope.session === undefined ? {} : { session: scope.session }), ...(data.task === undefined ? {} : { task: data.task }) };
206
+ return {
207
+ ...data,
208
+ ...recordPart({ ...owned, context: data.number }),
209
+ turns: async () => (await call(conversationMethods.turns, { ...owned, context: data.number })).map((turn) => turnOf(owned, turn)),
210
+ };
211
+ };
64
212
  const taskOf = (session, data) => ({
65
- ...data,
66
- messages: () => call(conversationMethods.messages, { session, task: data.id }),
213
+ id: data.id,
214
+ title: data.title,
215
+ openedBy: data.openedBy,
216
+ openedAt: data.openedAt,
217
+ ...(data.closedAt === undefined ? {} : { closedAt: data.closedAt }),
218
+ owner: ownerOf(session, data),
219
+ ...recordPart({ session, task: data.id }),
67
220
  tasks: async () => (await call(conversationMethods.tasks, { session, task: data.id })).map((task) => taskOf(session, task)),
221
+ messages: () => call(conversationMethods.messages, { session, task: data.id }),
68
222
  });
69
- const goalOf = (session, data) => ({
223
+ const goalOf = (scope, data) => ({
70
224
  ...data,
225
+ ...recordPart({ ...scope, isGoal: true }),
226
+ tasks: async () => (await call(conversationMethods.tasks, { session: scope.session, ...(scope.task === undefined ? {} : { task: scope.task }) })).map((task) => taskOf(scope.session ?? current.id, task)),
71
227
  update: async ({ text }) => {
72
228
  if (text.trim() === '')
73
229
  throw new Error(`${name}: a goal needs text.`);
74
- await call(conversationMethods.updateGoal, { session, text });
230
+ await call(conversationMethods.updateGoal, { session: scope.session ?? current.id, ...(scope.task === undefined ? {} : { task: scope.task }), text });
75
231
  },
76
- tasks: async () => (await call(conversationMethods.tasks, { session })).map((task) => taskOf(session, task)),
77
232
  });
78
233
  const payloadFor = (session, change) => {
234
+ const scope = { session };
79
235
  if (change.type === Message)
80
236
  return change.message;
81
237
  if ('turn' in change)
82
- return turnOf(session, change.turn);
238
+ return turnOf(scope, change.turn);
83
239
  if ('context' in change)
84
- return contextOf(session, change.context);
240
+ return contextOf(scope, change.context);
85
241
  if ('task' in change)
86
242
  return taskOf(session, change.task);
87
- return goalOf(session, change.goal);
243
+ if ('usage' in change)
244
+ return change.usage;
245
+ return goalOf({ session, ...(change.goal.task === undefined ? {} : { task: change.goal.task }) }, { text: change.goal.text });
246
+ };
247
+ const act = (session, action, input, owned) => call(conversationMethods.act, { session: owned?.session ?? session, action, input, ...(owned === undefined ? {} : { task: owned.task }) });
248
+ const sendHere = async ({ type, text, as }) => {
249
+ if (type === Note) {
250
+ if (as !== undefined)
251
+ throw new Error(`${name}: a Note is always from this mod, so it takes no as.`);
252
+ const added = await claude.session.append({ message: { type: 'user', content: [{ type: 'text', text }] } });
253
+ if (added.deny !== undefined)
254
+ throw new Error(`${name}: Claude Code refused the note: ${added.deny}`);
255
+ return;
256
+ }
257
+ const submitted = await claude.prompt.submit({ text, ...(as === Person ? { asUser: true } : {}) });
258
+ if ('drop' in submitted && typeof submitted.drop === 'string')
259
+ throw new Error(`${name}: Claude Code dropped the prompt: ${submitted.drop}`);
88
260
  };
89
- const sessionOf = (read) => {
261
+ const startHere = async (task) => {
262
+ const started = await claude.agent.spawn({
263
+ prompt: task.prompt,
264
+ ...(task.name === undefined ? {} : { description: task.name, name: task.name }),
265
+ ...(task.agentType === undefined ? {} : { subagentType: task.agentType }),
266
+ ...(task.model === undefined ? {} : { model: task.model }),
267
+ ...(task.cwd === undefined ? {} : { cwd: task.cwd }),
268
+ });
269
+ if (started.deny !== undefined)
270
+ throw new Error(`${name}: Claude Code refused the task: ${started.deny}`);
271
+ return taskOf(current.id, { id: started.agentId ?? '', title: task.name ?? '', agentType: task.agentType ?? 'general-purpose', model: started.model, openedBy: '', openedAt: new Date().toISOString() });
272
+ };
273
+ const clientsHere = async () => [...(await claude.session.surfaces())];
274
+ const sessionOf = (read, owned) => {
90
275
  const id = () => read().id;
276
+ const isHere = () => owned === undefined && id() === current.id;
277
+ const scope = () => (owned === undefined ? { session: id() } : { session: owned.session, task: owned.task });
278
+ const where = () => (owned === undefined ? { session: id() } : { session: owned.session, task: owned.task });
279
+ const turnsOf = async (options = {}) => (await call(conversationMethods.turns, { ...where(), ...options })).map((turn) => turnOf(scope(), turn));
280
+ const contextsOf = async () => (await call(conversationMethods.contexts, where())).map((context) => contextOf(scope(), context));
91
281
  const session = {
92
282
  get id() {
93
283
  return read().id;
@@ -98,12 +288,18 @@ export function createSessionsClient({ name, claude, report }) {
98
288
  get project() {
99
289
  return read().project;
100
290
  },
101
- get cwd() {
102
- return read().cwd;
291
+ get agentType() {
292
+ return read().agentType;
103
293
  },
104
294
  get model() {
105
295
  return read().model;
106
296
  },
297
+ get cwd() {
298
+ return read().cwd;
299
+ },
300
+ get isRunning() {
301
+ return read().isRunning;
302
+ },
107
303
  get startedAt() {
108
304
  return read().startedAt;
109
305
  },
@@ -113,123 +309,125 @@ export function createSessionsClient({ name, claude, report }) {
113
309
  get forkedFrom() {
114
310
  return read().forkedFrom;
115
311
  },
116
- get isRunning() {
117
- return read().isRunning;
312
+ search: (text, options = {}) => recordPart(scope()).search(text, options),
313
+ on: (event, matcherOrListener, maybeListener) => recordPart(scope()).on(event, matcherOrListener, maybeListener),
314
+ before: (event, matcherOrListener, maybeListener) => recordPart(scope()).before(event, matcherOrListener, maybeListener),
315
+ goal: async () => goalOf(scope(), await call(conversationMethods.goal, where())),
316
+ context: async () => {
317
+ const all = await contextsOf();
318
+ const last = all.at(-1);
319
+ if (last === undefined)
320
+ throw new Error(`${name}: session ${id()} has no context yet.`);
321
+ return last;
322
+ },
323
+ contexts: contextsOf,
324
+ turn: async () => {
325
+ const [last] = await turnsOf({ last: 1 });
326
+ return last?.closedAt === undefined ? last : undefined;
118
327
  },
119
- async usage() {
120
- if (!isCurrent(id()))
121
- refuseElsewhere(id(), 'usage()');
122
- return usageOf(claude);
123
- },
124
- on(event, matcherOrListener, maybeListener) {
125
- const listener = (maybeListener ?? matcherOrListener);
126
- const matcher = maybeListener === undefined ? undefined : matcherOrListener;
127
- const owner = id();
128
- const heard = { event, matcher, listener };
129
- const set = listening.get(owner) ?? new Set();
130
- set.add(heard);
131
- listening.set(owner, set);
132
- subscribe(owner);
133
- return {
134
- dispose() {
135
- if (!set.delete(heard))
136
- return;
137
- if (set.size === 0)
138
- listening.delete(owner);
139
- subscribe(owner);
140
- },
141
- };
142
- },
143
- async send({ type, text, as }) {
144
- if (text.trim() === '')
328
+ turns: turnsOf,
329
+ messages: (options = {}) => call(conversationMethods.messages, { ...where(), ...options }),
330
+ usage: async () => (isHere() ? usageOf(claude) : act(id(), 'usage', null, owned)),
331
+ clients: async () => (isHere() ? clientsHere() : act(id(), 'clients', null, owned)),
332
+ async send(message) {
333
+ if (message.text.trim() === '')
145
334
  throw new Error(`${name}: send needs text.`);
146
- if (isCurrent(id())) {
147
- if (as === Session)
148
- throw new Error(`${name}: send with as: Session messages another session, and this one runs this mod.`);
149
- if (type === Note) {
150
- if (as !== undefined)
151
- throw new Error(`${name}: a Note is always from this mod, so it takes no as.`);
152
- const added = await claude.session.append({ message: { type: 'user', content: [{ type: 'text', text }] } });
153
- if (added.deny !== undefined)
154
- throw new Error(`${name}: Claude Code refused the note: ${added.deny}`);
155
- return;
156
- }
157
- const submitted = await claude.prompt.submit({ text, ...(as === Person ? { asUser: true } : {}) });
158
- if ('drop' in submitted && typeof submitted.drop === 'string')
159
- throw new Error(`${name}: Claude Code dropped the prompt: ${submitted.drop}`);
160
- return;
161
- }
162
- if (!read().isRunning)
163
- throw new Error(`${name}: session ${id()} has ended, so it takes no messages.`);
164
- if (as === Session) {
165
- if (type === Note)
166
- throw new Error(`${name}: a message from this session reaches Claude as a Prompt, so send it with type: Prompt.`);
167
- const sent = await claude.session.send({ to: { sessionId: id() }, text });
168
- if (sent.isDelivered !== true)
169
- throw new Error(`${name}: Claude Code could not deliver the message to session ${id()}.`);
170
- return;
171
- }
172
- await call(conversationMethods.send, { session: id(), type, text, ...(as === undefined ? {} : { as }) });
335
+ if (isHere() && message.as !== Session)
336
+ return sendHere(message);
337
+ await act(id(), 'send', message, owned);
173
338
  },
174
339
  async startTask(task) {
175
- if (!isCurrent(id()))
176
- refuseElsewhere(id(), 'startTask()');
177
- const started = await claude.agent.spawn({
178
- prompt: task.prompt,
179
- ...(task.name === undefined ? {} : { description: task.name, name: task.name }),
180
- ...(task.agentType === undefined ? {} : { subagentType: task.agentType }),
181
- ...(task.model === undefined ? {} : { model: task.model }),
182
- ...(task.cwd === undefined ? {} : { cwd: task.cwd }),
183
- });
184
- if (started.deny !== undefined)
185
- throw new Error(`${name}: Claude Code refused the task: ${started.deny}`);
186
- const data = { id: started.agentId ?? '', title: task.name ?? '', agentType: task.agentType ?? 'general-purpose', model: started.model, openedBy: '', openedAt: new Date().toISOString() };
187
- return taskOf(id(), data);
188
- },
189
- messages: (options = {}) => call(conversationMethods.messages, { session: id(), ...options }),
190
- turns: async (options = {}) => (await call(conversationMethods.turns, { session: id(), ...options })).map((turn) => turnOf(id(), turn)),
191
- contexts: async () => (await call(conversationMethods.contexts, { session: id() })).map((context) => contextOf(id(), context)),
192
- goal: async () => goalOf(id(), await call(conversationMethods.goal, { session: id() })),
340
+ if (owned !== undefined)
341
+ throw new Error(asked('startTask'));
342
+ if (isHere())
343
+ return startHere(task);
344
+ const started = await act(id(), 'startTask', task);
345
+ return taskOf(id(), started);
346
+ },
347
+ async callTool(toolCall) {
348
+ if (owned !== undefined)
349
+ throw new Error(asked('callTool'));
350
+ return act(id(), 'callTool', toolCall);
351
+ },
193
352
  };
194
353
  return session;
195
354
  };
355
+ const ownerOf = (session, data) => {
356
+ const parent = session === current.id ? current : known.get(session);
357
+ return sessionOf(() => ({
358
+ id: data.id,
359
+ title: data.title,
360
+ project: parent?.project ?? '',
361
+ agentType: data.agentType,
362
+ model: data.model ?? '',
363
+ cwd: parent?.cwd ?? '',
364
+ isRunning: data.closedAt === undefined && (parent?.isRunning ?? true),
365
+ startedAt: data.openedAt,
366
+ lastAt: data.closedAt ?? data.openedAt,
367
+ }), { session, task: data.id });
368
+ };
196
369
  const currentSession = sessionOf(() => current);
370
+ const every = recordPart({});
371
+ const answerBefore = async (session, event, change, isInProject) => (isGranted('conversation') ? combined(session, event, change, isInProject) : undefined);
197
372
  return {
198
373
  sessions: {
199
374
  get current() {
200
375
  return currentSession;
201
376
  },
202
377
  list: async (scope = {}) => (await call(conversationMethods.sessions, scope)).map((data) => {
203
- if (data.id !== current.id)
204
- return sessionOf(() => data);
378
+ if (data.id !== current.id) {
379
+ known.set(data.id, data);
380
+ return sessionOf(() => known.get(data.id) ?? data);
381
+ }
205
382
  if (current.title === '')
206
383
  current = { ...current, title: data.title };
207
384
  return currentSession;
208
385
  }),
209
- search: (text, options = {}) => call(conversationMethods.search, { text, ...options, ...(options.before === undefined ? {} : { before: { session: options.before.session, id: options.before.id } }) }),
386
+ search: (text, options = {}) => call(conversationMethods.search, { text, ...optionsOf(options), ...(options.running === undefined ? {} : { running: options.running }), ...(options.since === undefined ? {} : { since: options.since }), ...(options.everyProject === undefined ? {} : { everyProject: options.everyProject }) }),
387
+ on: every.on,
388
+ before: every.before,
210
389
  },
211
- changed(session, change) {
212
- for (const { event, matcher, listener } of listening.get(session) ?? []) {
213
- if (!isHeardBy(event, matcher, change))
214
- continue;
215
- void Promise.resolve()
216
- .then(() => listener(payloadFor(session, change)))
217
- .catch((error) => report(`session:${event}`, `The ${event} listener failed`, error));
390
+ changed(session, change, isInProject) {
391
+ remember(session, change);
392
+ for (const key of isInProject ? [session, everySession] : [session]) {
393
+ for (const { scope, event, matcher, listener } of listening.get(key) ?? []) {
394
+ if (!isHeardBy(event, matcher, change) || !isInScope(scope, session, change, isUnderIn(session)))
395
+ continue;
396
+ void Promise.resolve()
397
+ .then(() => listener(payloadFor(session, change)))
398
+ .catch((error) => report(`session:${event}`, `The ${event} listener failed`, error));
399
+ }
218
400
  }
219
401
  },
402
+ answerBefore,
220
403
  follow(next) {
221
404
  const moved = next.id !== current.id && current.id !== '';
222
405
  const previous = current.id;
223
406
  current = { ...next, title: next.title || (next.id === current.id ? current.title : ''), isRunning: true, lastAt: new Date().toISOString() };
224
407
  if (!moved)
225
408
  return;
226
- const kept = listening.get(previous);
227
- if (kept === undefined)
228
- return;
229
- listening.delete(previous);
230
- listening.set(current.id, kept);
409
+ for (const into of [listening, deciding]) {
410
+ const kept = into.get(previous);
411
+ if (kept === undefined)
412
+ continue;
413
+ into.delete(previous);
414
+ for (const entry of kept)
415
+ if (entry.scope.session === previous)
416
+ entry.scope = { ...entry.scope, session: current.id };
417
+ into.set(current.id, new Set([...(into.get(current.id) ?? []), ...kept]));
418
+ }
419
+ parents.delete(previous);
231
420
  subscribe(previous);
232
421
  subscribe(current.id);
233
422
  },
234
423
  };
235
424
  }
425
+ export function resultOf(where, answered) {
426
+ const base = { id: '', type: ToolResult, at: new Date().toISOString(), session: where.session, context: where.context, ...(where.turn === undefined ? {} : { turn: where.turn.prompt.id }), callId: '' };
427
+ if (answered.deny !== undefined)
428
+ return { ...base, text: answered.deny, isError: true };
429
+ return { ...base, text: answered.text ?? JSON.stringify(answered.result), isError: answered.isError === true };
430
+ }
431
+ function optionsOf(options) {
432
+ return { ...(options.limit === undefined ? {} : { limit: options.limit }), ...(options.before === undefined ? {} : { before: { session: options.before.session, id: options.before.id } }) };
433
+ }
@@ -1,5 +1,5 @@
1
1
  import type { ElementTable, RenderElement, RenderPropsOf } from 'claude-code';
2
- import type { Mod, Toast, ToastId, ToastType } from '../mod.js';
2
+ import type { Toast, ToastId, ToastType } from '../mod.js';
3
3
  import type { Claude } from './claude.js';
4
4
  export type ShownToast = {
5
5
  readonly type: ToastType;
@@ -11,21 +11,14 @@ export type Failure = {
11
11
  readonly message: string;
12
12
  readonly error?: unknown;
13
13
  readonly description?: string;
14
+ readonly retry?: () => unknown;
14
15
  };
15
16
  export type Toaster = {
16
17
  readonly toast: Toast;
17
18
  copy(text: string): Promise<void>;
19
+ fail(failure: Failure): ToastId;
18
20
  readonly shown: readonly ShownToast[];
19
- draw(drawn: RenderElement, table: ElementTable, band: RenderPropsOf['AbovePrompt']): RenderElement;
21
+ draw(drawn: RenderElement, table: ElementTable, band: RenderPropsOf['AbovePrompt'], bandId: string): RenderElement;
20
22
  };
21
- export declare function createToaster(claude: Claude, name: string): Toaster;
22
- export type FailureTarget = {
23
- readonly name: string;
24
- readonly ui: Pick<Mod['ui'], 'toast' | 'copy'>;
25
- readonly claude: {
26
- readonly plugin: Claude['plugin'];
27
- readonly fs: Pick<Claude['fs'], 'read'>;
28
- };
29
- };
30
- export declare function showFailure({ name, ui, claude }: FailureTarget, { id, message, error, description }: Failure): ToastId;
23
+ export declare function createToaster(claude: Claude, name: string, version: () => string | undefined): Toaster;
31
24
  //# sourceMappingURL=toaster.d.ts.map