@agentprojectcontext/apx 1.16.0 → 1.18.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentprojectcontext/apx",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.18.0",
|
|
4
4
|
"description": "APX — unified CLI + daemon for the Agent Project Context (APC) standard.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -60,7 +60,8 @@
|
|
|
60
60
|
"semver": "^7.8.0",
|
|
61
61
|
"solid-js": "^1.9.12",
|
|
62
62
|
"strip-ansi": "^7.2.0",
|
|
63
|
-
"yargs": "^18.0.0"
|
|
63
|
+
"yargs": "^18.0.0",
|
|
64
|
+
"zod": "^3.25.76"
|
|
64
65
|
},
|
|
65
66
|
"optionalDependencies": {
|
|
66
67
|
"fast-glob": "^3.3.2",
|
|
@@ -37,7 +37,7 @@ const ACK_ONLY_TOOLS = new Set(["send_telegram"]);
|
|
|
37
37
|
// (the model already had its chance to call a real tool).
|
|
38
38
|
const MAX_CONSECUTIVE_ACKS = 2;
|
|
39
39
|
|
|
40
|
-
const DEFAULT_SYSTEM = `# Identity (override everything else)
|
|
40
|
+
export const DEFAULT_SYSTEM = `# Identity (override everything else)
|
|
41
41
|
You are **APX** — Manuel's personal assistant running on his Mac.
|
|
42
42
|
You are NOT a code analyzer, NOT a generic chatbot, NOT a tutor.
|
|
43
43
|
You are an **action agent**: you USE TOOLS to do real things on Manuel's system.
|
|
@@ -4,6 +4,7 @@ import { onCleanup } from "solid-js"
|
|
|
4
4
|
import fs from "node:fs"
|
|
5
5
|
import os from "node:os"
|
|
6
6
|
import path from "node:path"
|
|
7
|
+
import { spawn } from "node:child_process"
|
|
7
8
|
|
|
8
9
|
const TOKEN_PATH = path.join(os.homedir(), ".apx", "daemon.token")
|
|
9
10
|
|
|
@@ -20,6 +21,9 @@ export type ApxEvent =
|
|
|
20
21
|
| { type: "chunk"; sessionID: string; chunk: string }
|
|
21
22
|
| { type: "final"; sessionID: string; text: string; usage?: { input_tokens: number; output_tokens: number } }
|
|
22
23
|
| { type: "error"; sessionID: string; error: string }
|
|
24
|
+
| { type: "shell.start"; sessionID: string; shellID: string; command: string; cwd: string }
|
|
25
|
+
| { type: "shell.output"; sessionID: string; shellID: string; stream: "stdout" | "stderr"; chunk: string }
|
|
26
|
+
| { type: "shell.done"; sessionID: string; shellID: string; exitCode: number | null; signal: NodeJS.Signals | null }
|
|
23
27
|
|
|
24
28
|
export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
|
|
25
29
|
name: "SDK",
|
|
@@ -102,6 +106,27 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
|
|
|
102
106
|
return (data as any).id as string
|
|
103
107
|
}
|
|
104
108
|
|
|
109
|
+
function runShell(sessionID: string, command: string, cwd: string = process.cwd()): Promise<{ shellID: string; exitCode: number | null }> {
|
|
110
|
+
const shellID = `sh-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`
|
|
111
|
+
emitter.emit("event", { type: "shell.start", sessionID, shellID, command, cwd })
|
|
112
|
+
return new Promise((resolve) => {
|
|
113
|
+
const child = spawn(command, { shell: true, cwd, env: process.env })
|
|
114
|
+
child.stdout?.on("data", (buf) => {
|
|
115
|
+
emitter.emit("event", { type: "shell.output", sessionID, shellID, stream: "stdout", chunk: buf.toString() })
|
|
116
|
+
})
|
|
117
|
+
child.stderr?.on("data", (buf) => {
|
|
118
|
+
emitter.emit("event", { type: "shell.output", sessionID, shellID, stream: "stderr", chunk: buf.toString() })
|
|
119
|
+
})
|
|
120
|
+
child.on("error", (err) => {
|
|
121
|
+
emitter.emit("event", { type: "shell.output", sessionID, shellID, stream: "stderr", chunk: `[spawn error] ${err.message}\n` })
|
|
122
|
+
})
|
|
123
|
+
child.on("close", (code, signal) => {
|
|
124
|
+
emitter.emit("event", { type: "shell.done", sessionID, shellID, exitCode: code, signal })
|
|
125
|
+
resolve({ shellID, exitCode: code })
|
|
126
|
+
})
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
|
|
105
130
|
async function listSessions(): Promise<Array<{ id: string; title: string; updatedAt?: number }>> {
|
|
106
131
|
try {
|
|
107
132
|
const token = readToken()
|
|
@@ -146,7 +171,12 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
|
|
|
146
171
|
fork: async (_opts: any) => ({ data: undefined, error: new Error("not supported") }),
|
|
147
172
|
abort: async (_opts: any) => {},
|
|
148
173
|
prompt: async (_opts: any) => {},
|
|
149
|
-
shell: async (
|
|
174
|
+
shell: async (opts: { sessionID?: string; command?: string; cwd?: string }) => {
|
|
175
|
+
if (!opts?.command) return { data: undefined }
|
|
176
|
+
const sid = opts.sessionID || (await createSession())
|
|
177
|
+
const r = await runShell(sid, opts.command, opts.cwd)
|
|
178
|
+
return { data: r }
|
|
179
|
+
},
|
|
150
180
|
command: async (_opts: any) => {},
|
|
151
181
|
refresh: async () => {},
|
|
152
182
|
update: async (_opts: any) => ({ data: undefined }),
|
|
@@ -180,6 +210,7 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
|
|
|
180
210
|
streamChat,
|
|
181
211
|
createSession,
|
|
182
212
|
listSessions,
|
|
213
|
+
runShell,
|
|
183
214
|
}
|
|
184
215
|
},
|
|
185
216
|
})
|
|
@@ -13,10 +13,15 @@ export type ApxSession = {
|
|
|
13
13
|
export type ApxMessage = {
|
|
14
14
|
id: string
|
|
15
15
|
sessionID: string
|
|
16
|
-
role: "user" | "assistant"
|
|
16
|
+
role: "user" | "assistant" | "shell"
|
|
17
17
|
text: string
|
|
18
18
|
streaming?: boolean
|
|
19
19
|
error?: boolean
|
|
20
|
+
// Shell-specific
|
|
21
|
+
shellID?: string
|
|
22
|
+
command?: string
|
|
23
|
+
cwd?: string
|
|
24
|
+
exitCode?: number | null
|
|
20
25
|
}
|
|
21
26
|
|
|
22
27
|
export const { use: useApxSync, provider: ApxSyncProvider } = createSimpleContext({
|
|
@@ -78,6 +83,55 @@ export const { use: useApxSync, provider: ApxSyncProvider } = createSimpleContex
|
|
|
78
83
|
setStore("previousMessages", (prev) => [...prev, { role: "assistant", content: e.text }])
|
|
79
84
|
}
|
|
80
85
|
|
|
86
|
+
if (ev.type === "shell.start") {
|
|
87
|
+
const e = ev
|
|
88
|
+
setStore(
|
|
89
|
+
"messages",
|
|
90
|
+
produce((draft) => {
|
|
91
|
+
;(draft[e.sessionID] ??= []).push({
|
|
92
|
+
id: e.shellID,
|
|
93
|
+
sessionID: e.sessionID,
|
|
94
|
+
role: "shell",
|
|
95
|
+
text: "",
|
|
96
|
+
streaming: true,
|
|
97
|
+
shellID: e.shellID,
|
|
98
|
+
command: e.command,
|
|
99
|
+
cwd: e.cwd,
|
|
100
|
+
})
|
|
101
|
+
}),
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (ev.type === "shell.output") {
|
|
106
|
+
const e = ev
|
|
107
|
+
setStore(
|
|
108
|
+
"messages",
|
|
109
|
+
produce((draft) => {
|
|
110
|
+
const msgs = draft[e.sessionID]
|
|
111
|
+
if (!msgs) return
|
|
112
|
+
const target = msgs.find((m) => m.role === "shell" && m.shellID === e.shellID)
|
|
113
|
+
if (target) target.text += e.chunk
|
|
114
|
+
}),
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (ev.type === "shell.done") {
|
|
119
|
+
const e = ev
|
|
120
|
+
setStore(
|
|
121
|
+
"messages",
|
|
122
|
+
produce((draft) => {
|
|
123
|
+
const msgs = draft[e.sessionID]
|
|
124
|
+
if (!msgs) return
|
|
125
|
+
const target = msgs.find((m) => m.role === "shell" && m.shellID === e.shellID)
|
|
126
|
+
if (target) {
|
|
127
|
+
target.streaming = false
|
|
128
|
+
target.exitCode = e.exitCode
|
|
129
|
+
if (e.signal) target.text += `\n[killed by signal ${e.signal}]`
|
|
130
|
+
}
|
|
131
|
+
}),
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
|
|
81
135
|
if (ev.type === "error") {
|
|
82
136
|
const e = ev
|
|
83
137
|
setStore(
|
|
@@ -121,6 +175,11 @@ export const { use: useApxSync, provider: ApxSyncProvider } = createSimpleContex
|
|
|
121
175
|
return id
|
|
122
176
|
}
|
|
123
177
|
|
|
178
|
+
async function runShell(command: string, cwd?: string) {
|
|
179
|
+
const sessionID = await ensureSession()
|
|
180
|
+
await sdk.runShell(sessionID, command, cwd ?? process.cwd())
|
|
181
|
+
}
|
|
182
|
+
|
|
124
183
|
async function sendMessage(text: string) {
|
|
125
184
|
const sessionID = await ensureSession()
|
|
126
185
|
const userMsg: ApxMessage = {
|
|
@@ -172,6 +231,7 @@ export const { use: useApxSync, provider: ApxSyncProvider } = createSimpleContex
|
|
|
172
231
|
async refresh() {},
|
|
173
232
|
},
|
|
174
233
|
sendMessage,
|
|
234
|
+
runShell,
|
|
175
235
|
ensureSession,
|
|
176
236
|
}
|
|
177
237
|
},
|
|
@@ -47,6 +47,35 @@ function AssistantBubble(props: { msg: ApxMessage }) {
|
|
|
47
47
|
)
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
function ShellBubble(props: { msg: ApxMessage }) {
|
|
51
|
+
const { theme } = useTheme()
|
|
52
|
+
const header = () => {
|
|
53
|
+
const code = props.msg.exitCode
|
|
54
|
+
const status = props.msg.streaming
|
|
55
|
+
? "running"
|
|
56
|
+
: code === 0
|
|
57
|
+
? "exit 0"
|
|
58
|
+
: code == null
|
|
59
|
+
? "ended"
|
|
60
|
+
: `exit ${code}`
|
|
61
|
+
return `$ ${props.msg.command ?? ""} · ${status}`
|
|
62
|
+
}
|
|
63
|
+
const body = () => props.msg.text || (props.msg.streaming ? "…" : "(no output)")
|
|
64
|
+
return (
|
|
65
|
+
<box flexDirection="column" marginBottom={1} paddingLeft={2} paddingRight={2}>
|
|
66
|
+
<text color={theme.warning ?? theme.primary} bold>
|
|
67
|
+
{header()}
|
|
68
|
+
</text>
|
|
69
|
+
<text
|
|
70
|
+
color={props.msg.exitCode && props.msg.exitCode !== 0 ? theme.error : theme.text}
|
|
71
|
+
wrap
|
|
72
|
+
>
|
|
73
|
+
{body()}
|
|
74
|
+
</text>
|
|
75
|
+
</box>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
50
79
|
export function Session() {
|
|
51
80
|
const dims = useTerminalDimensions()
|
|
52
81
|
const { theme } = useTheme()
|
|
@@ -109,7 +138,11 @@ export function Session() {
|
|
|
109
138
|
inputEl.clear()
|
|
110
139
|
setSending(true)
|
|
111
140
|
try {
|
|
112
|
-
|
|
141
|
+
if (text.startsWith("!") && text.length > 1) {
|
|
142
|
+
await sync.runShell(text.slice(1).trim())
|
|
143
|
+
} else {
|
|
144
|
+
await sync.sendMessage(text)
|
|
145
|
+
}
|
|
113
146
|
} catch (e) {
|
|
114
147
|
toast.error(e instanceof Error ? e : new Error(String(e)))
|
|
115
148
|
} finally {
|
|
@@ -132,17 +165,17 @@ export function Session() {
|
|
|
132
165
|
fallback={
|
|
133
166
|
<box paddingLeft={2} paddingTop={2}>
|
|
134
167
|
<text color={theme.textMuted} italic>
|
|
135
|
-
Type a message
|
|
168
|
+
Type a message to chat, or prefix with ! to run a shell command (e.g. !ls).
|
|
136
169
|
</text>
|
|
137
170
|
</box>
|
|
138
171
|
}
|
|
139
172
|
>
|
|
140
173
|
<For each={messages()}>
|
|
141
|
-
{(msg) =>
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
174
|
+
{(msg) => {
|
|
175
|
+
if (msg.role === "user") return <UserBubble msg={msg} />
|
|
176
|
+
if (msg.role === "shell") return <ShellBubble msg={msg} />
|
|
177
|
+
return <AssistantBubble msg={msg} />
|
|
178
|
+
}}
|
|
146
179
|
</For>
|
|
147
180
|
</Show>
|
|
148
181
|
<box height={1} />
|
|
@@ -163,7 +196,7 @@ export function Session() {
|
|
|
163
196
|
inputEl = r
|
|
164
197
|
promptRef.set(makeRef(r))
|
|
165
198
|
}}
|
|
166
|
-
placeholder={sending() ? "Waiting for response…" : "Ask anything... (
|
|
199
|
+
placeholder={sending() ? "Waiting for response…" : "Ask anything... (prefix ! to run shell, e.g. !ls)"}
|
|
167
200
|
placeholderColor={theme.textMuted}
|
|
168
201
|
textColor={theme.text}
|
|
169
202
|
focusedTextColor={theme.text}
|