@agent-native/core 0.210.1 → 0.211.0-nightly-20261020183616
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/dist/action.d.ts +8 -0
- package/dist/action.js +17 -0
- package/dist/agent/production-agent.js +11 -20
- package/dist/client/chat/agentkit-protocol.js +48 -5
- package/dist/client/chat/runtime.d.ts +2 -0
- package/dist/client/chat/runtime.js +6 -1
- package/dist/client/hooks/index.d.ts +1 -1
- package/dist/client/hooks/index.js +1 -1
- package/dist/client/resources/mcp-integration-logos.js +4 -0
- package/dist/client/session-identity.d.ts +8 -0
- package/dist/client/session-identity.js +13 -0
- package/dist/client/use-action.d.ts +14 -0
- package/dist/client/use-action.js +117 -28
- package/dist/client/use-agent-engine-configured.d.ts +2 -2
- package/dist/client/use-agent-engine-configured.js +3 -9
- package/dist/client/use-session.js +2 -0
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/deploy/build.js +24 -3
- package/dist/mcp/actions/revoke-org-service-token.js +5 -1
- package/dist/mcp/execute-action-call.js +8 -17
- package/dist/observability/routes.d.ts +5 -5
- package/dist/org/actions/set-app-member-roles.js +4 -0
- package/dist/org/actions/set-app-permission-roles.js +4 -0
- package/dist/org/actions/set-workspace-app-access.js +4 -0
- package/dist/org/index.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +17 -17
- package/dist/provider-api/actions/provider-api.d.ts +11 -11
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/review/suggestions/actions.d.ts +1 -0
- package/dist/review/suggestions/actions.js +54 -42
- package/dist/secrets/actions/delete-api-key.js +4 -0
- package/dist/secrets/routes.d.ts +4 -4
- package/dist/server/action-approval.d.ts +15 -0
- package/dist/server/action-approval.js +39 -0
- package/dist/server/action-routes.js +33 -28
- package/dist/server/edge.d.ts +1 -0
- package/dist/server/edge.js +1 -0
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/server/workspace-provider-oauth.js +1 -0
- package/dist/shared/action-error-codes.d.ts +5 -0
- package/dist/shared/action-error-codes.js +5 -0
- package/dist/shared/index.d.ts +1 -0
- package/dist/shared/index.js +1 -0
- package/dist/templates/chat/.agents/skills/actions/references/action-fields.md +5 -3
- package/dist/templates/default/.agents/skills/actions/references/action-fields.md +5 -3
- package/dist/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +105 -33
- package/dist/templates/factory/.agents/skills/review-latest-feedback/references/channel-inventory.md +128 -74
- package/dist/templates/factory/.agents/skills/review-latest-feedback/references/slack-reaction-replies.md +39 -11
- package/dist/templates/headless/.agents/skills/actions/references/action-fields.md +5 -3
- package/dist/templates/workspace-core/.agents/skills/actions/references/action-fields.md +5 -3
- package/dist/vite/client.js +2 -1
- package/docs/content/actions-access-control.mdx +3 -3
- package/package.json +4 -4
- package/src/templates/chat/.agents/skills/actions/references/action-fields.md +5 -3
- package/src/templates/default/.agents/skills/actions/references/action-fields.md +5 -3
- package/src/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +105 -33
- package/src/templates/factory/.agents/skills/review-latest-feedback/references/channel-inventory.md +128 -74
- package/src/templates/factory/.agents/skills/review-latest-feedback/references/slack-reaction-replies.md +39 -11
- package/src/templates/headless/.agents/skills/actions/references/action-fields.md +5 -3
- package/src/templates/workspace-core/.agents/skills/actions/references/action-fields.md +5 -3
|
@@ -3,7 +3,7 @@ import "../authorization/check-action.js";
|
|
|
3
3
|
import { verifyA2ATokenWithClaims } from "../a2a-claims.js";
|
|
4
4
|
import { isA2AIdentityVerificationUnavailableError } from "../a2a/server.js";
|
|
5
5
|
import { actionChangeResource } from "../action-call-classification.js";
|
|
6
|
-
import { actionCallIntent, ActionContractError, isActionContractError, isActionExposedToExternalAgents, isAgentActionStopError, failedCallEmitsChange, recordInvalidActionCall, preparedCallArguments, ranCallEmitsChange, runPreparedActionCall, } from "../action.js";
|
|
6
|
+
import { actionCallIntent, ActionContractError, isActionContractError, isActionExposedToExternalAgents, isAgentActionStopError, failedCallEmitsChange, recordInvalidActionCall, preparedCallArguments, preparedCallNeedsApproval, ranCallEmitsChange, runPreparedActionCall, } from "../action.js";
|
|
7
7
|
import { LLM_CREDENTIAL_KEYS, LLM_MISSING_CREDENTIALS_ERROR_CODE, LLM_MISSING_CREDENTIALS_MESSAGE, } from "../agent/engine/credential-errors.js";
|
|
8
8
|
import { isTransientDatabaseError } from "../db/client.js";
|
|
9
9
|
import { declaresFeatureFlagDelegation } from "../feature-flags/a2a-action-route.js";
|
|
@@ -13,12 +13,13 @@ import { isWorkspaceAppAccessUnavailable, workspaceAppAccessUnavailableBody, } f
|
|
|
13
13
|
import { checkWorkspaceAppAccessForRequest } from "../org/workspace-app-access-request.js";
|
|
14
14
|
import { ACTION_BROWSER_PERSIST_ALLOW, ACTION_BROWSER_PERSIST_HEADER, } from "../shared/action-browser-persist.js";
|
|
15
15
|
import { ACTION_CHANGE_MARKER_FAILED, ACTION_CHANGE_MARKER_HEADER, } from "../shared/action-change-marker-header.js";
|
|
16
|
-
import { LLM_PROVIDER_MISSING_ERROR_CODE, LLM_PROVIDER_MISSING_STATUS, } from "../shared/action-error-codes.js";
|
|
16
|
+
import { LLM_PROVIDER_MISSING_ERROR_CODE, LLM_PROVIDER_MISSING_STATUS, UI_CAPABILITY_REQUIRED_ERROR_CODE, } from "../shared/action-error-codes.js";
|
|
17
17
|
import { agentNativeMcpInstructions, agentNativeToolTitle, } from "../shared/agent-mcp-metadata.js";
|
|
18
18
|
import { EMBED_TARGET_HEADER, EMBED_TARGET_QUERY_PARAM, EMBED_TOKEN_QUERY_PARAM, MCP_DIRECTORY_WIDGET_SESSION_EXPIRED_HEADER, allowsMcpDirectoryWidgetReadAction, getMcpDirectoryWidgetReadCapabilityResourceIds, getMcpDirectoryWidgetWriteCapabilityGrant, isExpiredMcpDirectoryWidgetWriteCapability, isMcpDirectoryWidgetCapabilityScope, isMcpDirectoryWidgetReadCapabilityScope, isMcpDirectoryWidgetWriteCapabilityScope, normalizeMcpDirectoryWidgetReadActionArguments, normalizeMcpDirectoryWidgetWriteActionArguments, } from "../shared/embed-auth.js";
|
|
19
19
|
import { isMcpEmbedCorsOrigin, MCP_EMBED_CORS_ALLOW_HEADERS, shouldAllowMcpEmbedCredentials, } from "../shared/mcp-embed-headers.js";
|
|
20
20
|
import { countActionFailure, countCredentialState, } from "../tracking/failure-counters.js";
|
|
21
21
|
import { redact, redactErrorStack } from "../tracking/redaction.js";
|
|
22
|
+
import { actionApprovalRequiredError } from "./action-approval.js";
|
|
22
23
|
import { ACTION_ROUTE_PREFIX, bindActionBatch } from "./action-batch.js";
|
|
23
24
|
import { notifyActionChangeForResponse } from "./action-change.js";
|
|
24
25
|
import { readBrowserSessionIdHeader, readBrowserTabIdHeader, readAnalyticsClientPlatformHeader, readSyntheticTrafficHeader, resolveAgentRunOrgId, seedAgentRunOwnerContext, } from "./agent-run-context.js";
|
|
@@ -150,6 +151,17 @@ function isFrontendActionRequest(event) {
|
|
|
150
151
|
return false;
|
|
151
152
|
}
|
|
152
153
|
}
|
|
154
|
+
/**
|
|
155
|
+
* The frontend header is only the client's claim. A request proves it came
|
|
156
|
+
* from this app's signed-in UI with a same-origin request that carries the
|
|
157
|
+
* short-lived UI capability the browser minted for the same user.
|
|
158
|
+
*/
|
|
159
|
+
function isProvenUiRequest(event, caller, userEmail) {
|
|
160
|
+
return (caller === "frontend" &&
|
|
161
|
+
Boolean(userEmail) &&
|
|
162
|
+
isSameOriginRequest(event) &&
|
|
163
|
+
hasUiActionCapability(event, userEmail));
|
|
164
|
+
}
|
|
153
165
|
function getAllowedCorsOrigin(origin) {
|
|
154
166
|
const allowedOrigin = resolveAllowedCorsOrigin(origin, {
|
|
155
167
|
allowedOrigins: readCorsAllowedOrigins(),
|
|
@@ -793,16 +805,12 @@ function mountActionRoutesInternal(nitroApp, actions, options) {
|
|
|
793
805
|
: isFrontendActionRequest(event)
|
|
794
806
|
? "frontend"
|
|
795
807
|
: "http");
|
|
796
|
-
const
|
|
797
|
-
if (entry.uiOnly === true &&
|
|
798
|
-
(!frontendCaller ||
|
|
799
|
-
!userEmail ||
|
|
800
|
-
!isSameOriginRequest(event) ||
|
|
801
|
-
!hasUiActionCapability(event, userEmail))) {
|
|
808
|
+
const provenUiRequest = isProvenUiRequest(event, requestCaller, userEmail);
|
|
809
|
+
if (entry.uiOnly === true && !provenUiRequest) {
|
|
802
810
|
setResponseStatus(event, 403);
|
|
803
811
|
return {
|
|
804
812
|
error: "This action can only be called from the signed-in app UI.",
|
|
805
|
-
errorCode:
|
|
813
|
+
errorCode: UI_CAPABILITY_REQUIRED_ERROR_CODE,
|
|
806
814
|
};
|
|
807
815
|
}
|
|
808
816
|
const timezone = readTimezoneHeader(event);
|
|
@@ -1090,25 +1098,22 @@ function mountActionRoutesInternal(nitroApp, actions, options) {
|
|
|
1090
1098
|
// Each reader below gets its own copy of the prepared
|
|
1091
1099
|
// arguments; this one is for a failure's change marker.
|
|
1092
1100
|
params = preparedCallArguments(prepared);
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
if (mustApprove) {
|
|
1110
|
-
throw new ActionContractError(`"${name}" requires human approval for these arguments. WebMCP tool calls cannot grant that approval themselves — ask the user to confirm this action in chat, then call it there.`, { errorCode: "approval_required", statusCode: 409 });
|
|
1111
|
-
}
|
|
1101
|
+
// Approval is given in the app: in its UI, or in an agent chat
|
|
1102
|
+
// or MCP flow that runs the action in-process. No other HTTP
|
|
1103
|
+
// caller can grant it. The app's UI is the proven browser app or
|
|
1104
|
+
// its MCP directory widget, whose capability is scoped to the
|
|
1105
|
+
// resources and actions the user is working on.
|
|
1106
|
+
if (!provenUiRequest &&
|
|
1107
|
+
!directoryWidgetReadAllowed &&
|
|
1108
|
+
!directoryWidgetWriteAllowed &&
|
|
1109
|
+
(await preparedCallNeedsApproval(entry, prepared, {
|
|
1110
|
+
userEmail,
|
|
1111
|
+
orgId: orgId ?? null,
|
|
1112
|
+
appId: options?.appId,
|
|
1113
|
+
caller,
|
|
1114
|
+
actionName: name,
|
|
1115
|
+
}))) {
|
|
1116
|
+
throw actionApprovalRequiredError(name, caller, Boolean(verifiedServiceIdentity));
|
|
1112
1117
|
}
|
|
1113
1118
|
const result = await runPreparedActionCall(entry, prepared, runContext, execution);
|
|
1114
1119
|
// The change follows what ran in the call, with the arguments
|
package/dist/server/edge.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export { createOnboardingPlugin, defaultOnboardingPlugin, } from "../onboarding/
|
|
|
15
15
|
export { createOrgPlugin, defaultOrgPlugin } from "../org/plugin.js";
|
|
16
16
|
export { createResourcesPlugin, defaultResourcesPlugin, } from "./resources-plugin.js";
|
|
17
17
|
export { getH3App, awaitBootstrap, markDefaultPluginProvided, type H3AppShim, } from "./framework-request-handler.js";
|
|
18
|
+
export { directHttpActionApprovalRefusal } from "./action-approval.js";
|
|
18
19
|
export { runWithRequestContext } from "./request-context.js";
|
|
19
20
|
export { isSameOriginRequest } from "./request-origin.js";
|
|
20
21
|
export { hasUiActionCapability, mountUiActionCapabilityRoute, } from "./ui-action-capability.js";
|
package/dist/server/edge.js
CHANGED
|
@@ -14,6 +14,7 @@ export { createOnboardingPlugin, defaultOnboardingPlugin, } from "../onboarding/
|
|
|
14
14
|
export { createOrgPlugin, defaultOrgPlugin } from "../org/plugin.js";
|
|
15
15
|
export { createResourcesPlugin, defaultResourcesPlugin, } from "./resources-plugin.js";
|
|
16
16
|
export { getH3App, awaitBootstrap, markDefaultPluginProvided, } from "./framework-request-handler.js";
|
|
17
|
+
export { directHttpActionApprovalRefusal } from "./action-approval.js";
|
|
17
18
|
export { runWithRequestContext } from "./request-context.js";
|
|
18
19
|
export { isSameOriginRequest } from "./request-origin.js";
|
|
19
20
|
export { hasUiActionCapability, mountUiActionCapabilityRoute, } from "./ui-action-capability.js";
|
|
@@ -54,8 +54,8 @@ export declare function createRealtimeTokenHandler(): import("h3").EventHandlerW
|
|
|
54
54
|
expiresAt?: undefined;
|
|
55
55
|
ttlSeconds?: undefined;
|
|
56
56
|
} | {
|
|
57
|
-
error?: undefined;
|
|
58
57
|
token: string;
|
|
59
58
|
expiresAt: string;
|
|
60
59
|
ttlSeconds: number;
|
|
60
|
+
error?: undefined;
|
|
61
61
|
}>>;
|
|
@@ -405,6 +405,7 @@ export async function handleWorkspaceProviderOAuthCallback(event, providerId) {
|
|
|
405
405
|
? { externalAccountId: identity.accountId }
|
|
406
406
|
: {}),
|
|
407
407
|
...(identity.config ?? {}),
|
|
408
|
+
workspaceConnectionScope: flow.scope,
|
|
408
409
|
...(providerId === "salesforce"
|
|
409
410
|
? {
|
|
410
411
|
salesforceLoginUrl: flow.salesforceLoginUrl ?? SALESFORCE_PRODUCTION_LOGIN_URL,
|
|
@@ -5,3 +5,8 @@
|
|
|
5
5
|
export declare const LLM_PROVIDER_MISSING_ERROR_CODE = "llm_provider_missing";
|
|
6
6
|
/** 424 Failed Dependency: the request needs an LLM provider that is not connected. */
|
|
7
7
|
export declare const LLM_PROVIDER_MISSING_STATUS = 424;
|
|
8
|
+
/**
|
|
9
|
+
* 403: the call needs proof it came from the app's signed-in UI. The browser
|
|
10
|
+
* client mints its UI capability and retries once on this code.
|
|
11
|
+
*/
|
|
12
|
+
export declare const UI_CAPABILITY_REQUIRED_ERROR_CODE = "ui_capability_required";
|
|
@@ -5,3 +5,8 @@
|
|
|
5
5
|
export const LLM_PROVIDER_MISSING_ERROR_CODE = "llm_provider_missing";
|
|
6
6
|
/** 424 Failed Dependency: the request needs an LLM provider that is not connected. */
|
|
7
7
|
export const LLM_PROVIDER_MISSING_STATUS = 424;
|
|
8
|
+
/**
|
|
9
|
+
* 403: the call needs proof it came from the app's signed-in UI. The browser
|
|
10
|
+
* client mints its UI capability and retries once on this code.
|
|
11
|
+
*/
|
|
12
|
+
export const UI_CAPABILITY_REQUIRED_ERROR_CODE = "ui_capability_required";
|
package/dist/shared/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { agentChat, type AgentChatMessage, type AgentChatCallOptions, type AgentChatResponse, } from "./agent-chat.js";
|
|
2
2
|
export { appendAgentChatContextToMessage, splitAgentChatContextFromMessage, stripAgentChatContextFromMessage, type AgentChatMessageParts, } from "./agent-chat-context.js";
|
|
3
3
|
export { agentEnv, type EnvVar } from "./agent-env.js";
|
|
4
|
+
export { ACTION_BROWSER_PERSIST_ALLOW, ACTION_BROWSER_PERSIST_HEADER, } from "./action-browser-persist.js";
|
|
4
5
|
export { APP_STATUS, DEFAULT_APP_STATUS, getAppStatus, type AppStatus, } from "./app-status.js";
|
|
5
6
|
export { extractOAuthStateAppId, extractOAuthStateProvider, } from "./oauth-state.js";
|
|
6
7
|
export { isGoogleProfileImageUrl } from "./google-profile-image.js";
|
package/dist/shared/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { agentChat, } from "./agent-chat.js";
|
|
2
2
|
export { appendAgentChatContextToMessage, splitAgentChatContextFromMessage, stripAgentChatContextFromMessage, } from "./agent-chat-context.js";
|
|
3
3
|
export { agentEnv } from "./agent-env.js";
|
|
4
|
+
export { ACTION_BROWSER_PERSIST_ALLOW, ACTION_BROWSER_PERSIST_HEADER, } from "./action-browser-persist.js";
|
|
4
5
|
export { APP_STATUS, DEFAULT_APP_STATUS, getAppStatus, } from "./app-status.js";
|
|
5
6
|
export { extractOAuthStateAppId, extractOAuthStateProvider, } from "./oauth-state.js";
|
|
6
7
|
export { isGoogleProfileImageUrl } from "./google-profile-image.js";
|
|
@@ -95,9 +95,9 @@ MCP clients see no `outputSchema` by default. Set `mcpOutputSchema: true` (requi
|
|
|
95
95
|
|
|
96
96
|
`authorize` decides whether the caller may run the action at all. It wraps
|
|
97
97
|
`run`, so it holds at **all six dispatch sites** — agent tool, HTTP, frontend
|
|
98
|
-
hook, MCP, A2A, CLI
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
hook, MCP, A2A, CLI. Use `authorize` for "not everyone may do this"; use
|
|
99
|
+
`needsApproval` to ask a human to bless one call a permitted caller is already
|
|
100
|
+
allowed to make.
|
|
101
101
|
|
|
102
102
|
```ts
|
|
103
103
|
import { coachAccess } from "../lib/access.js"; // defineAppRoles(...)
|
|
@@ -140,4 +140,6 @@ export default defineAction({
|
|
|
140
140
|
|
|
141
141
|
When the gate is truthy and the call isn't yet approved, the loop emits an `approval_required` event and **stops the turn — `run()` never executes**. A predicate gates conditionally (e.g. only external recipients) and **fails closed**: a throw is treated as "approval required". It receives its own copy of the prepared snapshot of the validated arguments, defaults applied, and the approval binds to the snapshot's hash, so an approved call runs only with the arguments that were approved (a Date binds by its time). The human approves via the chat UI's Approve affordance, which re-issues the turn with the call's `approvalKey`, and only then does the action run.
|
|
142
142
|
|
|
143
|
+
Approval is given in the app, so HTTP never grants it. The HTTP action route and the generated Workers entry run a gated call only for the proven app UI (a frontend call from the signed-in user, same origin, holding the short-lived UI capability; `isProvenUiRequest`, the proof `uiOnly` uses) or the app's MCP directory widget. Every other HTTP caller (bearer or API key, MCP OAuth token, org service token, A2A, extension iframe, mobile app) gets `409 approval_required`, and a service identity can never approve. The shared client presents the capability with every write from a signed-in session (minted once per user, reused for nine minutes, never for reads or public pages), so a gated button works on its first request. A call without proof gets `403 ui_capability_required`; the client then re-mints and retries once, which is only the fallback for an expired or rejected capability. Every surface evaluates the gate through `preparedCallNeedsApproval`, so a throwing predicate asks there too. A caller that must run unattended needs a different action, never a bypass of the gate.
|
|
144
|
+
|
|
143
145
|
**Keep approvals rare** — the default is off and almost every action should leave it off. The canonical example is Mail's `send-email` (`needsApproval: true`). See the `security` skill and the Human Approval doc.
|
|
@@ -95,9 +95,9 @@ MCP clients see no `outputSchema` by default. Set `mcpOutputSchema: true` (requi
|
|
|
95
95
|
|
|
96
96
|
`authorize` decides whether the caller may run the action at all. It wraps
|
|
97
97
|
`run`, so it holds at **all six dispatch sites** — agent tool, HTTP, frontend
|
|
98
|
-
hook, MCP, A2A, CLI
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
hook, MCP, A2A, CLI. Use `authorize` for "not everyone may do this"; use
|
|
99
|
+
`needsApproval` to ask a human to bless one call a permitted caller is already
|
|
100
|
+
allowed to make.
|
|
101
101
|
|
|
102
102
|
```ts
|
|
103
103
|
import { coachAccess } from "../lib/access.js"; // defineAppRoles(...)
|
|
@@ -140,4 +140,6 @@ export default defineAction({
|
|
|
140
140
|
|
|
141
141
|
When the gate is truthy and the call isn't yet approved, the loop emits an `approval_required` event and **stops the turn — `run()` never executes**. A predicate gates conditionally (e.g. only external recipients) and **fails closed**: a throw is treated as "approval required". It receives its own copy of the prepared snapshot of the validated arguments, defaults applied, and the approval binds to the snapshot's hash, so an approved call runs only with the arguments that were approved (a Date binds by its time). The human approves via the chat UI's Approve affordance, which re-issues the turn with the call's `approvalKey`, and only then does the action run.
|
|
142
142
|
|
|
143
|
+
Approval is given in the app, so HTTP never grants it. The HTTP action route and the generated Workers entry run a gated call only for the proven app UI (a frontend call from the signed-in user, same origin, holding the short-lived UI capability; `isProvenUiRequest`, the proof `uiOnly` uses) or the app's MCP directory widget. Every other HTTP caller (bearer or API key, MCP OAuth token, org service token, A2A, extension iframe, mobile app) gets `409 approval_required`, and a service identity can never approve. The shared client presents the capability with every write from a signed-in session (minted once per user, reused for nine minutes, never for reads or public pages), so a gated button works on its first request. A call without proof gets `403 ui_capability_required`; the client then re-mints and retries once, which is only the fallback for an expired or rejected capability. Every surface evaluates the gate through `preparedCallNeedsApproval`, so a throwing predicate asks there too. A caller that must run unattended needs a different action, never a bypass of the gate.
|
|
144
|
+
|
|
143
145
|
**Keep approvals rare** — the default is off and almost every action should leave it off. The canonical example is Mail's `send-email` (`needsApproval: true`). See the `security` skill and the Human Approval doc.
|
|
@@ -21,7 +21,25 @@ Four phases, in order. Phase 0 comes before any investigation, not after.
|
|
|
21
21
|
2. **Fix** what the evidence actually proves, at the owning boundary.
|
|
22
22
|
3. **Reply** with useful questions, then recap.
|
|
23
23
|
|
|
24
|
-
Output is fixes. For every report root this task owns, keep one concise, current same-actor status reply after the latest human message. It names every verified `✅` scope and its evidence, every open scope, and each `🎫` action and owner (or why a mark does not apply). Add and read back root `✅` as soon as direct post-change proof verifies a scope, and root `🎫` as soon as another person or team must act. An `👀` alone never completes the status. Preserve all reaction history, and follow [`slack-reaction-replies.md`](references/slack-reaction-replies.md). Do not reply on person-assigned or `:mag:` items.
|
|
24
|
+
Output is fixes. For every unheld in-scope report root this task owns, keep one concise, current same-actor status reply after the latest human message. It names every verified `✅` scope and its evidence, every open scope, and each `🎫` action and owner (or why a mark does not apply). Add and read back root `✅` as soon as direct post-change proof verifies a scope, and root `🎫` as soon as another person or team must act. An `👀` alone never completes the status. Preserve all reaction history, and follow [`slack-reaction-replies.md`](references/slack-reaction-replies.md). Do not reply on held, person-assigned, or `:mag:` items.
|
|
25
|
+
|
|
26
|
+
### Read the root marks as history; use the reply for current status
|
|
27
|
+
|
|
28
|
+
- `👀` records this workflow's claim. It does not mean the report is still open.
|
|
29
|
+
- `✅` records a scope that passed direct post-change proof. Add it as soon as
|
|
30
|
+
any scope passes, even when another scope remains open; name the verified
|
|
31
|
+
scope and evidence in the status reply.
|
|
32
|
+
- `🎫` records a distinct action another person must take, including a reporter
|
|
33
|
+
request. Keep the reaction as history after that action is completed,
|
|
34
|
+
answered, or abandoned; the current reply must say whether the action is
|
|
35
|
+
still open or historical.
|
|
36
|
+
- Every owned root needs one current same-actor reply after the latest human
|
|
37
|
+
reply. Reuse it while the disposition and evidence remain current; do not
|
|
38
|
+
send a duplicate progress reply on every sweep. The reply names verified
|
|
39
|
+
scopes, open scopes, human actions and owners, and why either `✅` or `🎫` is
|
|
40
|
+
absent. An eye-only root is correct when no scope is verified and no separate
|
|
41
|
+
human action is pending; say whether the next step is this workflow's
|
|
42
|
+
investigation or no further action.
|
|
25
43
|
|
|
26
44
|
## Slack channels and ownership
|
|
27
45
|
|
|
@@ -34,44 +52,93 @@ channel. For a permalink, its channel ID identifies only where the exact linked
|
|
|
34
52
|
root must appear; it does not limit the cutoff or channel scope. For an
|
|
35
53
|
explicitly narrowed sweep, verify that the active request names every selected
|
|
36
54
|
channel, then record its exact text and IDs in the separate scope-confirmation
|
|
37
|
-
file.
|
|
55
|
+
file. A request can select exactly two channels by linking one report in the
|
|
56
|
+
first and directly asking for “the same in” the second, without saying “only.”
|
|
57
|
+
The checker permits one adjacent-transposition typo in that target only when it
|
|
58
|
+
uniquely matches a selected canonical channel; it rejects incidental or quoted
|
|
59
|
+
mentions and prefixes. See [`channel-inventory.md`](references/channel-inventory.md)
|
|
60
|
+
for the scope gate and its confirmation requirements. `#dev-agent-native-feedback`
|
|
61
|
+
is not excluded.
|
|
38
62
|
|
|
39
63
|
In every scoped channel, run the complete bot-inclusive `slack_search_public`
|
|
40
64
|
scan with only the channel filter and that channel's inclusive lower bound.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
65
|
+
Freeze one `windowEndTs` at sweep start and use it for every channel and
|
|
66
|
+
owned-mark search. Messages newer than it belong to the next scheduled sweep;
|
|
67
|
+
never move the boundary to chase arrivals. Exhaust every cursor;
|
|
68
|
+
`slack_read_channel` cannot replace this inventory. Resolve
|
|
69
|
+
each row to its true parent and read the root reactions before opening the thread.
|
|
70
|
+
If another actor owns any workflow mark (`👀`, `✅`, or `🎫`), or this actor owns
|
|
71
|
+
`:mag:`, apply the held-thread boundary below: do not read the thread, react, or
|
|
72
|
+
reply. Otherwise read the report's full thread and attachments before
|
|
73
|
+
classifying or claiming it. Follow
|
|
44
74
|
[`channel-inventory.md`](references/channel-inventory.md) for exact query
|
|
45
|
-
arguments, raw-page receipts, cap recovery, root classification,
|
|
46
|
-
searches for this actor's `👀`, `✅`, and `🎫`,
|
|
75
|
+
arguments, raw-page receipts, cap recovery, root classification, fixed-window
|
|
76
|
+
source scans, bounded ownership searches for this actor's `👀`, `✅`, and `🎫`,
|
|
77
|
+
and checker requirements. Owned-mark searches use Slack's exact syntax
|
|
78
|
+
`hasmy::eyes:`, `hasmy::white_check_mark:`, or `hasmy::ticket:` with the channel
|
|
79
|
+
filter and the same inclusive `after` and `before` as that channel's source
|
|
80
|
+
scan. Do not widen a finite catch-up request into an all-history audit. A root
|
|
81
|
+
older than `after` is in scope only when an in-window reply links to it. A
|
|
82
|
+
missing trailing colon is not a valid ownership query.
|
|
47
83
|
|
|
48
84
|
Only the invoking Slack actor's root marks are owned here. Another task's eye
|
|
49
85
|
plus its current open status remains theirs; an explicit handoff naming this
|
|
50
86
|
task and scope is required to transfer investigation. An eye is a permanent
|
|
51
87
|
claim, not proof of open or completed work. A mark on a reply never claims or
|
|
52
|
-
checks off its parent. Before exit, reconcile every
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
88
|
+
checks off its parent. Before exit, reconcile every in-window root carrying
|
|
89
|
+
this actor's `👀`, `✅`, or `🎫` found by the bounded searches. Also reconcile an
|
|
90
|
+
older root when an in-window reply links to it. For every owned root, compare
|
|
91
|
+
the latest human reply, current status, verified scopes, open scopes, human
|
|
92
|
+
actions, and exact root reactions.
|
|
93
|
+
Never let a high eye count hide a verified scope without `✅` or a missing
|
|
94
|
+
current status reply.
|
|
95
|
+
|
|
96
|
+
**Held-thread boundary:** A direct root reaction read showing another person's
|
|
97
|
+
`👀`, `✅`, or `🎫` means the thread belongs to that workflow; this actor's
|
|
98
|
+
`:mag:` means this actor opted out. Record the exact root, reaction owners, and
|
|
99
|
+
one reason (`foreign_workflow_mark` or `mag_opt_out`) in `ownershipSkip`, with
|
|
100
|
+
`Skipped` disposition. When this actor owns both `:mag:` and a workflow mark,
|
|
101
|
+
use `mag_opt_out` and preserve the exact root reaction-owner record as history.
|
|
102
|
+
Do not open/read that thread, inspect reply reactions, add or remove reactions,
|
|
103
|
+
or post a reply. Apply the same skip to channel reply
|
|
104
|
+
rows linked to that root. A linked pre-window root without source text remains
|
|
105
|
+
`unclassified`; do not infer its original report from the reply. This explicit
|
|
106
|
+
exception keeps ownership boundaries intact while allowing the rest of the
|
|
107
|
+
window to be reconciled. A foreign `👀` without current status is still held;
|
|
108
|
+
do not read it to decide whether its owner is active.
|
|
57
109
|
|
|
58
110
|
Only Steve's `:mag:` and `:yes-party:` count (`U0GCV21HC`). When Steve invokes,
|
|
59
|
-
exhaust `hasmy::mag:` and `hasmy::yes-party:` searches
|
|
60
|
-
|
|
61
|
-
|
|
111
|
+
exhaust `hasmy::mag:` and `hasmy::yes-party:` searches within each channel's
|
|
112
|
+
same inclusive cutoff; otherwise inspect root reactions in the requested
|
|
113
|
+
window. `:mag:` opts out and wins over `:yes-party:`. The latter authorizes only
|
|
114
|
+
an otherwise review-only item; it never overrides an assignment or another
|
|
62
115
|
task's claim.
|
|
63
116
|
|
|
64
117
|
## Phase 0: claim and reconcile
|
|
65
118
|
|
|
66
119
|
Resolve known duplicates before claiming. Check ownership, then add and read
|
|
67
120
|
back this task's root `👀` before investigating an eligible unowned report.
|
|
68
|
-
Reconcile
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
121
|
+
Reconcile only owned roots inside this invocation's fixed window and older
|
|
122
|
+
roots explicitly linked by an in-window reply. Never expand a finite sweep to
|
|
123
|
+
all historical roots just because this actor owns an old mark; widen the range
|
|
124
|
+
only when the invocation explicitly requests it or hands off exact roots. Finish
|
|
125
|
+
this bounded reconciliation before discovering new questions. Keep one
|
|
126
|
+
concise canonical status by the same actor after the latest human message; read
|
|
127
|
+
back the visible reply and root reactions after each write. Use
|
|
72
128
|
[`slack-reaction-replies.md`](references/slack-reaction-replies.md) for exact
|
|
73
129
|
status and reaction rules.
|
|
74
130
|
|
|
131
|
+
**Bounded exit gate:** enumerate the complete candidate set from the fixed
|
|
132
|
+
source window, the three matching owned-mark searches, and older roots linked
|
|
133
|
+
by in-window replies. For every unheld owned root, compare the latest human
|
|
134
|
+
message, current status reply, verified scopes, open scopes, and root marks.
|
|
135
|
+
Add and read back `✅` for each newly verified scope and `🎫` for each distinct
|
|
136
|
+
pending human action; the status reply must follow the latest human message and
|
|
137
|
+
name current evidence, remaining work, and owners. An `👀` with a verified
|
|
138
|
+
scope missing `✅`, a pending human action missing `🎫`, or no current status is
|
|
139
|
+
unfinished. Finish the complete bounded set once, then stop; do not chase
|
|
140
|
+
unrelated historical marks.
|
|
141
|
+
|
|
75
142
|
Split reports into distinct scopes. Add root `✅` as soon as any named scope
|
|
76
143
|
passes direct post-change proof, even while its PR is open; say **Verified
|
|
77
144
|
locally** and that merge is pending. Use **Fixed** only after merge and direct
|
|
@@ -97,11 +164,14 @@ action and owner. Use **Skipped** only for non-defects.
|
|
|
97
164
|
|
|
98
165
|
Phase 0 handles new human replies on owned roots first; never hide a newer
|
|
99
166
|
reporter reply behind a mark. Then find prior unanswered questions using the
|
|
100
|
-
exact bot disclosure and the saved channel reply cursor
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
167
|
+
exact bot disclosure and the saved channel reply cursor, bounded by this
|
|
168
|
+
invocation's fixed window. On a first run, use the same default five-day window;
|
|
169
|
+
never scan all available history to initialize the cursor. Exhaust pages, use a
|
|
170
|
+
one-day overlap for date-only cursors, resolve every hit to its parent, and read
|
|
171
|
+
the full thread after checking reactions. An older root is eligible only when
|
|
172
|
+
an in-window reply links to it. Follow
|
|
173
|
+
[`slack-reaction-replies.md`](references/slack-reaction-replies.md) for exact
|
|
174
|
+
searches, legacy identity handling, and answer criteria.
|
|
105
175
|
|
|
106
176
|
A valid answer is a human post after the question, without this workflow's
|
|
107
177
|
disclosure, that addresses the question. Rebuild the evidence and attempt the
|
|
@@ -316,15 +386,17 @@ send one concise disposition covering done, remaining, and next action/owner.
|
|
|
316
386
|
Do not reply again to announce merge, publication, or live beta; answer direct
|
|
317
387
|
reporter follow-ups. End Slack replies with `this was sent from a bot.`.
|
|
318
388
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
actor
|
|
323
|
-
reply
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
389
|
+
Reporter-facing replies are one or two sentences: the change or question, its limit, and the next step. Do not explain workflow mechanics, ledgers, or external tracker tickets. Never send **Open**/**pending** or a symptom recap. Ask for reporter input only when it changes the next step; never request supplied evidence or internal access. Keep hashes, branches, CI, publisher, and run details in the recap.
|
|
390
|
+
|
|
391
|
+
Every root this actor marks with `👀`, `✅`, or `🎫` also needs the current
|
|
392
|
+
same-actor status reply required by the Slack reaction reference. One concise
|
|
393
|
+
reply may serve both purposes when it answers the reporter and records the
|
|
394
|
+
current disposition, verified/open scopes, owner/action, and any reason a mark
|
|
395
|
+
is absent. This status requirement takes precedence over the reporter-facing
|
|
396
|
+
brevity rule only for those facts; do not explain ledger mechanics or external
|
|
397
|
+
tracker tickets. A Slack `🎫` reaction tracks a distinct human action, not an
|
|
398
|
+
external ticket, and remains as history after the action closes. Never imply
|
|
399
|
+
that an external ticket was created or assigned.
|
|
328
400
|
|
|
329
401
|
Keep publisher and beta checks in the internal recap after the disposition;
|
|
330
402
|
they do not trigger a second reporter reply.
|