@7n/tauri-components 0.10.1 → 0.11.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 +6 -0
- package/package.json +1 -1
- package/src/components/AgentDialog.vue +21 -46
- package/src/components/AuditDialog.vue +9 -17
- package/src/components/BaseDialog.vue +2 -6
- package/src/components/DialogActions.vue +2 -3
- package/src/components/RequestView.vue +2 -3
- package/src/components/StatePill.vue +1 -1
- package/src/components/docs/AgentDialog.md +4 -4
- package/src/components/docs/index.md +4 -4
- package/src/components/index.js +2 -2
- package/src/components/status.js +1 -1
- package/src/core/acp-agent-presets.js +2 -2
- package/src/core/acp-agent.js +21 -15
- package/src/core/acp-kit.js +79 -30
- package/src/core/dispatch.js +3 -3
- package/src/core/docs/acp-agent.md +4 -4
- package/src/core/docs/acp-kit.md +4 -4
- package/src/core/manifest.js +10 -8
- package/src/docs/index.md +4 -4
- package/src/index.js +3 -8
- package/src/testing/quasar.js +2 -2
- package/src/vue/docs/index.md +4 -4
- package/src/vue/docs/journal-store-tauri.md +4 -4
- package/src/vue/docs/use-acp-agent.md +4 -4
- package/src/vue/index.js +4 -6
- package/src/vue/journal-store-tauri.js +2 -2
- package/src/vue/use-acp-agent.js +22 -14
- package/src/vue/use-updater.js +4 -6
- package/types/core/dispatch.d.ts +5 -2
- package/types/core/manifest.d.ts +14 -9
- package/types/core/scope.d.ts +24 -11
- package/types/core/tools.d.ts +1 -1
- package/types/index.d.ts +4 -9
- package/src/core/agent-handler.js +0 -171
- package/src/core/agent-kit.js +0 -43
- package/src/core/docs/agent-handler.md +0 -31
- package/src/core/docs/agent-kit.md +0 -25
- package/src/core/docs/llm.md +0 -29
- package/src/core/docs/omlx-models.md +0 -30
- package/src/core/docs/resolve-omlx-base-url.md +0 -40
- package/src/core/llm.js +0 -90
- package/src/core/omlx-models.js +0 -34
- package/src/core/resolve-omlx-base-url.js +0 -84
- package/src/vue/docs/use-agent.md +0 -29
- package/src/vue/docs/use-omlx.md +0 -30
- package/src/vue/use-agent.js +0 -60
- package/src/vue/use-omlx.js +0 -102
- package/types/core/agent-handler.d.ts +0 -43
- package/types/core/agent-kit.d.ts +0 -30
- package/types/core/llm.d.ts +0 -52
- package/types/core/omlx-models.d.ts +0 -17
- package/types/core/resolve-omlx-base-url.d.ts +0 -42
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.11.0] - 2026-07-17
|
|
4
|
+
|
|
5
|
+
### Removed
|
|
6
|
+
|
|
7
|
+
- useAgent()/createAgentKit (omlx/runAgent chat-completion шлях) видалено — `useAcpAgent()`/`createAcpAgentKit` (ACP: спавн codex/claude/cursor/pi) тепер єдиний агентний шлях. Разом з ним прибрано `core/llm.js`, `core/agent-handler.js`, `core/omlx-models.js`, `core/resolve-omlx-base-url.js`, `vue/use-omlx.js` та Rust-команду `omlx_config` (tauri-plugin-agent)
|
|
8
|
+
|
|
3
9
|
## [0.10.1] - 2026-07-16
|
|
4
10
|
|
|
5
11
|
### Changed
|
package/package.json
CHANGED
|
@@ -3,33 +3,26 @@
|
|
|
3
3
|
@update:model-value="val => emit('update:modelValue', val)"
|
|
4
4
|
@show="onShow"
|
|
5
5
|
:model-value="modelValue"
|
|
6
|
-
title="Agent
|
|
6
|
+
title="Agent"
|
|
7
7
|
icon="sym_o_smart_toy"
|
|
8
8
|
:width="560"
|
|
9
|
-
body-class="q-gutter-sm"
|
|
10
|
-
>
|
|
9
|
+
body-class="q-gutter-sm">
|
|
11
10
|
<template #header>
|
|
12
|
-
<q-btn @click="showConfig = !showConfig" icon="sym_o_tune" flat round dense size="sm" title="
|
|
11
|
+
<q-btn @click="showConfig = !showConfig" icon="sym_o_tune" flat round dense size="sm" title="agent config" />
|
|
13
12
|
</template>
|
|
14
13
|
|
|
15
14
|
<template v-if="showConfig">
|
|
16
|
-
<q-
|
|
15
|
+
<q-select v-model="agentKind" :options="availableAgentKinds" dense outlined label="agent" />
|
|
17
16
|
<q-select
|
|
18
|
-
v-model="
|
|
19
|
-
:options="
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
hide-selected
|
|
25
|
-
input-debounce="0"
|
|
26
|
-
new-value-mode="add-unique"
|
|
17
|
+
v-model="modelTier"
|
|
18
|
+
:options="availableTiers"
|
|
19
|
+
option-value="id"
|
|
20
|
+
option-label="label"
|
|
21
|
+
emit-value
|
|
22
|
+
map-options
|
|
27
23
|
dense
|
|
28
24
|
outlined
|
|
29
|
-
label="
|
|
30
|
-
hint="завантажені моделі omlx; можна вписати свою"
|
|
31
|
-
/>
|
|
32
|
-
<q-input v-model="apiKey" dense outlined label="API key" type="password" />
|
|
25
|
+
label="tier" />
|
|
33
26
|
<q-separator class="q-my-sm" />
|
|
34
27
|
</template>
|
|
35
28
|
|
|
@@ -38,9 +31,7 @@
|
|
|
38
31
|
<div v-if="turn.role === 'user'" class="chat-user">{{ turn.text }}</div>
|
|
39
32
|
<RequestView v-else :result="turn.result" />
|
|
40
33
|
</template>
|
|
41
|
-
<div v-if="running" class="chat-thinking">
|
|
42
|
-
<q-spinner-dots size="18px" /> думаю…
|
|
43
|
-
</div>
|
|
34
|
+
<div v-if="running" class="chat-thinking"><q-spinner-dots size="18px" /> думаю…</div>
|
|
44
35
|
</div>
|
|
45
36
|
|
|
46
37
|
<q-input
|
|
@@ -52,8 +43,7 @@
|
|
|
52
43
|
type="textarea"
|
|
53
44
|
autogrow
|
|
54
45
|
:label="inputLabel"
|
|
55
|
-
:hint="promptHint"
|
|
56
|
-
/>
|
|
46
|
+
:hint="promptHint" />
|
|
57
47
|
|
|
58
48
|
<!-- jscpd:ignore-start — formal footer markup; jscpd html-mode aligns its
|
|
59
49
|
attribute tokens with an unrelated import block (no shared logic) -->
|
|
@@ -64,8 +54,7 @@
|
|
|
64
54
|
:submit-label="sendLabel"
|
|
65
55
|
icon="sym_o_play_arrow"
|
|
66
56
|
:disable="sendDisabled"
|
|
67
|
-
:loading="running"
|
|
68
|
-
/>
|
|
57
|
+
:loading="running" />
|
|
69
58
|
</template>
|
|
70
59
|
<!-- jscpd:ignore-end -->
|
|
71
60
|
</BaseDialog>
|
|
@@ -78,17 +67,17 @@ import BaseDialog from './BaseDialog.vue'
|
|
|
78
67
|
import DialogActions from './DialogActions.vue'
|
|
79
68
|
import RequestView from './RequestView.vue'
|
|
80
69
|
|
|
81
|
-
// The agent gateway is injected by the host app (`
|
|
82
|
-
// @7n/tauri-components/vue), so this dialog stays domain-free and testable.
|
|
70
|
+
// The agent gateway is injected by the host app (`useAcpAgent({ catalog, … })`
|
|
71
|
+
// from @7n/tauri-components/vue), so this dialog stays domain-free and testable.
|
|
83
72
|
const props = defineProps({
|
|
84
73
|
modelValue: { type: Boolean, default: false },
|
|
85
74
|
agent: { type: Object, required: true },
|
|
86
|
-
promptHint: { type: String, default: 'наприклад: Create a task named deploy in /Users/.../mt, agent mode' }
|
|
75
|
+
promptHint: { type: String, default: 'наприклад: Create a task named deploy in /Users/.../mt, agent mode' }
|
|
87
76
|
})
|
|
88
77
|
const emit = defineEmits(['update:modelValue', 'ran'])
|
|
89
78
|
|
|
90
79
|
const $q = useQuasar()
|
|
91
|
-
const {
|
|
80
|
+
const { agentKind, modelTier, availableAgentKinds, availableTiers, loadEnv, request, respond } = props.agent
|
|
92
81
|
|
|
93
82
|
const prompt = ref('')
|
|
94
83
|
const running = ref(false)
|
|
@@ -96,8 +85,6 @@ const turns = ref([])
|
|
|
96
85
|
const requestId = ref(null)
|
|
97
86
|
const logEl = ref(null)
|
|
98
87
|
const showConfig = ref(false)
|
|
99
|
-
const models = ref([])
|
|
100
|
-
const modelsLoading = ref(false)
|
|
101
88
|
|
|
102
89
|
// Labels shift once a conversation is under way (fresh request → follow-up).
|
|
103
90
|
const inputLabel = computed(() => (turns.value.length ? 'Повідомлення' : 'Prompt'))
|
|
@@ -105,23 +92,13 @@ const sendLabel = computed(() => (turns.value.length ? 'Надіслати' : '
|
|
|
105
92
|
const sendDisabled = computed(() => running.value || !prompt.value.trim())
|
|
106
93
|
|
|
107
94
|
/**
|
|
108
|
-
*
|
|
95
|
+
* Resolve the per-machine default agent + start the domain MCP bridge; reset the conversation.
|
|
109
96
|
*/
|
|
110
97
|
async function onShow() {
|
|
111
98
|
prompt.value = ''
|
|
112
99
|
turns.value = []
|
|
113
100
|
requestId.value = null
|
|
114
|
-
await
|
|
115
|
-
// Populate the model dropdown from the omlx server (best-effort; stays editable).
|
|
116
|
-
if (listModels) {
|
|
117
|
-
modelsLoading.value = true
|
|
118
|
-
try {
|
|
119
|
-
models.value = await listModels()
|
|
120
|
-
}
|
|
121
|
-
finally {
|
|
122
|
-
modelsLoading.value = false
|
|
123
|
-
}
|
|
124
|
-
}
|
|
101
|
+
await loadEnv()
|
|
125
102
|
}
|
|
126
103
|
|
|
127
104
|
/**
|
|
@@ -154,12 +131,10 @@ async function send() {
|
|
|
154
131
|
scrollToEnd()
|
|
155
132
|
running.value = true
|
|
156
133
|
try {
|
|
157
|
-
saveOmlx()
|
|
158
134
|
apply(await (requestId.value ? respond(requestId.value, text) : request(text)))
|
|
159
135
|
} catch (error) {
|
|
160
136
|
$q.notify({ type: 'negative', message: String(error?.message ?? error) })
|
|
161
|
-
}
|
|
162
|
-
finally {
|
|
137
|
+
} finally {
|
|
163
138
|
running.value = false
|
|
164
139
|
}
|
|
165
140
|
}
|
|
@@ -6,8 +6,7 @@
|
|
|
6
6
|
title="Request journal"
|
|
7
7
|
icon="sym_o_history"
|
|
8
8
|
:width="680"
|
|
9
|
-
body-class=""
|
|
10
|
-
>
|
|
9
|
+
body-class="">
|
|
11
10
|
<template #header>
|
|
12
11
|
<q-btn @click="refresh" icon="sym_o_refresh" flat round dense size="sm" :loading="loading" title="Refresh" />
|
|
13
12
|
</template>
|
|
@@ -36,8 +35,7 @@
|
|
|
36
35
|
color="negative"
|
|
37
36
|
unelevated
|
|
38
37
|
no-caps
|
|
39
|
-
:loading="busyId === rec.id"
|
|
40
|
-
/>
|
|
38
|
+
:loading="busyId === rec.id" />
|
|
41
39
|
<q-btn @click="onApprove(rec, false)" label="Відхилити" flat no-caps :disable="busyId === rec.id" />
|
|
42
40
|
</div>
|
|
43
41
|
</div>
|
|
@@ -57,7 +55,7 @@ import StatePill from './StatePill.vue'
|
|
|
57
55
|
// keeping this dialog domain-free.
|
|
58
56
|
const props = defineProps({
|
|
59
57
|
modelValue: { type: Boolean, default: false },
|
|
60
|
-
agent: { type: Object, required: true }
|
|
58
|
+
agent: { type: Object, required: true }
|
|
61
59
|
})
|
|
62
60
|
const emit = defineEmits(['update:modelValue', 'changed'])
|
|
63
61
|
|
|
@@ -84,11 +82,9 @@ async function refresh() {
|
|
|
84
82
|
loading.value = true
|
|
85
83
|
try {
|
|
86
84
|
records.value = await journal.list()
|
|
87
|
-
}
|
|
88
|
-
catch (error) {
|
|
85
|
+
} catch (error) {
|
|
89
86
|
$q.notify({ type: 'negative', message: String(error?.message ?? error) })
|
|
90
|
-
}
|
|
91
|
-
finally {
|
|
87
|
+
} finally {
|
|
92
88
|
loading.value = false
|
|
93
89
|
}
|
|
94
90
|
}
|
|
@@ -111,11 +107,9 @@ async function onRespond(rec, message) {
|
|
|
111
107
|
await respond(rec.id, message)
|
|
112
108
|
await refresh()
|
|
113
109
|
emit('changed')
|
|
114
|
-
}
|
|
115
|
-
catch (error) {
|
|
110
|
+
} catch (error) {
|
|
116
111
|
$q.notify({ type: 'negative', message: String(error?.message ?? error) })
|
|
117
|
-
}
|
|
118
|
-
finally {
|
|
112
|
+
} finally {
|
|
119
113
|
busyId.value = null
|
|
120
114
|
}
|
|
121
115
|
}
|
|
@@ -131,11 +125,9 @@ async function onApprove(rec, ok) {
|
|
|
131
125
|
await approve(rec.id, ok)
|
|
132
126
|
await refresh()
|
|
133
127
|
emit('changed')
|
|
134
|
-
}
|
|
135
|
-
catch (error) {
|
|
128
|
+
} catch (error) {
|
|
136
129
|
$q.notify({ type: 'negative', message: String(error?.message ?? error) })
|
|
137
|
-
}
|
|
138
|
-
finally {
|
|
130
|
+
} finally {
|
|
139
131
|
busyId.value = null
|
|
140
132
|
}
|
|
141
133
|
}
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<q-dialog
|
|
3
|
-
@update:model-value="value => emit('update:modelValue', value)"
|
|
4
|
-
v-bind="$attrs"
|
|
5
|
-
:model-value="modelValue"
|
|
6
|
-
>
|
|
2
|
+
<q-dialog @update:model-value="value => emit('update:modelValue', value)" v-bind="$attrs" :model-value="modelValue">
|
|
7
3
|
<q-card class="base-dialog" :style="{ width: `${width}px` }">
|
|
8
4
|
<q-card-section class="row items-center no-wrap q-pb-sm">
|
|
9
5
|
<q-icon v-if="icon" :name="icon" size="20px" class="q-mr-sm" />
|
|
@@ -40,7 +36,7 @@ defineProps({
|
|
|
40
36
|
title: { type: String, required: true },
|
|
41
37
|
icon: { type: String, default: '' },
|
|
42
38
|
width: { type: Number, default: 520 },
|
|
43
|
-
bodyClass: { type: String, default: 'q-gutter-md' }
|
|
39
|
+
bodyClass: { type: String, default: 'q-gutter-md' }
|
|
44
40
|
})
|
|
45
41
|
const emit = defineEmits(['update:modelValue'])
|
|
46
42
|
</script>
|
|
@@ -8,8 +8,7 @@
|
|
|
8
8
|
:loading="loading"
|
|
9
9
|
color="primary"
|
|
10
10
|
unelevated
|
|
11
|
-
no-caps
|
|
12
|
-
/>
|
|
11
|
+
no-caps />
|
|
13
12
|
</template>
|
|
14
13
|
|
|
15
14
|
<script setup>
|
|
@@ -20,7 +19,7 @@ defineProps({
|
|
|
20
19
|
submitLabel: { type: String, required: true },
|
|
21
20
|
icon: { type: String, default: undefined },
|
|
22
21
|
disable: { type: Boolean, default: false },
|
|
23
|
-
loading: { type: Boolean, default: false }
|
|
22
|
+
loading: { type: Boolean, default: false }
|
|
24
23
|
})
|
|
25
24
|
const emit = defineEmits(['submit'])
|
|
26
25
|
</script>
|
|
@@ -11,8 +11,7 @@
|
|
|
11
11
|
<q-icon
|
|
12
12
|
:name="action.envelope?.ok ? 'sym_o_check_circle' : 'sym_o_error'"
|
|
13
13
|
:color="action.envelope?.ok ? 'positive' : 'negative'"
|
|
14
|
-
size="14px"
|
|
15
|
-
/>
|
|
14
|
+
size="14px" />
|
|
16
15
|
<code>{{ action.tool }}({{ JSON.stringify(action.input) }})</code>
|
|
17
16
|
</div>
|
|
18
17
|
</div>
|
|
@@ -23,7 +22,7 @@
|
|
|
23
22
|
import StatePill from './StatePill.vue'
|
|
24
23
|
|
|
25
24
|
defineProps({
|
|
26
|
-
result: { type: Object, required: true }
|
|
25
|
+
result: { type: Object, required: true }
|
|
27
26
|
})
|
|
28
27
|
</script>
|
|
29
28
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
---
|
|
2
|
+
type: Vue Component
|
|
3
|
+
title: AgentDialog.vue
|
|
4
|
+
resource: npm/src/components/AgentDialog.vue
|
|
2
5
|
docgen:
|
|
3
|
-
|
|
4
|
-
crc: 197cc9c0
|
|
6
|
+
crc: aa14e51b
|
|
5
7
|
model: omlx/gemma-4-e4b-it-OptiQ-4bit
|
|
6
8
|
---
|
|
7
9
|
|
|
8
|
-
# AgentDialog.vue
|
|
9
|
-
|
|
10
10
|
Компонент відображає діалогове вікно для взаємодії з локальною мовною моделлю (LLM) через агент. Він дозволяє користувачеві вводити запити, налаштовувати конфігурацію агента та бачити історію чату.
|
|
11
11
|
|
|
12
12
|
## Поведінка
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
---
|
|
2
|
+
type: JS Module
|
|
3
|
+
title: index.js
|
|
4
|
+
resource: npm/src/components/index.js
|
|
2
5
|
docgen:
|
|
3
|
-
|
|
4
|
-
crc: 42b50c52
|
|
6
|
+
crc: 6d136e3a
|
|
5
7
|
model: omlx/gemma-4-e4b-it-OptiQ-4bit
|
|
6
8
|
score: 100
|
|
7
9
|
judgeModel: openai-codex/gpt-5.4-mini
|
|
8
10
|
---
|
|
9
11
|
|
|
10
|
-
# index.js
|
|
11
|
-
|
|
12
12
|
## Огляд
|
|
13
13
|
|
|
14
14
|
Компоненти цього файлу керують відображенням діалогових вікон, які взаємодіють з агентом. Експортується базовий клас для діалогів, компоненти для відображення специфічних дій, деталей запиту та поточного стану, а також константи для визначення кольорів стану.
|
package/src/components/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// `@7n/tauri-components/components` — Quasar dialogs for the agent. AgentDialog
|
|
2
|
-
// and AuditDialog take the injected `agent` gateway (from ./vue
|
|
3
|
-
// prop; the rest are pure props-only building blocks. Requires the host app to
|
|
2
|
+
// and AuditDialog take the injected `agent` gateway (from ./vue useAcpAgent) via
|
|
3
|
+
// a prop; the rest are pure props-only building blocks. Requires the host app to
|
|
4
4
|
// provide vue + quasar (peer deps) and to have Quasar installed.
|
|
5
5
|
|
|
6
6
|
export { default as AgentDialog } from './AgentDialog.vue'
|
package/src/components/status.js
CHANGED
|
@@ -20,6 +20,6 @@ export const CODEX_ACP_AGENT_PRESET = {
|
|
|
20
20
|
tiers: {
|
|
21
21
|
MIN: { label: 'GPT-5.6 Luna', env: codexModelEnv('gpt-5.6-luna') },
|
|
22
22
|
AVG: { label: 'GPT-5.6 Terra', env: codexModelEnv('gpt-5.6-terra') },
|
|
23
|
-
MAX: { label: 'GPT-5.6 Sol', env: codexModelEnv('gpt-5.6-sol') }
|
|
24
|
-
}
|
|
23
|
+
MAX: { label: 'GPT-5.6 Sol', env: codexModelEnv('gpt-5.6-sol') }
|
|
24
|
+
}
|
|
25
25
|
}
|
package/src/core/acp-agent.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { invoke } from '@tauri-apps/api/core'
|
|
2
2
|
import { listen } from '@tauri-apps/api/event'
|
|
3
3
|
|
|
4
|
-
// ACP session driver:
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// return, so agent-handler.js's runAndJournal needs no structural changes —
|
|
10
|
-
// only which turn-runner it calls.
|
|
4
|
+
// ACP session driver: instead of running our own chat-completion loop, we
|
|
5
|
+
// spawn an external ACP agent (codex-acp/claude-agent-acp/cursor `agent acp`/
|
|
6
|
+
// pi-acp) via the Rust plugin's acp_* commands and stream its `session/update`
|
|
7
|
+
// notifications back into a {content, trace, messages, stopped?} shape that
|
|
8
|
+
// acp-kit.js's runAndJournal folds straight into the journal.
|
|
11
9
|
//
|
|
12
10
|
// Domain-tool approval (the MCP bridge's `acp://mcp-tool-call`) and native ACP
|
|
13
11
|
// permission requests (`acp://permission-request`) are separate event streams
|
|
@@ -30,9 +28,18 @@ import { listen } from '@tauri-apps/api/event'
|
|
|
30
28
|
* @param {boolean} [params.allowTerminal] grant terminal/*
|
|
31
29
|
* @returns {Promise<{sessionKey: string, agentKind: string}>} session handle
|
|
32
30
|
*/
|
|
33
|
-
export async function createAcpSession({
|
|
31
|
+
export async function createAcpSession({
|
|
32
|
+
agentKind,
|
|
33
|
+
command,
|
|
34
|
+
args = [],
|
|
35
|
+
env = {},
|
|
36
|
+
cwd,
|
|
37
|
+
mcpBridgeUrl,
|
|
38
|
+
allowFs = false,
|
|
39
|
+
allowTerminal = false
|
|
40
|
+
}) {
|
|
34
41
|
const sessionKey = await invoke('plugin:agent|acp_spawn_agent', {
|
|
35
|
-
args: { command, args, env, cwd, mcpBridgeUrl, allowFs, allowTerminal }
|
|
42
|
+
args: { command, args, env, cwd, mcpBridgeUrl, allowFs, allowTerminal }
|
|
36
43
|
})
|
|
37
44
|
return { sessionKey, agentKind }
|
|
38
45
|
}
|
|
@@ -70,7 +77,7 @@ function applySessionUpdate(state, update) {
|
|
|
70
77
|
state.calls.set(update.toolCallId, {
|
|
71
78
|
tool: update.title ?? update.toolCallId,
|
|
72
79
|
input: update.rawInput ?? {},
|
|
73
|
-
envelope: null
|
|
80
|
+
envelope: null
|
|
74
81
|
})
|
|
75
82
|
break
|
|
76
83
|
}
|
|
@@ -100,7 +107,7 @@ function applySessionUpdate(state, update) {
|
|
|
100
107
|
export async function runAcpTurn({ sessionKey, text, onChunk }) {
|
|
101
108
|
const state = { text: '', messageId: null, calls: new Map() }
|
|
102
109
|
|
|
103
|
-
const unlisten = await listen('acp://session-update',
|
|
110
|
+
const unlisten = await listen('acp://session-update', event => {
|
|
104
111
|
if (event.payload?.sessionKey !== sessionKey) return
|
|
105
112
|
applySessionUpdate(state, event.payload.update)
|
|
106
113
|
onChunk?.(event.payload.update)
|
|
@@ -115,10 +122,9 @@ export async function runAcpTurn({ sessionKey, text, onChunk }) {
|
|
|
115
122
|
steps: 1,
|
|
116
123
|
trace,
|
|
117
124
|
messages,
|
|
118
|
-
stopped: stoppedFromStopReason(stopReason)
|
|
125
|
+
stopped: stoppedFromStopReason(stopReason)
|
|
119
126
|
}
|
|
120
|
-
}
|
|
121
|
-
finally {
|
|
127
|
+
} finally {
|
|
122
128
|
unlisten()
|
|
123
129
|
}
|
|
124
130
|
}
|
|
@@ -175,7 +181,7 @@ export function respondAcpPermission(requestId, optionId) {
|
|
|
175
181
|
|
|
176
182
|
/**
|
|
177
183
|
* Register this app's tool catalog with the domain MCP bridge and start it.
|
|
178
|
-
* @param {object[]} catalog tool definitions (same shape passed to `
|
|
184
|
+
* @param {object[]} catalog tool definitions (same shape passed to `createAcpAgentKit`)
|
|
179
185
|
* @returns {Promise<string>} the bridge's loopback URL, e.g. `http://127.0.0.1:54321/`
|
|
180
186
|
*/
|
|
181
187
|
export async function startAcpMcpBridge(catalog) {
|
package/src/core/acp-kit.js
CHANGED
|
@@ -3,17 +3,15 @@ import { createDispatch } from './dispatch.js'
|
|
|
3
3
|
import { classify, DEFAULT_ACTOR_TIERS } from './scope.js'
|
|
4
4
|
|
|
5
5
|
// createAcpAgentKit binds an app's catalog + journal to the ACP execution
|
|
6
|
-
// engine — the
|
|
7
|
-
//
|
|
8
|
-
// path while migrating at their own pace (see SPEC / plan for the sequencing).
|
|
6
|
+
// engine — the sole agent kit (the earlier omlx/runAgent-based createAgentKit
|
|
7
|
+
// has been removed; see CHANGELOG).
|
|
9
8
|
//
|
|
10
|
-
// The
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
// construction only an autonomous agent process can reach the bridge.
|
|
9
|
+
// The ACP agent process drives its OWN tool-calling loop and calls domain
|
|
10
|
+
// tools through the Rust-side MCP bridge — so dispatch is triggered by a
|
|
11
|
+
// STANDING listener on `acp://mcp-tool-call`, not by anything in
|
|
12
|
+
// request()/respond(). Every such call is tier-classified as an
|
|
13
|
+
// `{kind:'agent'}` actor, since by construction only an autonomous agent
|
|
14
|
+
// process can reach the bridge.
|
|
17
15
|
//
|
|
18
16
|
// Scope: one active session/request at a time, matching how AgentDialog is
|
|
19
17
|
// actually used today (a single live conversation). A tool call or permission
|
|
@@ -43,7 +41,7 @@ function finalizeTurn(turn) {
|
|
|
43
41
|
if (turn.stopped === 'max_steps' || turn.stopped === 'refusal') status = 'partial'
|
|
44
42
|
else if (turn.stopped === 'cancelled') status = 'partial'
|
|
45
43
|
else if (question) status = 'needs_clarification'
|
|
46
|
-
return { status, summary: question ? null :
|
|
44
|
+
return { status, summary: question ? null : turn.content || null, question }
|
|
47
45
|
}
|
|
48
46
|
|
|
49
47
|
/**
|
|
@@ -53,9 +51,15 @@ function finalizeTurn(turn) {
|
|
|
53
51
|
* @param {(tool: object, input: object) => unknown} [config.transport] backend runner for domain tools; omit for a chat-only kit (no domain MCP tools)
|
|
54
52
|
* @param {Record<string, number>} [config.actorTiers] max executable tier rank per actor kind
|
|
55
53
|
* @param {object} [config.deps] injectable `acp-agent.js` functions (tests only — defaults to the real module)
|
|
56
|
-
* @returns {{request:
|
|
54
|
+
* @returns {{request: (opts: {intent: string, agent: object}) => Promise<object>, respond: (opts: {requestId: string, message: string}) => Promise<object>, approve: (opts: {requestId: string, approve: boolean}) => Promise<object>}} bound kit
|
|
57
55
|
*/
|
|
58
|
-
export function createAcpAgentKit({
|
|
56
|
+
export function createAcpAgentKit({
|
|
57
|
+
catalog,
|
|
58
|
+
journal,
|
|
59
|
+
transport,
|
|
60
|
+
actorTiers = DEFAULT_ACTOR_TIERS,
|
|
61
|
+
deps = acpAgent
|
|
62
|
+
} = {}) {
|
|
59
63
|
if (!Array.isArray(catalog)) throw new Error('createAcpAgentKit: catalog (array) is required')
|
|
60
64
|
|
|
61
65
|
const dispatch = transport ? createDispatch(catalog, transport) : null
|
|
@@ -71,17 +75,26 @@ export function createAcpAgentKit({ catalog, journal, transport, actorTiers = DE
|
|
|
71
75
|
*/
|
|
72
76
|
async function handleToolCall({ requestId, tool, input }) {
|
|
73
77
|
if (!dispatch) {
|
|
74
|
-
await deps.respondAcpToolCall(requestId, {
|
|
78
|
+
await deps.respondAcpToolCall(requestId, {
|
|
79
|
+
ok: false,
|
|
80
|
+
error: { code: 'forbidden', message: 'No transport configured.' }
|
|
81
|
+
})
|
|
75
82
|
return
|
|
76
83
|
}
|
|
77
84
|
const decision = classify(catalog, actorTiers, AGENT_ACTOR, tool)
|
|
78
85
|
if (decision === 'deny') {
|
|
79
|
-
await deps.respondAcpToolCall(requestId, {
|
|
86
|
+
await deps.respondAcpToolCall(requestId, {
|
|
87
|
+
ok: false,
|
|
88
|
+
error: { code: 'forbidden', message: `Tool "${tool}" is not allowed.` }
|
|
89
|
+
})
|
|
80
90
|
return
|
|
81
91
|
}
|
|
82
92
|
if (decision === 'approval') {
|
|
83
93
|
if (activeRequestId) {
|
|
84
|
-
await journal.update(activeRequestId, {
|
|
94
|
+
await journal.update(activeRequestId, {
|
|
95
|
+
status: 'needs_approval',
|
|
96
|
+
pendingApproval: { kind: 'mcp', requestId, tool, input }
|
|
97
|
+
})
|
|
85
98
|
}
|
|
86
99
|
return // acp_mcp_tool_result comes later, from approve()
|
|
87
100
|
}
|
|
@@ -127,8 +140,7 @@ export function createAcpAgentKit({ catalog, journal, transport, actorTiers = DE
|
|
|
127
140
|
let turn
|
|
128
141
|
try {
|
|
129
142
|
turn = await turnPromise
|
|
130
|
-
}
|
|
131
|
-
catch (error) {
|
|
143
|
+
} catch (error) {
|
|
132
144
|
await journal.update(requestId, { status: 'failed', error: String(error?.message ?? error) })
|
|
133
145
|
return { requestId, status: 'failed', summary: null, actions: baseActions, question: null, pendingApproval: null }
|
|
134
146
|
}
|
|
@@ -163,18 +175,35 @@ export function createAcpAgentKit({ catalog, journal, transport, actorTiers = DE
|
|
|
163
175
|
*/
|
|
164
176
|
async respond({ requestId, message }) {
|
|
165
177
|
if (!activeSessionKey) {
|
|
166
|
-
return {
|
|
178
|
+
return {
|
|
179
|
+
requestId,
|
|
180
|
+
status: 'failed',
|
|
181
|
+
summary: null,
|
|
182
|
+
actions: [],
|
|
183
|
+
question: 'No active ACP session.',
|
|
184
|
+
pendingApproval: null
|
|
185
|
+
}
|
|
167
186
|
}
|
|
168
187
|
let record
|
|
169
188
|
try {
|
|
170
189
|
record = await journal.load(requestId)
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
|
|
190
|
+
} catch {
|
|
191
|
+
return {
|
|
192
|
+
requestId,
|
|
193
|
+
status: 'failed',
|
|
194
|
+
summary: null,
|
|
195
|
+
actions: [],
|
|
196
|
+
question: 'Request not found.',
|
|
197
|
+
pendingApproval: null
|
|
198
|
+
}
|
|
174
199
|
}
|
|
175
200
|
await journal.update(requestId, { status: 'running' })
|
|
176
201
|
activeRequestId = requestId
|
|
177
|
-
return runAndJournal(
|
|
202
|
+
return runAndJournal(
|
|
203
|
+
requestId,
|
|
204
|
+
record.actions ?? [],
|
|
205
|
+
deps.runAcpTurn({ sessionKey: activeSessionKey, text: message })
|
|
206
|
+
)
|
|
178
207
|
},
|
|
179
208
|
|
|
180
209
|
/**
|
|
@@ -187,19 +216,32 @@ export function createAcpAgentKit({ catalog, journal, transport, actorTiers = DE
|
|
|
187
216
|
let record
|
|
188
217
|
try {
|
|
189
218
|
record = await journal.load(requestId)
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
|
|
219
|
+
} catch {
|
|
220
|
+
return {
|
|
221
|
+
requestId,
|
|
222
|
+
status: 'failed',
|
|
223
|
+
summary: null,
|
|
224
|
+
actions: [],
|
|
225
|
+
question: 'Request not found.',
|
|
226
|
+
pendingApproval: null
|
|
227
|
+
}
|
|
193
228
|
}
|
|
194
229
|
|
|
195
230
|
const pending = record.pendingApproval
|
|
196
231
|
if (record.status !== 'needs_approval' || !pending) {
|
|
197
|
-
return {
|
|
232
|
+
return {
|
|
233
|
+
requestId,
|
|
234
|
+
status: record.status,
|
|
235
|
+
summary: record.summary,
|
|
236
|
+
actions: record.actions ?? [],
|
|
237
|
+
question: null,
|
|
238
|
+
pendingApproval: null
|
|
239
|
+
}
|
|
198
240
|
}
|
|
199
241
|
|
|
200
242
|
if (pending.kind === 'acp') return approveAcpPermission(deps, journal, requestId, record, pending, decision)
|
|
201
243
|
return approveMcpToolCall(deps, dispatch, journal, requestId, record, pending, decision)
|
|
202
|
-
}
|
|
244
|
+
}
|
|
203
245
|
}
|
|
204
246
|
}
|
|
205
247
|
|
|
@@ -216,7 +258,7 @@ export function createAcpAgentKit({ catalog, journal, transport, actorTiers = DE
|
|
|
216
258
|
* means the old "retry a failed dispatch" UX doesn't carry over: once the
|
|
217
259
|
* agent has been told a tool call failed, it has already moved on.
|
|
218
260
|
* @param {object} deps injected acp-agent.js functions
|
|
219
|
-
* @param {
|
|
261
|
+
* @param {(name: string, input?: object) => Promise<object>} dispatch bound `createDispatch` result
|
|
220
262
|
* @param {object} journal journal store
|
|
221
263
|
* @param {string} requestId journal record id
|
|
222
264
|
* @param {object} record loaded journal record
|
|
@@ -256,5 +298,12 @@ async function approveAcpPermission(deps, journal, requestId, record, pending, d
|
|
|
256
298
|
await deps.respondAcpPermission(pending.requestId, option?.optionId)
|
|
257
299
|
|
|
258
300
|
await journal.update(requestId, { status: 'running', pendingApproval: null })
|
|
259
|
-
return {
|
|
301
|
+
return {
|
|
302
|
+
requestId,
|
|
303
|
+
status: 'running',
|
|
304
|
+
summary: null,
|
|
305
|
+
actions: record.actions ?? [],
|
|
306
|
+
question: null,
|
|
307
|
+
pendingApproval: null
|
|
308
|
+
}
|
|
260
309
|
}
|