mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-11 23:36:51 +02:00
Distinguish file delivery references from inline contents
Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
fef9e84562
commit
062902cea2
4 files changed
+28
-4
No files matched your search
@@ -431,6 +431,13 @@ prices are estimates. Unpriced usage must stay unpriced in the ledger and UI.
|
||||
in chat do not. All 76 completion tests pass; the exact failed agent-files
|
||||
objective still needs no download. Wait for clean source review before
|
||||
rebuilding public packages again.
|
||||
- At `fef9e8456`, review identifies a missing explicit "send it" delivery
|
||||
requirement. Preserve delivery references to the preceding file, including
|
||||
across sentences, while explicit inline/chat content remains inline.
|
||||
Attachment/export directives always retain publication requirements. All 84
|
||||
completion tests pass, including both delivery and inline instructions. The
|
||||
exact failed agent-files objective still needs no download; no paid retry runs
|
||||
on these intermediate candidates.
|
||||
- The `0d65753fe` broad local suite is terminal with 736 passed files, four
|
||||
skipped files, 14,982 passed tests and three failures: a Git scan load
|
||||
single-flight count (497 versus 498) and two HTTP socket resets. One associated
|
||||
|
||||
@@ -22,6 +22,11 @@ describe("explicit file output requirements", () => {
|
||||
"Write report.txt and attach it. This is an internal verification file, not a deliverable.",
|
||||
"Attach it.",
|
||||
"Write report.txt and return it as an attachment. This is an internal verification file, not a deliverable.",
|
||||
"Write report.txt and send it to me. This is an internal verification file, not a deliverable.",
|
||||
"Write report.txt. This is an internal verification file, not a deliverable. Send it to me.",
|
||||
"Write report.txt and provide it to me. This is an internal verification file, not a deliverable.",
|
||||
"Write report.txt and give me it. This is an internal verification file, not a deliverable.",
|
||||
"Write report.txt and return it. This is an internal verification file, not a deliverable.",
|
||||
])("recognizes an explicit output request: %s", objective => {
|
||||
expect(explicitlyRequestsFileOutput(objective)).toBe(true);
|
||||
});
|
||||
@@ -50,6 +55,8 @@ describe("explicit file output requirements", () => {
|
||||
"Write internal-proof.txt; do not attach it. This is an internal verification file, not a deliverable.",
|
||||
"Write a reply and return it in chat.",
|
||||
"Write internal-proof.txt and return it in chat. This is an internal verification file, not a deliverable.",
|
||||
"Write internal-proof.txt and send it in chat. This is an internal verification file, not a deliverable.",
|
||||
"Write internal-proof.txt. This is an internal verification file, not a deliverable. Return it inline.",
|
||||
])("does not require a file for a text or source-review request: %s", objective => {
|
||||
expect(explicitlyRequestsFileOutput(objective)).toBe(false);
|
||||
});
|
||||
|
||||
@@ -64,6 +64,7 @@ async function hasCurrentPublicationReceipt(db: Db, companyId: string, receipts:
|
||||
*/
|
||||
export function explicitlyRequestsFileOutput(objective: string): boolean {
|
||||
const sentences = objective.replaceAll("\\_", "_").split(/(?:[.!?](?:\s|$)|\n)/iu);
|
||||
let precedingFileOutput = false;
|
||||
return sentences.some((sentence, index) => {
|
||||
const file = /\b(?:files?|attachments?|downloads?|pdf|spreadsheets?|workbooks?|slide decks?|powerpoints?|docx|xlsx|csv)\b|\b[^\s/]+\.(?:md|txt|pdf|docx?|xlsx?|csv|pptx?|png|jpe?g|svg|zip)\b/giu;
|
||||
const outputs = sentence.split(/(?:[;,]|\bbut\b)/iu).flatMap(clause => {
|
||||
@@ -89,15 +90,19 @@ export function explicitlyRequestsFileOutput(objective: string): boolean {
|
||||
});
|
||||
// An explicit attachment/export request can refer to the file by
|
||||
// pronoun. It still requires publication when its name is omitted.
|
||||
const referencedAttachment = /^(?:attach|export)$/iu.test(create[0])
|
||||
const referencesOutput = /^(?:attach|export|send|provide|give|return)$/iu.test(create[0])
|
||||
&& /^\s+(?:me\s+)?(?:it|them|this|that)\b/iu.test(output);
|
||||
const referencedAttachment = referencesOutput && /^(?:attach|export)$/iu.test(create[0]);
|
||||
const inline = /\b(?:in (?:the )?chat|inline|as (?:a |the )?(?:chat )?(?:reply|message)|(?:its|the) contents)\b/iu.test(output);
|
||||
const downloadable = referencedAttachment || (!/\b(?:no|without)\s+(?:downloadable|attached)/iu.test(output)
|
||||
&& /\b(?:downloadable|attached)\s+(?:file|report|document|checklist|draft)\b/iu.test(output));
|
||||
const publication = /\b(?:downloadable|attached|attach|export|send|provide|return|give)\b/iu.test(create[0] + output);
|
||||
const deniedAttempt = create[0].toLowerCase() === "write" && objects.length === 1
|
||||
&& /\b(?:attempt|try)\s+(?:the\s+)?native\s*$/iu.test(before)
|
||||
&& /\b(?:must be denied|denial is (?:the )?expected|(?:this|the) negative test)\b/iu.test(objective);
|
||||
return [{ objects: objects.length, downloadable, publication, deniedAttempt }];
|
||||
if (objects.length === 0 && !downloadable && /^\s+(?:no|zero|without)\b/iu.test(output)) return [];
|
||||
return [{ objects: objects.length, downloadable, publication, deniedAttempt,
|
||||
referencesOutput, publicationReference: referencesOutput && !inline }];
|
||||
});
|
||||
});
|
||||
// "This ..." qualifies a single requested file in the preceding sentence,
|
||||
@@ -105,8 +110,12 @@ export function explicitlyRequestsFileOutput(objective: string): boolean {
|
||||
// exception and separate report requests still require publication.
|
||||
const internal = outputs.reduce((count, output) => count + output.objects + Number(output.downloadable && output.objects === 0), 0) === 1
|
||||
&& /^\s*This is (?:an? )?(?:personal memory|internal (?:assertion|verification) file)\b[^.!?]*\bnot a (?:task )?deliverable\b/iu.test(sentences[index + 1] ?? "");
|
||||
return outputs.some(output => (output.objects > 0 || output.downloadable)
|
||||
&& (output.publication || (!internal && !output.deniedAttempt)));
|
||||
return outputs.some(output => {
|
||||
const publicationReference = output.publicationReference && precedingFileOutput;
|
||||
if (!output.referencesOutput) precedingFileOutput = output.objects > 0;
|
||||
return (output.objects > 0 || output.downloadable || publicationReference)
|
||||
&& (output.publication || (!internal && !output.deniedAttempt));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -354,6 +354,7 @@ describe("native runner file handoff", () => {
|
||||
"Write report.pdf; write internal-proof.txt. This is an internal verification file, not a deliverable.",
|
||||
"Write report.pdf and attempt native write to /outside/probe.txt; this negative test must be denied.",
|
||||
"Write report.txt and attach it. This is an internal verification file, not a deliverable.",
|
||||
"Write report.txt and send it to me. This is an internal verification file, not a deliverable.",
|
||||
])("still requires publication when the task also asks for internal or denied writes: %s", async objective => {
|
||||
await db.update(heartbeatRuns).set({ contextSnapshot: { issueId, executionContinuation: { objective } } })
|
||||
.where(eq(heartbeatRuns.id, runId));
|
||||
|
||||
Reference in new issue
Block a user