@things-factory/ai-assistant 10.1.113 → 10.1.116
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/client/components/assistant-chat.ts +19 -0
- package/client/components/assistant-dock.ts +26 -3
- package/client/setup-assistant-dock.ts +3 -2
- package/client/utils/entry-proposal.ts +14 -1
- package/client/utils/open-page.ts +21 -0
- package/dist-client/components/assistant-chat.d.ts +2 -0
- package/dist-client/components/assistant-chat.js +19 -0
- package/dist-client/components/assistant-chat.js.map +1 -1
- package/dist-client/components/assistant-dock.d.ts +5 -0
- package/dist-client/components/assistant-dock.js +25 -3
- package/dist-client/components/assistant-dock.js.map +1 -1
- package/dist-client/setup-assistant-dock.d.ts +1 -0
- package/dist-client/setup-assistant-dock.js +1 -0
- package/dist-client/setup-assistant-dock.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/utils/entry-proposal.d.ts +10 -1
- package/dist-client/utils/entry-proposal.js +13 -1
- package/dist-client/utils/entry-proposal.js.map +1 -1
- package/dist-client/utils/open-page.d.ts +5 -0
- package/dist-client/utils/open-page.js +19 -0
- package/dist-client/utils/open-page.js.map +1 -0
- package/dist-server/service/assistant-mode/resolve.d.ts +1 -0
- package/dist-server/service/assistant-mode/resolve.js +2 -1
- package/dist-server/service/assistant-mode/resolve.js.map +1 -1
- package/dist-server/service/assistant-mode/types.d.ts +2 -0
- package/dist-server/service/assistant-mode/types.js.map +1 -1
- package/dist-server/service/chat-session/attachment-tool-result.d.ts +28 -0
- package/dist-server/service/chat-session/attachment-tool-result.js +9 -0
- package/dist-server/service/chat-session/attachment-tool-result.js.map +1 -0
- package/dist-server/service/chat-session/attachment-tools.js +4 -5
- package/dist-server/service/chat-session/attachment-tools.js.map +1 -1
- package/dist-server/service/data-entry/entry-apply.d.ts +8 -0
- package/dist-server/service/data-entry/entry-apply.js +19 -2
- package/dist-server/service/data-entry/entry-apply.js.map +1 -1
- package/dist-server/service/data-entry/entry-refusal.d.ts +6 -0
- package/dist-server/service/data-entry/entry-refusal.js +25 -0
- package/dist-server/service/data-entry/entry-refusal.js.map +1 -0
- package/dist-server/service/data-entry/entry-resolver.js +2 -0
- package/dist-server/service/data-entry/entry-resolver.js.map +1 -1
- package/dist-server/service/data-read/list-read.d.ts +36 -0
- package/dist-server/service/data-read/list-read.js +64 -0
- package/dist-server/service/data-read/list-read.js.map +1 -0
- package/dist-server/service/data-read/read-target.d.ts +109 -0
- package/dist-server/service/data-read/read-target.js +189 -0
- package/dist-server/service/data-read/read-target.js.map +1 -0
- package/dist-server/service/data-read/read-tools.d.ts +228 -0
- package/dist-server/service/data-read/read-tools.js +197 -0
- package/dist-server/service/data-read/read-tools.js.map +1 -0
- package/dist-server/service/index.d.ts +3 -0
- package/dist-server/service/index.js +7 -1
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/server/service/assistant-mode/resolve.ts +3 -2
- package/server/service/assistant-mode/types.ts +2 -0
- package/server/service/chat-session/attachment-tool-result.test.ts +26 -0
- package/server/service/chat-session/attachment-tool-result.ts +18 -0
- package/server/service/chat-session/attachment-tools.ts +4 -5
- package/server/service/data-entry/data-entry-update.test.ts +10 -1
- package/server/service/data-entry/data-entry.test.ts +29 -1
- package/server/service/data-entry/entry-apply.ts +27 -2
- package/server/service/data-entry/entry-refusal.test.ts +73 -0
- package/server/service/data-entry/entry-refusal.ts +20 -0
- package/server/service/data-entry/entry-resolver.ts +2 -0
- package/server/service/data-read/data-read.test.ts +229 -0
- package/server/service/data-read/list-read.ts +98 -0
- package/server/service/data-read/read-target.ts +230 -0
- package/server/service/data-read/read-tools.ts +212 -0
- package/server/service/index.ts +4 -0
- package/test/entry-proposal.test.ts +31 -1
- package/test/open-page.test.ts +31 -0
- package/translations/en.json +14 -0
- package/translations/ja.json +14 -0
- package/translations/ko.json +14 -0
- package/translations/ms.json +14 -0
- package/translations/zh.json +14 -0
|
@@ -70,6 +70,7 @@ import { availableTriggers, type TriggerChip } from './chat-triggers.js'
|
|
|
70
70
|
import './entry-review.js'
|
|
71
71
|
import { entryGroupOf, entryReadyCount, entryTitle, isChangeProposal, isEntryProposal, latestEntryKeys } from '../utils/entry-proposal.js'
|
|
72
72
|
import { isWorkspaceProposal, versionOfCard, workspaceAddress } from '../utils/workspace.js'
|
|
73
|
+
import { isOpenPageProposal, openPageNote } from '../utils/open-page.js'
|
|
73
74
|
|
|
74
75
|
interface ChatLine {
|
|
75
76
|
role: 'user' | 'assistant' | 'system'
|
|
@@ -4321,6 +4322,8 @@ export class OxAssistantChat extends LitElement {
|
|
|
4321
4322
|
const key = this.proposalKey(p)
|
|
4322
4323
|
/* A workspace card opens the page; it runs nothing (design/ai/workspace.md §4). */
|
|
4323
4324
|
if (isWorkspaceProposal(p)) return this.renderWorkspaceCard(p, key)
|
|
4325
|
+
/* A screen card opens the application's list with the question's conditions; it runs nothing (design/ai/read-targets.md §5). */
|
|
4326
|
+
if (isOpenPageProposal(p)) return this.renderOpenPageCard(p)
|
|
4324
4327
|
const card = proposalCardState(key, { outcomes, requested, sentHere: this.sentProposals.has(key) })
|
|
4325
4328
|
const entry = isEntryProposal(p)
|
|
4326
4329
|
const replaced = entry && latestEntry?.get(entryGroupOf(p)) !== key && card.state === 'ready'
|
|
@@ -4415,6 +4418,22 @@ export class OxAssistantChat extends LitElement {
|
|
|
4415
4418
|
`
|
|
4416
4419
|
}
|
|
4417
4420
|
|
|
4421
|
+
/** The screen card - which list, the conditions the screen does not take, and the way to open it. */
|
|
4422
|
+
private renderOpenPageCard(p: any) {
|
|
4423
|
+
const note = openPageNote(p, (key, options) => String(i18next.t(key, options)))
|
|
4424
|
+
const open = () => this.dispatchEvent(new CustomEvent('assistant-open-page', { detail: { href: p.href }, bubbles: true, composed: true }))
|
|
4425
|
+
return html`
|
|
4426
|
+
<div class="proposal workspace">
|
|
4427
|
+
<md-icon>${p.icon || 'open_in_new'}</md-icon>
|
|
4428
|
+
<span class="pbody">
|
|
4429
|
+
<span class="plabel">${p.label}</span>
|
|
4430
|
+
${note ? html`<span class="preason">${note}</span>` : nothing}
|
|
4431
|
+
</span>
|
|
4432
|
+
<button class="run" @click=${open}>${i18next.t('ai-assistant.screen.open', { defaultValue: 'open the screen' })}</button>
|
|
4433
|
+
</div>
|
|
4434
|
+
`
|
|
4435
|
+
}
|
|
4436
|
+
|
|
4418
4437
|
/** The AI 작업판 card - what it holds, which version, and the way to open it. The person opens it; the model does not. */
|
|
4419
4438
|
private renderWorkspaceCard(p: any, key: string) {
|
|
4420
4439
|
const { version, of } = versionOfCard(this.lines.map(one => one.proposals ?? []), p.workspace.name, key)
|
|
@@ -109,9 +109,9 @@ export class AssistantDock extends localize(i18next)(LitElement) {
|
|
|
109
109
|
@media (max-width: 1439.98px) {
|
|
110
110
|
:host {
|
|
111
111
|
position: fixed;
|
|
112
|
-
inset:
|
|
113
|
-
height: 100vh;
|
|
114
|
-
height: 100dvh;
|
|
112
|
+
inset: var(--assistant-dock-overlay-top, 0px) 0 0 auto;
|
|
113
|
+
height: calc(100vh - var(--assistant-dock-overlay-top, 0px));
|
|
114
|
+
height: calc(100dvh - var(--assistant-dock-overlay-top, 0px));
|
|
115
115
|
z-index: 10;
|
|
116
116
|
box-shadow: var(--md-sys-elevation-shadow-3, -4px 0 16px rgb(0 0 0 / 20%));
|
|
117
117
|
}
|
|
@@ -329,6 +329,23 @@ export class AssistantDock extends localize(i18next)(LitElement) {
|
|
|
329
329
|
|
|
330
330
|
private onStartNewSession = () => void this.newConversation()
|
|
331
331
|
|
|
332
|
+
/** An editor can reserve its toolbar above the floating dock. Other apps keep the full-height default. */
|
|
333
|
+
@property({ attribute: false }) overlayBoundary?: () => Element | undefined
|
|
334
|
+
private boundary?: Element
|
|
335
|
+
private boundaryObserver?: ResizeObserver
|
|
336
|
+
private placeOverlay = () => {
|
|
337
|
+
const boundary = this.overlayBoundary?.()
|
|
338
|
+
if (boundary !== this.boundary) {
|
|
339
|
+
this.boundaryObserver?.disconnect()
|
|
340
|
+
this.boundary = boundary
|
|
341
|
+
if (boundary) {
|
|
342
|
+
this.boundaryObserver = new ResizeObserver(this.placeOverlay)
|
|
343
|
+
this.boundaryObserver.observe(boundary)
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
this.style.setProperty('--assistant-dock-overlay-top', `${boundary?.getBoundingClientRect().bottom ?? 0}px`)
|
|
347
|
+
}
|
|
348
|
+
|
|
332
349
|
/* A card asks for a page of this application - the AI 작업판. The dock stays open beside it. */
|
|
333
350
|
private onOpenPage = (event: CustomEvent) => {
|
|
334
351
|
const href = event.detail?.href
|
|
@@ -349,6 +366,8 @@ export class AssistantDock extends localize(i18next)(LitElement) {
|
|
|
349
366
|
|
|
350
367
|
connectedCallback() {
|
|
351
368
|
super.connectedCallback()
|
|
369
|
+
window.addEventListener('resize', this.placeOverlay)
|
|
370
|
+
this.placeOverlay()
|
|
352
371
|
this.addEventListener('assistant-start-new-session', this.onStartNewSession)
|
|
353
372
|
this.addEventListener('assistant-open-page', this.onOpenPage as EventListener)
|
|
354
373
|
this.unsubscribers.push(
|
|
@@ -371,6 +390,9 @@ export class AssistantDock extends localize(i18next)(LitElement) {
|
|
|
371
390
|
|
|
372
391
|
/* Closing keeps the conversation: the dock comes back as it was left (design/ai/shared-dock.md §2-1, §2-7). */
|
|
373
392
|
disconnectedCallback() {
|
|
393
|
+
window.removeEventListener('resize', this.placeOverlay)
|
|
394
|
+
this.boundaryObserver?.disconnect()
|
|
395
|
+
this.boundary = undefined
|
|
374
396
|
this.removeEventListener('assistant-start-new-session', this.onStartNewSession)
|
|
375
397
|
this.removeEventListener('assistant-open-page', this.onOpenPage as EventListener)
|
|
376
398
|
this.unsubscribers.splice(0).forEach(unsubscribe => unsubscribe())
|
|
@@ -417,6 +439,7 @@ export class AssistantDock extends localize(i18next)(LitElement) {
|
|
|
417
439
|
|
|
418
440
|
/** The screen changed - a different surface or domain is a different conversation. */
|
|
419
441
|
private follow(force = false) {
|
|
442
|
+
this.placeOverlay()
|
|
420
443
|
const next = currentAssistantSurface()
|
|
421
444
|
const transition = surfaceTransition({
|
|
422
445
|
identityChanged: this.openedIdentity !== this.identityKey(),
|
|
@@ -117,7 +117,7 @@ export class AssistantDockButton extends LitElement {
|
|
|
117
117
|
|
|
118
118
|
let installed = false
|
|
119
119
|
|
|
120
|
-
export function setupAssistantDock(options: { viewpart?: string } = {}): void {
|
|
120
|
+
export function setupAssistantDock(options: { viewpart?: string; overlayBoundary?: () => Element | undefined } = {}): void {
|
|
121
121
|
if (installed) return
|
|
122
122
|
installed = true
|
|
123
123
|
const viewpart = options.viewpart ?? ASSISTANT_DOCK_VIEWPART
|
|
@@ -129,8 +129,9 @@ export function setupAssistantDock(options: { viewpart?: string } = {}): void {
|
|
|
129
129
|
* makes the element would make a new dock on every opening - and a new composer, the text being written gone
|
|
130
130
|
* (design/ai/shared-dock.md §2-1, §2-7). The same element is put back instead, as the entity360 dock is.
|
|
131
131
|
*/
|
|
132
|
-
const dock = document.createElement('ox-assistant-dock') as HTMLElement & { viewpart: string }
|
|
132
|
+
const dock = document.createElement('ox-assistant-dock') as HTMLElement & { viewpart: string; overlayBoundary?: () => Element | undefined }
|
|
133
133
|
dock.viewpart = viewpart
|
|
134
|
+
dock.overlayBoundary = options.overlayBoundary
|
|
134
135
|
|
|
135
136
|
appendViewpart({
|
|
136
137
|
name: viewpart,
|
|
@@ -46,6 +46,7 @@ export interface EntryRowResult {
|
|
|
46
46
|
|
|
47
47
|
export interface EntryApplyAnswer extends ProposalOutcome {
|
|
48
48
|
rows?: EntryRowResult[]
|
|
49
|
+
refusal?: { message: string; extensions: Record<string, unknown>; path: string[] }
|
|
49
50
|
/** Set by the handler from the card - which words the conversation line uses. */
|
|
50
51
|
op?: string
|
|
51
52
|
}
|
|
@@ -96,7 +97,12 @@ export function entryOutcomeNote(answer: EntryApplyAnswer, t: (key: string, opti
|
|
|
96
97
|
export function entryProposalHandler(
|
|
97
98
|
apply: (sessionId: string, proposalKey: string, reviewedHash?: string) => Promise<EntryApplyAnswer>,
|
|
98
99
|
t: (key: string, options?: any) => string,
|
|
99
|
-
reviewedOf: (key: string) => string | undefined = reviewedHashOf
|
|
100
|
+
reviewedOf: (key: string) => string | undefined = reviewedHashOf,
|
|
101
|
+
reportRefusal: (notification: { level: 'error'; message: string; ex: NonNullable<EntryApplyAnswer['refusal']>[] }) => void | Promise<void> = async notification => {
|
|
102
|
+
/* The generic toast transport. auth-ui alone interprets the refusal and offers an access request. */
|
|
103
|
+
const { notify } = await import('@operato/layout')
|
|
104
|
+
notify(notification)
|
|
105
|
+
}
|
|
100
106
|
) {
|
|
101
107
|
return async (proposal: any, context: ProposalContext): Promise<ProposalOutcome> => {
|
|
102
108
|
if (!context.sessionId || !proposal?.key) return { verdict: 'unavailable', errorCode: 'no-conversation' }
|
|
@@ -104,6 +110,13 @@ export function entryProposalHandler(
|
|
|
104
110
|
const reviewed = reviewedOf(proposal.key)
|
|
105
111
|
if (!reviewStillHolds(proposal, reviewed)) return { verdict: 'refused', errorCode: 'review-stale', note: t('ai-assistant.entry.review-stale', { defaultValue: 'this proposal changed after it was reviewed in the workspace - review it again before entering.' }) } as any
|
|
106
112
|
const answer = { ...(await apply(context.sessionId, proposal.key, reviewed)), op: proposal.op }
|
|
113
|
+
if (answer.verdict === 'refused' && answer.errorCode === 'door-closed' && answer.refusal) {
|
|
114
|
+
try {
|
|
115
|
+
await reportRefusal({ level: 'error', message: answer.refusal.message, ex: [answer.refusal] })
|
|
116
|
+
} catch {
|
|
117
|
+
/* Notification delivery is best-effort; it never changes the stored proposal's refusal. */
|
|
118
|
+
}
|
|
119
|
+
}
|
|
107
120
|
if (answer.errorCode === 'review-stale')
|
|
108
121
|
return { verdict: 'refused', errorCode: 'review-stale', applied: 0, missed: 0, note: t('ai-assistant.entry.review-stale', { defaultValue: 'this proposal changed after it was reviewed in the workspace - review it again before entering.' }) }
|
|
109
122
|
return {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The card that opens an application's list screen with the conditions of a question (design/ai/read-targets.md §5).
|
|
3
|
+
* It reads nothing and runs nothing - the person presses it and the page opens beside the dock.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** An address inside the application - never another origin, never a script. */
|
|
7
|
+
export function isAppAddress(href: unknown): href is string {
|
|
8
|
+
return typeof href === 'string' && !!href.trim() && !/^[a-z][a-z0-9+.-]*:/i.test(href.trim()) && !href.trim().startsWith('//')
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function isOpenPageProposal(proposal: any): boolean {
|
|
12
|
+
return proposal?.kind === 'open-page' && isAppAddress(proposal?.href)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** The line under the card's title - the conditions the screen does not take, said, never dropped quietly. */
|
|
16
|
+
export function openPageNote(proposal: any, t: (key: string, options?: any) => string): string {
|
|
17
|
+
const missing: string[] = Array.isArray(proposal?.notAppliedLabels) ? proposal.notAppliedLabels : []
|
|
18
|
+
return missing.length
|
|
19
|
+
? t('ai-assistant.screen.not-applied', { defaultValue: 'the screen does not take: {conditions}', conditions: missing.join(', ') })
|
|
20
|
+
: ''
|
|
21
|
+
}
|
|
@@ -560,6 +560,8 @@ export declare class OxAssistantChat extends LitElement {
|
|
|
560
560
|
* 실행 결과는 호스트가 **대화에 기록**한다 — 누가 무엇을 실행했는지가 협의 이력에 남아야 한다.
|
|
561
561
|
*/
|
|
562
562
|
private renderProposals;
|
|
563
|
+
/** The screen card - which list, the conditions the screen does not take, and the way to open it. */
|
|
564
|
+
private renderOpenPageCard;
|
|
563
565
|
/** The AI 작업판 card - what it holds, which version, and the way to open it. The person opens it; the model does not. */
|
|
564
566
|
private renderWorkspaceCard;
|
|
565
567
|
/**
|
|
@@ -54,6 +54,7 @@ import { availableTriggers } from './chat-triggers.js';
|
|
|
54
54
|
import './entry-review.js';
|
|
55
55
|
import { entryGroupOf, entryReadyCount, entryTitle, isChangeProposal, isEntryProposal, latestEntryKeys } from '../utils/entry-proposal.js';
|
|
56
56
|
import { isWorkspaceProposal, versionOfCard, workspaceAddress } from '../utils/workspace.js';
|
|
57
|
+
import { isOpenPageProposal, openPageNote } from '../utils/open-page.js';
|
|
57
58
|
function formatFileSize(bytes) {
|
|
58
59
|
if (!bytes || bytes < 1024)
|
|
59
60
|
return `${bytes || 0} B`;
|
|
@@ -3868,6 +3869,9 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
3868
3869
|
/* A workspace card opens the page; it runs nothing (design/ai/workspace.md §4). */
|
|
3869
3870
|
if (isWorkspaceProposal(p))
|
|
3870
3871
|
return this.renderWorkspaceCard(p, key);
|
|
3872
|
+
/* A screen card opens the application's list with the question's conditions; it runs nothing (design/ai/read-targets.md §5). */
|
|
3873
|
+
if (isOpenPageProposal(p))
|
|
3874
|
+
return this.renderOpenPageCard(p);
|
|
3871
3875
|
const card = proposalCardState(key, { outcomes, requested, sentHere: this.sentProposals.has(key) });
|
|
3872
3876
|
const entry = isEntryProposal(p);
|
|
3873
3877
|
const replaced = entry && latestEntry?.get(entryGroupOf(p)) !== key && card.state === 'ready';
|
|
@@ -3959,6 +3963,21 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
3959
3963
|
</div>
|
|
3960
3964
|
`;
|
|
3961
3965
|
}
|
|
3966
|
+
/** The screen card - which list, the conditions the screen does not take, and the way to open it. */
|
|
3967
|
+
renderOpenPageCard(p) {
|
|
3968
|
+
const note = openPageNote(p, (key, options) => String(i18next.t(key, options)));
|
|
3969
|
+
const open = () => this.dispatchEvent(new CustomEvent('assistant-open-page', { detail: { href: p.href }, bubbles: true, composed: true }));
|
|
3970
|
+
return html `
|
|
3971
|
+
<div class="proposal workspace">
|
|
3972
|
+
<md-icon>${p.icon || 'open_in_new'}</md-icon>
|
|
3973
|
+
<span class="pbody">
|
|
3974
|
+
<span class="plabel">${p.label}</span>
|
|
3975
|
+
${note ? html `<span class="preason">${note}</span>` : nothing}
|
|
3976
|
+
</span>
|
|
3977
|
+
<button class="run" @click=${open}>${i18next.t('ai-assistant.screen.open', { defaultValue: 'open the screen' })}</button>
|
|
3978
|
+
</div>
|
|
3979
|
+
`;
|
|
3980
|
+
}
|
|
3962
3981
|
/** The AI 작업판 card - what it holds, which version, and the way to open it. The person opens it; the model does not. */
|
|
3963
3982
|
renderWorkspaceCard(p, key) {
|
|
3964
3983
|
const { version, of } = versionOfCard(this.lines.map(one => one.proposals ?? []), p.workspace.name, key);
|