khal-os 1.260324.13 → 1.260324.14
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/package.json +1 -1
- package/packages/genie-app/views/genie/service/agent-lifecycle.ts +2 -2
- package/packages/genie-app/views/genie/service/comms.ts +2 -2
- package/packages/genie-app/views/genie/service/directory.ts +12 -14
- package/packages/genie-app/views/genie/service/index.ts +40 -8
- package/packages/genie-app/views/genie/service/teams.ts +2 -1
- package/packages/genie-app/views/genie/ui/hooks/useNatsRequest.ts +8 -2
package/package.json
CHANGED
|
@@ -58,14 +58,14 @@ export const agentLifecycleHandlers: ServiceHandler[] = [
|
|
|
58
58
|
subject: SUBJECTS.agent.history(),
|
|
59
59
|
handler: (msg) => {
|
|
60
60
|
try {
|
|
61
|
-
const { name, full, since } = msg.json<{ name: string; full?: boolean; since?:
|
|
61
|
+
const { name, full, since } = msg.json<{ name: string; full?: boolean; since?: number }>();
|
|
62
62
|
if (!name) {
|
|
63
63
|
msg.respond(JSON.stringify({ error: 'Missing required field: name' }));
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
66
66
|
const args = ['history', name, '--json'];
|
|
67
67
|
if (full) args.push('--full');
|
|
68
|
-
if (since) args.push('--since', since);
|
|
68
|
+
if (since !== undefined) args.push('--since', String(since));
|
|
69
69
|
|
|
70
70
|
const result = runGenie<unknown[]>(args, { timeout: 15_000 });
|
|
71
71
|
if (!result.ok) {
|
|
@@ -45,14 +45,14 @@ export const commsHandlers: ServiceHandler[] = [
|
|
|
45
45
|
subject: SUBJECTS.comms.broadcast(),
|
|
46
46
|
handler: (msg) => {
|
|
47
47
|
try {
|
|
48
|
-
const req = msg.json<{ body: string;
|
|
48
|
+
const req = msg.json<{ body: string; from?: string }>();
|
|
49
49
|
if (!req.body) {
|
|
50
50
|
msg.respond(JSON.stringify({ error: 'Missing required field: body' }));
|
|
51
51
|
return;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
const args = ['broadcast', req.body];
|
|
55
|
-
if (req.
|
|
55
|
+
if (req.from) args.push('--from', req.from);
|
|
56
56
|
|
|
57
57
|
const result = runGenie(args, { json: false });
|
|
58
58
|
if (!result.ok) {
|
|
@@ -14,7 +14,9 @@ interface DirAddPayload {
|
|
|
14
14
|
dir?: string;
|
|
15
15
|
repo?: string;
|
|
16
16
|
model?: string;
|
|
17
|
+
promptMode?: string;
|
|
17
18
|
roles?: string[];
|
|
19
|
+
global?: boolean;
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
interface DirRemovePayload {
|
|
@@ -26,8 +28,9 @@ interface DirEditPayload {
|
|
|
26
28
|
model?: string;
|
|
27
29
|
dir?: string;
|
|
28
30
|
repo?: string;
|
|
31
|
+
promptMode?: string;
|
|
29
32
|
roles?: string[];
|
|
30
|
-
|
|
33
|
+
global?: boolean;
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
export const directorySubscriptions = [
|
|
@@ -80,7 +83,7 @@ export const directorySubscriptions = [
|
|
|
80
83
|
subject: SUBJECTS.dir.add(),
|
|
81
84
|
handler: (msg: { data: Uint8Array; json: <T>() => T; respond: (data: string) => void }) => {
|
|
82
85
|
try {
|
|
83
|
-
const { name, dir, repo, model, roles } = msg.json<DirAddPayload>();
|
|
86
|
+
const { name, dir, repo, model, promptMode, roles, global: isGlobal } = msg.json<DirAddPayload>();
|
|
84
87
|
if (!name) {
|
|
85
88
|
msg.respond(JSON.stringify({ error: 'name is required' }));
|
|
86
89
|
return;
|
|
@@ -90,11 +93,9 @@ export const directorySubscriptions = [
|
|
|
90
93
|
if (dir) args.push('--dir', dir);
|
|
91
94
|
if (repo) args.push('--repo', repo);
|
|
92
95
|
if (model) args.push('--model', model);
|
|
93
|
-
if (
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
}
|
|
96
|
+
if (promptMode) args.push('--prompt-mode', promptMode);
|
|
97
|
+
if (roles?.length) args.push('--roles', ...roles);
|
|
98
|
+
if (isGlobal) args.push('--global');
|
|
98
99
|
|
|
99
100
|
const result = runGenie(args, { json: false });
|
|
100
101
|
if (!result.ok) {
|
|
@@ -136,7 +137,7 @@ export const directorySubscriptions = [
|
|
|
136
137
|
subject: SUBJECTS.dir.edit(),
|
|
137
138
|
handler: (msg: { data: Uint8Array; json: <T>() => T; respond: (data: string) => void }) => {
|
|
138
139
|
try {
|
|
139
|
-
const { name, model, dir, repo, roles,
|
|
140
|
+
const { name, model, dir, repo, promptMode, roles, global: isGlobal } = msg.json<DirEditPayload>();
|
|
140
141
|
if (!name) {
|
|
141
142
|
msg.respond(JSON.stringify({ error: 'name is required' }));
|
|
142
143
|
return;
|
|
@@ -146,12 +147,9 @@ export const directorySubscriptions = [
|
|
|
146
147
|
if (model) args.push('--model', model);
|
|
147
148
|
if (dir) args.push('--dir', dir);
|
|
148
149
|
if (repo) args.push('--repo', repo);
|
|
149
|
-
if (
|
|
150
|
-
if (roles)
|
|
151
|
-
|
|
152
|
-
args.push('--role', role);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
150
|
+
if (promptMode) args.push('--prompt-mode', promptMode);
|
|
151
|
+
if (roles?.length) args.push('--roles', ...roles);
|
|
152
|
+
if (isGlobal) args.push('--global');
|
|
155
153
|
|
|
156
154
|
const result = runGenie(args, { json: false });
|
|
157
155
|
if (!result.ok) {
|
|
@@ -2,6 +2,7 @@ import type { NatsConnection } from '@khal-os/sdk/service';
|
|
|
2
2
|
import { createService } from '@khal-os/sdk/service';
|
|
3
3
|
import { agentLifecycleHandlers } from './agent-lifecycle';
|
|
4
4
|
import { appsHandlers } from './apps';
|
|
5
|
+
import { runGenieAsync } from './cli';
|
|
5
6
|
import { commsHandlers } from './comms';
|
|
6
7
|
import { directorySubscriptions } from './directory';
|
|
7
8
|
import { systemSubscriptions } from './system';
|
|
@@ -133,18 +134,49 @@ createService({
|
|
|
133
134
|
// --- Spawn agent ---
|
|
134
135
|
{
|
|
135
136
|
subject: 'os.genie.spawn',
|
|
136
|
-
handler: (msg) => {
|
|
137
|
+
handler: async (msg) => {
|
|
137
138
|
try {
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
139
|
+
const req = msg.json<{
|
|
140
|
+
role: string;
|
|
141
|
+
team?: string;
|
|
142
|
+
repo?: string;
|
|
143
|
+
provider?: string;
|
|
144
|
+
model?: string;
|
|
145
|
+
skill?: string;
|
|
146
|
+
layout?: string;
|
|
147
|
+
color?: string;
|
|
148
|
+
planMode?: boolean;
|
|
149
|
+
permissionMode?: string;
|
|
150
|
+
cwd?: string;
|
|
151
|
+
session?: string;
|
|
152
|
+
}>();
|
|
153
|
+
if (!req.role) {
|
|
154
|
+
msg.respond(JSON.stringify({ ok: false, error: 'Missing required field: role' }));
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const args = ['spawn', req.role];
|
|
141
159
|
if (req.team) args.push('--team', req.team);
|
|
142
|
-
|
|
160
|
+
if (req.provider) args.push('--provider', req.provider);
|
|
161
|
+
if (req.model) args.push('--model', req.model);
|
|
162
|
+
if (req.skill) args.push('--skill', req.skill);
|
|
163
|
+
if (req.layout) args.push('--layout', req.layout);
|
|
164
|
+
if (req.color) args.push('--color', req.color);
|
|
165
|
+
if (req.planMode) args.push('--plan-mode');
|
|
166
|
+
if (req.permissionMode) args.push('--permission-mode', req.permissionMode);
|
|
167
|
+
if (req.cwd) args.push('--cwd', req.cwd);
|
|
168
|
+
if (req.session) args.push('--session', req.session);
|
|
169
|
+
|
|
170
|
+
const result = await runGenieAsync(args, {
|
|
171
|
+
json: false,
|
|
172
|
+
timeout: 15_000,
|
|
143
173
|
cwd: req.repo || process.env.HOME,
|
|
144
|
-
timeout: 15000,
|
|
145
|
-
encoding: 'utf-8',
|
|
146
174
|
});
|
|
147
|
-
|
|
175
|
+
if (!result.ok) {
|
|
176
|
+
msg.respond(JSON.stringify({ ok: false, error: result.error }));
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
msg.respond(JSON.stringify({ ok: true, output: result.data }));
|
|
148
180
|
} catch (err) {
|
|
149
181
|
msg.respond(JSON.stringify({ ok: false, error: String(err) }));
|
|
150
182
|
}
|
|
@@ -57,7 +57,7 @@ export const teamsHandlers: ServiceHandler[] = [
|
|
|
57
57
|
subject: SUBJECTS.teams.create(),
|
|
58
58
|
handler: async (msg) => {
|
|
59
59
|
try {
|
|
60
|
-
const req = msg.json<{ name: string; repo: string; branch?: string; wish?: string }>();
|
|
60
|
+
const req = msg.json<{ name: string; repo: string; branch?: string; wish?: string; session?: string }>();
|
|
61
61
|
if (!req.name || !req.repo) {
|
|
62
62
|
msg.respond(JSON.stringify({ ok: false, error: 'Missing required fields: name, repo' }));
|
|
63
63
|
return;
|
|
@@ -66,6 +66,7 @@ export const teamsHandlers: ServiceHandler[] = [
|
|
|
66
66
|
const args = ['team', 'create', req.name, '--repo', req.repo];
|
|
67
67
|
if (req.branch) args.push('--branch', req.branch);
|
|
68
68
|
if (req.wish) args.push('--wish', req.wish);
|
|
69
|
+
if (req.session) args.push('--session', req.session);
|
|
69
70
|
|
|
70
71
|
const result = await runGenieAsync(args, { json: false, timeout: 120_000 });
|
|
71
72
|
if (!result.ok) {
|
|
@@ -29,10 +29,16 @@ export function useNatsRequest<T = unknown>(
|
|
|
29
29
|
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
30
30
|
const mountedRef = useRef(true);
|
|
31
31
|
|
|
32
|
+
// Stabilize payload by serializing — prevents infinite re-render loops
|
|
33
|
+
// when callers pass inline objects like { name: teamName }
|
|
34
|
+
const payloadKey = JSON.stringify(payload ?? null);
|
|
35
|
+
const payloadRef = useRef(payload);
|
|
36
|
+
payloadRef.current = payload;
|
|
37
|
+
|
|
32
38
|
const fetchData = useCallback(async () => {
|
|
33
39
|
try {
|
|
34
40
|
const client = getNatsClient();
|
|
35
|
-
const response = await client.request(subject,
|
|
41
|
+
const response = await client.request(subject, payloadRef.current ?? {}, 5000);
|
|
36
42
|
if (!mountedRef.current) return;
|
|
37
43
|
setData(response as T);
|
|
38
44
|
setError(null);
|
|
@@ -44,7 +50,7 @@ export function useNatsRequest<T = unknown>(
|
|
|
44
50
|
setLoading(false);
|
|
45
51
|
}
|
|
46
52
|
}
|
|
47
|
-
}, [subject,
|
|
53
|
+
}, [subject, payloadKey]);
|
|
48
54
|
|
|
49
55
|
useEffect(() => {
|
|
50
56
|
mountedRef.current = true;
|